用 meta 校验脚本跑全量,我在 3.2 万条上撞了内存墙

环境是 Debian 12,Python 3.11,meta 校验脚本平时只跑增量的几百条,一直没出过事。上周做一次全量重跑,3.2 万条记录、单条平均 60KB,跑到第 1.8 万条左右进程被 OOM killer 干掉,dmesg 里能看到 oom-kill 记录,RSS 峰值到了 2.6GB,机器只有 4GB。现象很怪:单条解析很快,越往后越慢,最后直接没了。

我的做法是先确认是不是泄漏。用 tracemalloc 在脚本里插了一段,每 2000 条打一次 top 10:

import tracemalloc
tracemalloc.start(10)
for i, rec in enumerate(records):
    handle(rec)
    if i % 2000 == 0:
        snap = tracemalloc.take_snapshot()
        for stat in snap.statistics("lineno")[:5]:
            print(i, stat)

结果指到缓存字典上:为了查重,我把每条记录的 id 和整条原文都塞进了 dict,原文一直没释放。改成只存 id 的哈希,原文处理完立刻丢引用,峰值降到 380MB。同时把遍历改成按批读:

BATCH = 500
for start in range(0, len(paths), BATCH):
    for p in paths[start:start + BATCH]:
        with open(p, "rb") as f:
            handle(json.load(f))

另外在 systemd unit 里加了 MemoryMax=1G,超了让服务自己重启,而不是拖垮整台机器。

为什么这样改:OOM 不是脚本慢,是引用没断。Python 的引用计数对循环引用和容器持有很迟钝,dict 里存原文等于给每条记录续命到进程结束。分批读的代价是多了点 IO 次数,但换来内存上限可预测;MemoryMax 的代价是被杀时任务中断,需要外部重试兜底,我配了 Restart=on-failure 和幂等的写回,重跑不会重复写。

哪里会失效:哈希只存 id 时,极小概率的碰撞会让两条不同记录被当成重复,我加了二次比对,代价是偶尔多读一次原文。分批大小也不是越大越好,500 在我这里合适,换到机械盘或小内存机器上要下调。还有一个坑:MemoryMax 设太紧会把正常峰值也杀掉,我是先跑一遍看峰值再定的 1G,不是拍脑袋。

你们跑全量校验时,是限制单进程内存,还是拆成多进程分片?分片之后重复检测那一步怎么做的?

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

0 条评论

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

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