리눅스 메모리 분석 순서: RSS 증가부터 smaps·할당 경로까지

반응형

RSS 증가, cgroup OOM, latency spike가 함께 보이면 memory leak부터 의심하기 쉽다. 하지만 RSS 하나만으로 원인을 정할 수는 없다. 먼저 관측 범위와 memory pressure를 확인하고, mapping의 backing을 나눈 뒤, application allocation 경로로 내려가야 한다.

분석 순서는 다음 다섯 질문으로 정리할 수 있다.

증상이 실제인가
  → 어떤 memory 종류가 늘었나
  → 어느 mapping·cgroup에서 늘었나
  → 어떤 code 경로가 만들고 유지하나
  → 수정 후 같은 지표가 좋아졌나

0단계: host·container·process 범위를 고정한다

같은 ‘memory 사용량’이라도 host MemAvailable, cgroup memory.current, process RSS는 서로 다른 범위를 측정한다. container service라면 pod·container와 host PID의 대응, cgroup version, memory limit부터 기록한다.

증상 시간대도 고정한다. deploy, traffic, batch, cache warm-up, OOM event가 언제 발생했는지 하나의 timeline에 놓아야 서로 다른 snapshot을 잘못 비교하지 않는다.

1단계: 사용량과 pressure를 분리한다

RSS가 높아도 workload가 안정적이고 reclaim pressure가 낮을 수 있다. 반대로 limit에 가까운 cgroup에서는 작은 증가도 OOM으로 이어질 수 있다.

read-only로 시작할 수 있는 최소 확인은 다음과 같다.

pid=1234

grep -E '^(VmRSS|RssAnon|RssFile|RssShmem|VmSwap):' \
  "/proc/$pid/status"

cat /proc/pressure/memory

cgroup v2 경로를 알고 있다면 해당 group의 memory.current, memory.stat, memory.events도 같은 시점에 읽는다. cgroup path는 runtime과 배포 방식에 따라 다르므로 추측한 경로에 command를 적용하지 않는다.

PSI는 task가 memory resource를 기다리며 stall한 시간을 보여 준다. 사용 byte와 pressure가 같은 지표는 아니다. OOM이 있었다면 memory.eventsoom, oom_kill과 kernel log 접근 정책을 확인한다.

2단계: smaps로 증가 영역을 나눈다

/proc/<pid>/smaps_rollup은 process 전체의 PSS, anonymous, shared·private clean/dirty 같은 합계를 제공한다. 어떤 mapping이 늘었는지는 개별 smaps를 시간 간격을 두고 비교해야 한다.

cat "/proc/$pid/smaps_rollup"

여기서도 한 항목을 원인과 바로 등치하지 않는다.

  • anonymous 증가: heap뿐 아니라 stack, anonymous mmap, runtime·JIT 영역일 수 있다.
  • file-backed 증가: executable·shared library·mapped file이 될 수 있으며 항상 무해하지 않다.
  • mmap 증가: mapping 생성 방식이지 anonymous·file backing 자체를 뜻하지 않는다.
  • shared memory 증가: tmpfs, IPC, shared mapping의 owner와 accounting을 따로 확인해야 한다.

mapping별 path, permission, Anonymous, Private_Dirty, Pss의 변화로 후보를 좁힌다. smaps 읽기는 process map이 큰 환경에서 비용이 있으므로 짧은 주기로 계속 polling하지 않는다. 세부 필드 해석은 /proc/smaps로 메모리를 분석하는 방법에 정리되어 있다.

3단계: allocation과 lifetime을 찾는다

anonymous resident memory가 실제로 application allocation과 함께 증가한다면 heap profiler, runtime profiler, allocator statistic으로 live allocation과 retained object를 찾는다. 중요한 것은 allocation 횟수만이 아니라 얼마나 오래 살아 있고 누가 참조하는가다.

native process에서 eBPF uprobe로 malloc·free symbol을 관측할 수도 있다. 그러나 다음 한계가 있다.

  • custom allocator나 static linking에서는 target symbol이 다를 수 있다.
  • compiler inlining·wrapper·symbol versioning 때문에 기대한 호출을 놓칠 수 있다.
  • 호출량이 많으면 probe와 stack collection overhead가 커질 수 있다.
  • allocation과 free를 정확히 짝지으려면 pointer, PID/TID, 실패 반환을 신중히 다뤄야 한다.
  • stack과 symbol에는 민감한 code·request 정보가 포함될 수 있다.

production에서는 먼저 같은 binary의 test 환경에서 overhead와 누락률을 확인하고, PID·시간·size threshold·sampling을 제한한다. eBPF로 메모리를 추적하는 방법은 이 단계의 도구이지 분석의 출발점은 아니다.

4단계: 패턴을 원인 후보로 바꾼다

관측 패턴과 가능한 설명을 분리해 기록한다.

관측 원인 후보 추가 확인
live allocation이 계속 증가 leak, unbounded cache, queue backlog owner·reference·eviction policy
live bytes는 감소하지만 RSS 유지 allocator retention, fragmentation allocator stats·재할당 패턴
file PSS가 증가 mapped file working set mapping path·access pattern
latency와 PSI가 함께 증가 reclaim·cgroup pressure PSI, fault, cgroup event timeline
allocation 호출 tail만 증가 lock contention·slow path allocator latency·thread·size 분포

“free가 안 보인다”만으로 leak을 확정하지 않는다. sampling 누락, process 종료, 다른 allocator API, ownership transfer도 설명 후보다. 반대로 cache라는 이름이 붙었다고 무제한 증가가 정상인 것도 아니다. 용량 상한과 eviction 조건을 확인한다.

5단계: 수정 후 같은 조건으로 검증한다

수정 전 baseline과 같은 workload·시간 구간에서 다시 측정한다.

  1. process RSS·RssAnon과 cgroup memory.current
  2. application live bytes 또는 object 수
  3. PSI·page fault·OOM event
  4. CPU와 request p50·p95·p99 latency
  5. 기능 정확성과 cache hit rate 같은 guardrail

RSS만 낮추려고 cache를 비우거나 trim을 반복하면 latency가 나빠질 수 있다. memory limit을 올리면 OOM 시점만 늦출 수도 있다. 변경은 hypothesis 하나씩 적용하고, 되돌릴 기준을 미리 둔다.

free() 이후 allocator retention을 판단하는 법은 free와 RSS의 관계, allocator 호출 자체의 지연은 malloc 빠른 경로와 느린 경로로 이어진다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글