Hotdry.
systems-engineering

SQLite 出箱模式 + WAL 持久化任务队列:Exactly-Once 事件消费与崩溃恢复

用 SQLite Outbox Pattern + WAL 构建耐崩溃任务队列,实现 exactly-once 事件流消费,集成 Debezium CDC 管道。给出 schema、PRAGMA 配置与监控参数。

在分布式系统中,实现 durable execution(持久化执行)常需复杂引擎如 Temporal 或 Cadence,但对于中小规模任务队列,SQLite 的 Outbox Pattern 结合 WAL(Write-Ahead Logging)模式提供廉价、高可靠方案。该模式将业务变更与事件写入同一事务的 outbox 表,利用 WAL 持久化确保崩溃恢复,并通过 Debezium CDC 无缝流式投递到 Kafka,实现 exactly-once 语义。

Outbox Pattern 在 SQLite 中的核心设计

Outbox Pattern 解决 “双写不一致” 问题:业务服务在本地事务中同时写入业务表和 outbox 表,后者存储待发布事件。SQLite 天然支持此模式,因其 ACID 事务保证原子性。

典型 outbox 表 schema:

CREATE TABLE IF NOT EXISTS outbox_events (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  aggregate_type TEXT NOT NULL,
  aggregate_id TEXT NOT NULL,
  event_type TEXT NOT NULL,
  payload JSON NOT NULL,  -- 事件负载,序列化为 JSON
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  processed_at TIMESTAMP NULL
);
CREATE INDEX idx_outbox_aggregate ON outbox_events(aggregate_type, aggregate_id);
CREATE INDEX idx_outbox_created ON outbox_events(created_at);

业务事务示例(伪代码):

BEGIN TRANSACTION;
-- 业务变更,如订单创建
INSERT INTO orders (id, status) VALUES ('order-123', 'created');
-- 写入 outbox
INSERT INTO outbox_events (aggregate_type, aggregate_id, event_type, payload)
VALUES ('Order', 'order-123', 'OrderCreated', '{"id": "order-123", "status": "created"}');
COMMIT;

崩溃时,事务回滚确保业务与事件一致;成功提交后,事件持久化在 outbox。

WAL 模式:持久化与并发保障

默认 rollback journal 下,SQLite 写阻塞读,不适高并发任务队列。启用 WAL 模式(PRAGMA journal_mode=WAL;)将变更追加到 -wal 文件,支持读写并发:读从主文件 + wal 合并,写仅追加 wal。

关键 PRAGMA 配置:

  • PRAGMA journal_mode = WAL; // 启用 WAL
  • PRAGMA synchronous = NORMAL; // 平衡耐久性与性能,崩溃可能丢最近事务
  • PRAGMA wal_autocheckpoint = 1000; //wal 文件达 1000 页(默认~1MB)自动 checkpoint
  • PRAGMA busy_timeout = 5000; // 锁冲突等待 5s
  • PRAGMA cache_size = 10000; // 增大缓存,提升吞吐

WAL 确保耐崩溃:wal 文件 fsync 后事务持久,重启自动 replay 未 checkpoint 变更。测试显示,WAL 下写吞吐提升 2-4 倍,读写并发无阻塞。

Checkpoint 机制:

  • 被动:wal 满时当前 COMMIT 线程执行。
  • 主动:闲时调用 PRAGMA wal_checkpoint(FULL);,回写 wal 到主文件,truncate wal。 监控 wal 文件大小(<100MB),若过大,手动 FULL checkpoint。

Debezium CDC:Exactly-Once 事件流消费

轮询 outbox 易积压、延迟高。集成 Debezium(支持 SQLite connector)捕获 outbox 变更,实时流式投递到 Kafka。

Debezium SQLite connector 配置(docker-compose 示例):

debezium:
  image: debezium/connect:2.5
  depends_on: [kafka, sqlite-db]
  environment:
    BOOTSTRAP_SERVERS: kafka:9092
    GROUP_ID: 1
    CONFIG_STORAGE_TOPIC: dbserver1-config
    OFFSET_STORAGE_TOPIC: dbserver1-offset
    STATUS_STORAGE_TOPIC: dbserver1-status
  command: ["connect-standalone", "/etc/kafka-connect-sqlite.properties"]

kafka-connect-sqlite.properties:

connector.class=io.debezium.connector.sqlite.SqliteConnector
database.hostname=sqlite-db
database.path=/var/lib/sqlite/tasks.db
database.history.kafka.bootstrap.servers=kafka:9092
database.history.kafka.topic=dbhistory.tasks
table.include.list=outbox_events
transforms=unwrap
transforms.unwrap.type=io.debezium.transforms.ExtractNewRecordState
transforms.unwrap.drop.tombstones=true

Debezium 订阅 binlog/wal,捕获 INSERT 到 outbox,转换为 Kafka 事件。消费端 Kafka Streams / 消费者处理后,更新 processed_at(幂等)。

Exactly-Once:Debezium 事务边界 + Kafka exactly-once + 消费幂等(unique aggregate_id)。

Crash 恢复与监控参数

崩溃恢复流程:

  1. SQLite 重启:自动从 wal replay 未 checkpoint 事务。
  2. Debezium 重连:从 offset 恢复,replay outbox 变更。
  3. 任务队列:未 processed 事件重发,确保不丢。

落地参数清单:

  • 吞吐阈值:SQLite WAL 适 <1k TPS,高吞吐迁 Postgres。
  • Checkpoint 频率:每 5min 或 wal>50MB 执行 FULL。
  • 监控指标
    指标 阈值 告警
    wal 文件大小 <100MB >200MB
    Checkpoint 滞后 <1h >2h
    Outbox 未处理数 <1k >10k
    Debezium 滞后 <10s >1min

回滚策略:若 wal 膨胀,PRAGMA journal_mode=DELETE; 切换回默认。

实战风险与优化

风险:SQLite 单写者锁,wal 共享内存限单机;Debezium SQLite connector 实验性,生产慎用(fallback 轮询 publisher)。

优化:分区 outbox(按 aggregate_type),结合 Watermill 等库简化 pub/sub。

此方案成本低、无运维,适 edge computing 或单机 durable queue。

资料来源

  • Gunnar Morling: Building a Durable Execution Engine With SQLite (morling.dev)
  • Debezium SQLite Connector Docs
  • SQLite WAL Mode (sqlite.org/wal.html)
查看归档