banner
约 600 字
2 分钟

一次 OOM 排查记录

-
-
无标签

摘要

半夜告警一个 Python 进程把 8GB 内存吃光了。用 tracemalloc 定位到 pd.concat 在 8 亿行数据上的内存翻倍问题。

上周三凌晨 3 点,Prometheus 告警炸了:一个 Python worker 进程占了 8GB 内存,整个服务被 OOM Killer 干掉,重启后 4 小时又挂了。

看数据

打开 dmesg | grep -i oom

纯文本
[Wed Jun 18 03:14:22 2025] python3 invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0
[Wed Jun 18 03:14:22 2025] Memory cgroup out of memory: Killed process 18472 (python3) total-vm:8354320kB

挂 tracemalloc

给进程加个 tracemalloc,重启,等 4 小时再炸

看 top 20 分配点:

纯文本
5.2 GB    /usr/lib/python3/dist-packages/pandas/core/frame.py:5341
2.1 GB    /usr/local/lib/python3.11/dist-packages/our_app/etl.py:87
1.0 GB    /usr/local/lib/python3.11/dist-packages/our_app/cache.py:23

问题在 etl.py:87我自己的代码

代码是这样的

Python
def merge_old_new(old_df, new_df):
    # 我以为是流式合并
    result = pd.concat([old_df, new_df], ignore_index=True)
    result = result.drop_duplicates(subset=['id'])
    return result

old_df 是从数据库全量加载的,8 亿行。每次调用,pd.concat 都会复制一份,然后 drop_duplicates 又复制一份

怎么修

Python
def merge_old_new(old_ids: set, new_df):
    # old_ids 是 id 集合,不是全量 DataFrame
    mask = ~new_df['id'].isin(old_ids)
    return new_df[mask]

复盘

  1. 没有 "内存使用趋势" 看板,没看到缓慢上涨

  2. 没给 DataFrame 内存使用加 metric

  3. 写代码时没想数据增长这是个常见错误

留个备忘

Python 处理数据,三件套要警觉

  • pd.concat:永远 O(n+m),大表不要 concat

  • df.drop_duplicates():会复制内存,能 SQL 做就 SQL 做

  • 列表推导 + 字符串拼接:str.join不要用 +

END

相关文章

暂无相关文章