1. 问题背景
某业务系统使用 QuestDB 作为时序数据库,因磁盘空间写满,导致多张表无法正常写入新数据。清理磁盘空间后,发现部分表(如 collection_map_point_data)能够执行 INSERT 但查询不到新数据,且今天的分区未能自动创建;另一些表(如 eam_data、topic_data_3)在尝试修复过程中甚至从 SHOW TABLES 中消失。本文详细记录了从问题发现、排查到最终恢复的完整过程,并提供了一套可复现的修复方案。
2. 故障现象
- INSERT 执行成功,但
SELECT查不到新数据。 - 表分区未创建:
table_partitions中不存在今天的分区。 - 表状态异常:部分表在
wal_tables中suspended = true,甚至执行ALTER后表“消失”。 - 日志报错:常见错误如
could not create partition directory、disk full、WAL 文件丢失等。
3. 前置概念
- WAL (Write-Ahead Log:预处理日志):QuestDB 为保证数据不丢和一致性,先写日志再异步合并到数据文件。磁盘满了可能导致 WAL 损坏或挂起。
- BYPASS WAL:临时绕过 WAL,数据直接写入主表。用于清理卡住的 WAL 事务,但需要重启生效。
- 表挂起 (suspended):当 WAL 进程遇到不可恢复错误时,表会被挂起,暂停写入,需要手动恢复。
4. 排查步骤
4.1 查看表基本信息
SELECT * FROM tables() WHERE name = 'your_table';
重点关注 walEnabled、partitionBy 和 timestamp 列。
4.2 检查表分区状态
SELECT * FROM table_partitions('your_table') ORDER BY name DESC;
查看今天的分区是否存在,以及 active、attached 状态。
4.3 检查表是否挂起
SELECT * FROM wal_tables() WHERE name = 'your_table';
suspended = true 表示表被挂起,需要恢复。
4.4 查看 QuestDB 日志
在 Kubernetes 环境中:
kubectl logs <pod-name> --tail=500 | grep -E "ERROR|your_table"
物理部署则查看 ~/questdb/log/questdb.log。日志是定位根本原因的最重要依据。
5. 修复方法
5.1 恢复挂起的 WAL 表
如果 suspended = true,首先尝试恢复:
ALTER TABLE 'your_table' RESUME WAL;
执行后再次插入测试数据,观察是否恢复正常。
5.2 跳过损坏的 WAL 事务
如果恢复后依然无法创建分区,可能某个 WAL 段损坏。查询当前事务号并跳过损坏事务:
-- 查看当前写入事务
SELECT writerTxn FROM wal_tables() WHERE name = 'your_table';
-- 列出待处理事务
SELECT sequencerTxn, walId, segmentId FROM wal_transactions('your_table')
WHERE sequencerTxn > writerTxn ORDER BY sequencerTxn;
-- 从下一个事务开始恢复
ALTER TABLE 'your_table' RESUME WAL FROM TXN <writerTxn+1>;
5.3 强力修复:BYPASS WAL + 重启
当上述方法无效时,采用“绕过 WAL + 两次重启”的终极方案:
-- 步骤1:切换为 BYPASS WAL
ALTER TABLE 'your_table' SET TYPE BYPASS WAL;
-- 步骤2:重启 QuestDB(在 K8s 中删除 Pod 使其重建)
kubectl delete pod <pod-name>
-- 步骤3:验证表已处于非 WAL 模式
SELECT walEnabled FROM tables() WHERE name = 'your_table'; -- 应为 false
-- 步骤4:切回 WAL 模式
ALTER TABLE 'your_table' SET TYPE WAL;
-- 步骤5:再次重启
kubectl delete pod <pod-name>
-- 步骤6:验证 WAL 已恢复
SELECT walEnabled FROM tables() WHERE name = 'your_table'; -- 应为 true
此方法会清空所有卡住的 WAL 事务,使表回归干净状态。已落盘的历史数据不会丢失,但未提交的 WAL 数据(即之前插入但查不到的)可能会被丢弃。
5.4 批量修复多张表
如果有多个表同时挂起,可以一次性处理,减少重启次数:
-- 批量切换 BYPASS WAL
ALTER TABLE 'table1' SET TYPE BYPASS WAL;
ALTER TABLE 'table2' SET TYPE BYPASS WAL;
...
-- 重启一次
-- 批量切回 WAL
ALTER TABLE 'table1' SET TYPE WAL;
ALTER TABLE 'table2' SET TYPE WAL;
...
-- 再重启一次
无论多少张表,只需两次重启。
6. 表丢失后的重建
如果在修复过程中表从 SHOW TABLES 中消失(如 eam_data、topic_data_3),请先确认物理目录是否还在:
kubectl exec -it <pod> -- ls -la /root/.questdb/db/ | grep 表名
如果目录存在(例如 eam_data~45),数据文件大概率还在,可以尝试重建表结构并导入数据。若目录已丢失,只能重建空表让业务重新写入。
6.1 根据插入代码推断表结构
以 eam_data 为例,其 Java 插入代码如下:
String sql = "INSERT INTO eam_data VALUES (?,?,?,?,?,?)";
preparedStatement.setString(1, itemId);
preparedStatement.setString(2, recordId);
preparedStatement.setInt(3, 0);
preparedStatement.setString(4, value);
preparedStatement.setString(5, quality);
preparedStatement.setTimestamp(6, timestamp);
对应列及类型:
| 位置 | 含义 | 列名 | 数据类型 |
|---|---|---|---|
| 1 | 设备项ID | item_id | STRING |
| 2 | 记录ID | record_id | STRING |
| 3 | 标志位 | flag | INT |
| 4 | 数值 | value | STRING |
| 5 | 质量戳 | quality | STRING |
| 6 | 时间戳 | timestamp | TIMESTAMP |
6.2 重建表的 SQL
CREATE TABLE 'eam_data' (
item_id STRING,
record_id STRING,
flag INT,
value STRING,
quality STRING,
timestamp TIMESTAMP
) timestamp (timestamp) PARTITION BY DAY WAL;
根据实际业务,item_id 若基数较低可改为 SYMBOL 类型以优化存储和查询性能。
6.3 从旧目录恢复数据(如果目录还在)
若旧表数据目录存在,可尝试按分区导入:
-- 创建新表后,从旧表复制可读分区(假设旧表在系统表中仍可见)
INSERT INTO 'eam_data' SELECT * FROM 'eam_data_old' WHERE timestamp < '2024-03-14';
若旧表完全不可见,可能需要更底层的文件恢复操作(本文不展开),建议优先从业务源头重新生成数据。
7. 在 Kubernetes 环境中的注意事项
- 重启 = 删除 Pod:K8s 中没有传统
systemctl restart,通过kubectl delete pod让控制器重建 Pod 即可实现重启。 - 数据持久性:确保 QuestDB 的数据目录挂载了持久卷(PVC),否则 Pod 删除后数据会丢失。
- 服务中断:删除 Pod 会导致短暂服务不可用,需确保应用有重连机制,并在低峰期操作。
- 日志查看:使用
kubectl logs获取实时日志,方便排查。
8. 预防措施
- 磁盘监控:设置磁盘使用率告警(如 80% 预警),及时清理或扩容。
- 定期维护:对于按天分区的表,可定期删除过期分区(
ALTER TABLE DROP PARTITION)释放空间。 - 备份策略:对重要表定期备份,可通过
COPY导出为 Parquet 或直接备份 PVC。 - WAL 参数调整:根据写入量调整
maxUncommittedRows和o3MaxLag,避免 WAL 积压过多。
9. 总结
QuestDB 的 WAL 机制在磁盘满时容易进入不一致状态,导致写入失败。通过 RESUME WAL、跳过损坏事务、BYPASS WAL 配合重启等步骤,绝大多数挂起表都能恢复。对于极端情况下的表丢失,可依据应用代码重建表结构并恢复业务写入。整个修复过程需结合日志、系统表和分区状态进行精准诊断,切忌盲目操作。希望本文能为遇到类似问题的读者提供清晰的解决路径。