왜 문제가 되는가

애플리케이션 메모리는 정상인데 free가 보고하는 가용 메모리가 계속 줄고, 결국 OOM Killer가 엉뚱한 프로세스를 죽이는 상황이 있다. 이때 의심할 대상이 커널의 slab 할당자다. slab은 dentry, inode, 소켓 버퍼, 각종 커널 객체를 재사용 목적으로 캐싱하는데, 참조가 제대로 해제되지 않으면 캐시가 무한히 커진다. 사용자 공간 도구로는 원인이 보이지 않기 때문에 진단이 까다롭다.

전체 규모부터 확인

/proc/meminfo의 Slab, SReclaimable, SUnreclaim을 먼저 본다. SUnreclaim이 수 GB 단위로 꾸준히 증가한다면 회수 불가능한 커널 객체가 쌓이는 것이고, 이는 누수의 강한 신호다.

$ grep -E 'Slab|SReclaimable|SUnreclaim' /proc/meminfo
Slab:            8123456 kB
SReclaimable:     412300 kB
SUnreclaim:      7711156 kB   # 회수 불가 영역이 비정상적으로 큼

# 어떤 캐시가 큰지 크기순 정렬 (전역 뷰)
$ sudo slabtop -o -s c | head -15

어떤 캐시가 범인인지 좁히기

slabtop이나 /proc/slabinfo로 객체 수가 폭증하는 캐시를 특정한다. 캐시 이름이 원인 서브시스템을 알려준다.

캐시 이름대표 원인
dentry / inode_cache파일 대량 stat/open, 마운트 반복, negative dentry 폭증
kmalloc-* / kmalloc-cg-*드라이버·모듈의 명시적 free 누락
skbuff_head_cache네트워크 스택/소켓 버퍼 미해제
vm_area_structmmap/munmap 불균형
# objsize * num_objs 로 실제 점유 바이트 추정
$ awk 'NR>2 {printf "%-24s objs=%-9d bytes=%d\n", $1, $3, $3*$4}' \
    /proc/slabinfo | sort -t= -k3 -n -r | head

회수 가능 여부 판별

진짜 누수와 단순 캐시를 구분하려면 회수를 강제해 본다. drop_caches로 크게 줄면 정상 캐시이고, 거의 안 줄면 누수다.

# 운영 중 신중히. 순간적으로 I/O 지연이 생길 수 있음
$ sync; echo 2 | sudo tee /proc/sys/vm/drop_caches   # slab의 reclaimable만 회수
$ grep SUnreclaim /proc/meminfo   # 값이 그대로면 누수 의심

할당 경로 추적

회수 불가 kmalloc이 문제라면 커널을 slab_nomerge kmemleak=on으로 부팅하거나 실행 중 스택 추적 기능을 켠다. kmemleak은 해제되지 않은 할당의 호출 스택을 그대로 보여준다.

# kmemleak 스캔 트리거 후 리포트 확인
$ echo scan | sudo tee /sys/kernel/debug/kmemleak
$ sudo cat /sys/kernel/debug/kmemleak | head -30
# unreferenced object 0xffff... (size 512):
#   backtrace:
#     [<...>] my_driver_alloc+0x4a/0x90 [my_module]

# bpftrace로 특정 캐시 할당/해제 호출자 카운트
$ sudo bpftrace -e 'kprobe:kmem_cache_alloc { @[kstack] = count(); }'

임시 완화와 근본 해결

근본 원인은 코드 수정(누락된 kfree/put 추가, 커널·드라이버 버전 업그레이드)이다. 그 전까지의 완화책으로는 negative dentry 억제를 위한 vfs_cache_pressure 상향, 특정 워크로드 격리, cgroup v2의 memory.max로 폭주 범위 제한이 있다.

# dentry/inode 캐시를 더 적극적으로 회수하도록 압력 상향
$ sudo sysctl -w vm.vfs_cache_pressure=200

# cgroup v2로 특정 서비스의 커널 메모리 포함 상한 설정
$ echo 4G | sudo tee /sys/fs/cgroup/myservice.slice/memory.max

주의점

  • drop_caches는 진단용이다. 상시 cron 실행은 캐시 효율을 떨어뜨려 오히려 성능을 악화시킨다.
  • slab_nomerge는 유사 크기 캐시 병합을 막아 추적을 쉽게 하지만 메모리 오버헤드가 늘므로 진단 후 원복한다.
  • kmemleak은 자체 오버헤드가 크고 오탐(아직 참조 중인 객체)이 있으므로 스캔 직후 스냅샷만으로 단정하지 말고 재현·반복 확인한다.
  • SReclaimable이 큰 것은 대개 정상이다. 회수 불가 영역과 그 증가 추세를 기준으로 판단한다.