QuestDB 磁盘满故障恢复实战指南

0 阅读6分钟

1. 问题背景

某业务系统使用 QuestDB 作为时序数据库,因磁盘空间写满,导致多张表无法正常写入新数据。清理磁盘空间后,发现部分表(如 collection_map_point_data)能够执行 INSERT 但查询不到新数据,且今天的分区未能自动创建;另一些表(如 eam_datatopic_data_3)在尝试修复过程中甚至从 SHOW TABLES 中消失。本文详细记录了从问题发现、排查到最终恢复的完整过程,并提供了一套可复现的修复方案。

2. 故障现象

  • INSERT 执行成功,但 SELECT 查不到新数据。
  • 表分区未创建table_partitions 中不存在今天的分区。
  • 表状态异常:部分表在 wal_tablessuspended = true,甚至执行 ALTER 后表“消失”。
  • 日志报错:常见错误如 could not create partition directorydisk 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';

重点关注 walEnabledpartitionBytimestamp 列。

4.2 检查表分区状态

SELECT * FROM table_partitions('your_table') ORDER BY name DESC;

查看今天的分区是否存在,以及 activeattached 状态。

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_datatopic_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设备项IDitem_idSTRING
2记录IDrecord_idSTRING
3标志位flagINT
4数值valueSTRING
5质量戳qualitySTRING
6时间戳timestampTIMESTAMP

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 参数调整:根据写入量调整 maxUncommittedRowso3MaxLag,避免 WAL 积压过多。

9. 总结

QuestDB 的 WAL 机制在磁盘满时容易进入不一致状态,导致写入失败。通过 RESUME WAL、跳过损坏事务、BYPASS WAL 配合重启等步骤,绝大多数挂起表都能恢复。对于极端情况下的表丢失,可依据应用代码重建表结构并恢复业务写入。整个修复过程需结合日志、系统表和分区状态进行精准诊断,切忌盲目操作。希望本文能为遇到类似问题的读者提供清晰的解决路径。