用 jq 校验 meta 字段时,我把 400MB 日志读进了内存
上周在 meta 里翻一个老项目,顺手把它的 meta 校验脚本重写了一遍。环境是 Ubuntu 22.04、GNU bash 5.1、jq 1.6,数据是 12 万条记录、合计约 420MB 的 JSONL 日志,每条记录里带一个 meta 对象,需要确认 meta.source、meta.ts 两个字段齐全且 ts 是秒级时间戳。原来的脚本写得挺随意,跑一次要三分多钟,内存冲到 2G 多,机器直接开始换页。
先说我改完之后的跑法。核心是把逐行处理和整文件处理分开,别让 jq 一次吃掉整个文件:
# 逐行校验,流式读,不驻留内存
jq -c 'select((.meta.source? // "") == "" or (.meta.ts? // 0) < 1000000000)' \
app.jsonl > bad-lines.jsonl
# 统计坏行数,别再拿一个变量累加
wc -l < bad-lines.jsonl
字段存在性检查我用 ? 加默认值,而不是 has("meta") and .meta.has("source")。后者在字段缺失时会直接报错中断整条流,前者只是把这条记录筛出来。要区分「字段缺失」和「字段为空字符串」,就把两个条件拆成两条 jq 命令分别跑,输出到不同文件,别在一个表达式里硬凑。
为什么这样能省内存:jq 默认对输入做逐条解析,-c 只影响输出格式,真正决定内存的是你有没有把整个输入 slurp 进来。原脚本里有一句 jq -s 'map(select(...))',-s 会把所有输入读成一个数组,420MB 的文本在内存里膨胀成好几倍,这才是慢的根源。代价是流式处理拿不到全局信息,比如「同一个 source 在多少条记录里出现」,这种统计得单独过一遍,或者用 sort | uniq -c 走磁盘。
会失效的地方我踩过两个。一是文件里混进了非 JSON 行(有过一次是日志切分时留了半行),jq 遇到会报 parse error 并退出,后面的行全不处理;稳妥点先 jq -c . < app.jsonl > /dev/null 探一遍,或者加 --seq 但那样语义会变。二是 ts 字段有时候是毫秒,我最初只判断了小于 10 亿,结果毫秒值全被放过,后来改成同时看位数,或者直接和上一次记录做差值判断。另外 wc -l 在最后一行没有换行时会少数一条,我一般用 grep -c '' 兜底。
现在整套跑完大概 40 秒,内存稳定在几十兆。你们在 meta 校验里是倾向用 jq 流式过一遍,还是干脆拿 Python 读行做类型判断?我这边两条路都留着,想听听别人怎么选。
0 条评论
还没有评论,写下第一条。