디버깅 실전 · 자원이 바닥날 때의 증상 · 실습
오후만 되면 죽는다 — 바닥나는 자원을 자료로 잡는다
목표
파일 기술자 누수를 기울기로 증명하고, 한도를 낮춰 바닥을 앞당겨 재현하고, 바닥난 뒤 엉뚱한 곳에서 나는 오류들을 기록한다. 메모리도 주소 공간 한도로 안전하게 재현해 예외로 받는 것과 죽임당하는 것의 차이를 적고, 고친 판의 기울기가 0 임을 같은 도구로 증명한다.
왜 중요한가
자원 고갈은 증상이 원인을 가리키지 않는 대표적인 사건이다. 기술자를 흘린 것은 세션 처리기인데 오류는 데이터베이스 연결에서 난다. 새게 만든 코드는 이미 자기 몫을 가져갔고, 바닥이 드러난 순간 그다음에 자원을 요청한 코드가 실패하기 때문이다.
그래서 이 조사는 메시지를 읽는 일이 아니라 재는 일이다. 요청 수를 바꿔 가며 열린 기술자 수를 재면 점이 몇 개 나오고, 그 기울기가 요청 하나당 새는 개수다. 기울기가 0 이면 새지 않는다. 이것이 "느낌" 을 자료로 바꾸는 방법이다.
한도는 조사 도구이기도 하다. 소프트 상한은 프로세스가 스스로 낮출 수 있으므로, 열 시간 뒤에 만날 바닥을 지금 몇 초 만에 만들 수 있다. 반대로 한도를 올려 덮으면 죽는 시각만 뒤로 밀린다.
채점기는 여러분의 결론을 믿지 않는다. 채점기가 요청 하나당 정확히 몇 개를 흘리는지 아는 처리기를 따로 만들어 여러분의 계측 도구를 실제로 물리고, 기울기와 한도와 오류 이름을 직접 대조한다. 그 개수는 실행마다 바뀝니다.
단계
1. /root/exhaust/gen_exhaust.py 를 만들어 실행해 /root/exhaust/leaky.py 를 만드세요.
2. /root/exhaust/fdcount.py 로 살아 있는 프로세스의 열린 기술자 수를 세세요.
3. /root/exhaust/measure_leak.py 로 요청 수를 바꿔 가며 재 /root/exhaust/trend.json 에 기울기를 남기세요.
4. /root/exhaust/run_under_limit.py 로 한도를 낮춰 바닥을 앞당기고 /root/exhaust/nofile.json 에 적으세요.
5. /root/exhaust/symptoms.py 로 바닥난 뒤의 실패들을 모아 /root/exhaust/symptoms.json 에 적으세요.
6. /root/exhaust/mem.py 로 주소 공간 한도를 재현해 /root/exhaust/mem.json 에 적으세요.
7. 고친 판을 같은 도구로 재 /root/exhaust/fixed.json 에 기울기 0 과 통과를 남기세요.
8. /root/exhaust/summary.json 과 /root/exhaust/exhaust_report.md 에 네 절로 보고하세요.
참고
- 처리기 계약:
python3 /root/exhaust/leaky.py --requests N [--fixed] [--dir D] [--ready-file F] [--pause-file P]는 요청을 처리한 뒤 ready 파일을 만들고, pause 파일이 생길 때까지 살아 있다가 JSON 한 줄을 냅니다. 살아 있는 동안 기술자 수를 잴 수 있습니다. - 세는 도구:
python3 /root/exhaust/fdcount.py --pid <번호>는 pid·open_fds 를 담은 JSON 한 덩어리를 냅니다. 리눅스는/proc/<pid>/fd에 열린 기술자마다 항목 하나를 둡니다. - 계측 도구:
python3 /root/exhaust/measure_leak.py --target <처리기> --points 10,40,80 [--target-arg=--fixed] --out <json>은 target·target_args·points(요청 수와 기술자 수의 쌍)·per_request(기울기)·baseline 을 냅니다.--target-arg로 준 값은 처리기에 그대로 넘어갑니다(등호로 붙여 씁니다). 기울기는 (마지막 기술자 수 - 첫 기술자 수) / (마지막 요청 수 - 첫 요청 수) 입니다. - 한도 도구:
python3 /root/exhaust/run_under_limit.py --nofile <상한> --cmd "<명령>" --out <json>은 그 명령에만 낮은 상한을 씌워 돌리고 nofile·cmd·exit_code·errno_name·stderr_tail·stdout_tail 을 냅니다. errno_name 은 기술자가 바닥났으면EMFILE, 아니면 null 입니다. - 증상 도구:
python3 /root/exhaust/symptoms.py --nofile <상한> --out <json>은 기술자를 바닥낸 뒤 open·socket·subprocess·sqlite3 네 가지를 시도해 nofile·held·observations(op·error·errno)를 냅니다. - 메모리 도구:
python3 /root/exhaust/mem.py --as-mb <상한> --alloc-mb <할당> --out <json>은 as_mb·alloc_mb·outcome(MemoryError 또는 ok)·exit_code 를 냅니다. resource.setrlimit은 자기 프로세스와 그 뒤에 생기는 자식에게 적용됩니다. 자식을 띄우기 직전(preexec_fn)에 낮추면 부모는 그대로 두고 그 명령만 좁은 방에 넣을 수 있습니다.- 흔한 실수: 한 번만 재고 누수라고 말하기, 한도를 올려 덮기, 기술자를 파일만으로 세기, 이미 끝난 프로세스의 /proc 을 읽으려 하기.
- 이 실습의 가정: 기울기를 직선으로 보는 것은 이 처리기가 요청마다 같은 수를 여는 단순한 구조이기 때문입니다. 실제 서비스에서는 점을 더 찍어 직선인지부터 확인해야 합니다.
- 부하 시험을 만들지 마세요. 파드는 2코어·메모리 2Gi 이고 채점 하나의 예산은 60초입니다.
단계 8개
- 세션 처리기 손에 쥐기
- 지금 몇 개를 들고 있는지 세기
- 요청 하나당 몇 개가 새는지 기울기로 구하기
- 한도를 낮춰 바닥을 앞당기기
- 증상이 원인을 가리키지 않는다
- 메모리는 어떻게 바닥나는가
- 고친 판의 기울기가 0 임을 증명하기
- 증상과 원인을 따로 적어 보고하기