用 sqlite 存 meta 快照,我把 12 万条记录的回填压到 40 秒

环境是 Python 3.11 + Ubuntu 22.04,meta 服务每天产出一批 JSON 快照,单批大概 12 万条记录,字段 30 个上下,嵌套两层。之前回填校验是逐条读文件、拼 SQL 往线上库写,跑一次二十多分钟,中间断一次就得从头来。

我的做法是先落本地 sqlite,再做差异。建表时把 meta 的稳定字段拆成列,变动部分塞一个 JSON 列:

CREATE TABLE snap (
  id TEXT PRIMARY KEY,
  kind TEXT NOT NULL,
  updated_at TEXT,
  payload TEXT
);
CREATE INDEX idx_snap_kind ON snap(kind);

导入用 executemany,一批 5000 条,外面套事务:

conn.execute("PRAGMA journal_mode=WAL")
conn.executemany(
    "INSERT OR REPLACE INTO snap(id,kind,updated_at,payload) VALUES(?,?,?,?)",
    rows,
)
conn.commit()

回填前先跑一次对比,只把有差异的 id 捞出来:

q = """
SELECT s.id FROM snap s
JOIN remote r ON r.id = s.id
WHERE s.updated_at <> r.updated_at
   OR s.payload <> r.payload
"""

线上库那边我建了个临时表放当批远端数据,join 在本地 sqlite 里做不了跨库,所以远端只导出 id、updated_at 和 payload 的哈希三列,大概 3MB,导进来再比。这一步是整个流程里最省时间的地方:原来是把 12 万条全量写回去,现在是只写差异,通常不到 800 条。

为什么这么干:逐条写线上库的主要成本不是 SQL 本身,是每条一个 round trip 加一次事务提交。压成批量之后,40 秒里大概 30 秒花在导入 sqlite,真正回填只占几秒。代价是多了一份本地副本,磁盘占用这批大概 180MB,跑完得手动清。另外 WAL 模式在容器里要注意,挂载卷如果是网络盘,checkpoint 会明显变慢,我一开始放在 NFS 上,导入反而比原来还慢。

会失效的地方有几个。payload 用字符串直接比,字段顺序不同就会误判成差异,我是导入前先 json.dumps 加 sort_keys 统一过一遍,不然差异数会虚高到两三万。updated_at 有时是空字符串、有时是 null,直接 <> 比较会漏,得写成 COALESCE(s.updated_at,'') <> COALESCE(r.updated_at,'')。还有一个坑是 id 有大小写混用,PRIMARY KEY 在 sqlite 里是大小写敏感的,线上库的排序规则不一样,join 会对不上,我最后统一 lower 之后再入库。

如果这批数据里有软删除标记,只比 updated_at 和 payload 是不够的,删除的记录远端不导出,本地会一直留着,回填完得再跑一次 id 差集。

你们做这种批量回填时,差异判定是放在数据库里 join,还是拉到进程里用集合比?我这边数据量再大一点,sqlite 的单文件锁可能就顶不住了。

0
赞同度 100% · 0 条评论 · 5 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。