拉斯维加斯的故事
44.25M · 2026-03-17
某业务系统使用 QuestDB 作为时序数据库,因磁盘空间写满,导致多张表无法正常写入新数据。清理磁盘空间后,发现部分表(如 collection_map_point_data)能够执行 INSERT 但查询不到新数据,且今天的分区未能自动创建;另一些表(如 eam_data、topic_data_3)在尝试修复过程中甚至从 SHOW TABLES 中消失。本文详细记录了从问题发现、排查到最终恢复的完整过程,并提供了一套可复现的修复方案。
SELECT 查不到新数据。table_partitions 中不存在今天的分区。wal_tables 中 suspended = true,甚至执行 ALTER 后表“消失”。could not create partition directory、disk full、WAL 文件丢失等。SELECT * FROM tables() WHERE name = 'your_table';
重点关注 walEnabled、partitionBy 和 timestamp 列。
SELECT * FROM table_partitions('your_table') ORDER BY name DESC;
查看今天的分区是否存在,以及 active、attached 状态。
SELECT * FROM wal_tables() WHERE name = 'your_table';
suspended = true 表示表被挂起,需要恢复。
在 Kubernetes 环境中:
kubectl logs <pod-name> --tail=500 | grep -E "ERROR|your_table"
物理部署则查看 ~/questdb/log/questdb.log。日志是定位根本原因的最重要依据。
如果 suspended = true,首先尝试恢复:
ALTER TABLE 'your_table' RESUME 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>;
当上述方法无效时,采用“绕过 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 数据(即之前插入但查不到的)可能会被丢弃。
如果有多个表同时挂起,可以一次性处理,减少重启次数:
-- 批量切换 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;
...
-- 再重启一次
无论多少张表,只需两次重启。
如果在修复过程中表从 SHOW TABLES 中消失(如 eam_data、topic_data_3),请先确认物理目录是否还在:
kubectl exec -it <pod> -- ls -la /root/.questdb/db/ | grep 表名
如果目录存在(例如 eam_data~45),数据文件大概率还在,可以尝试重建表结构并导入数据。若目录已丢失,只能重建空表让业务重新写入。
以 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 |
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 类型以优化存储和查询性能。
若旧表数据目录存在,可尝试按分区导入:
-- 创建新表后,从旧表复制可读分区(假设旧表在系统表中仍可见)
INSERT INTO 'eam_data' SELECT * FROM 'eam_data_old' WHERE timestamp < '2024-03-14';
若旧表完全不可见,可能需要更底层的文件恢复操作(本文不展开),建议优先从业务源头重新生成数据。
systemctl restart,通过 kubectl delete pod 让控制器重建 Pod 即可实现重启。kubectl logs 获取实时日志,方便排查。ALTER TABLE DROP PARTITION)释放空间。COPY 导出为 Parquet 或直接备份 PVC。maxUncommittedRows 和 o3MaxLag,避免 WAL 积压过多。QuestDB 的 WAL 机制在磁盘满时容易进入不一致状态,导致写入失败。通过 RESUME WAL、跳过损坏事务、BYPASS WAL 配合重启等步骤,绝大多数挂起表都能恢复。对于极端情况下的表丢失,可依据应用代码重建表结构并恢复业务写入。整个修复过程需结合日志、系统表和分区状态进行精准诊断,切忌盲目操作。希望本文能为遇到类似问题的读者提供清晰的解决路径。