LabHub
배우기 러닝패스 코스

デバッグ実戦

午後になると落ちる — 尽きる資源をデータで捕まえる

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

파일 기술자 누수를 기울기로 증명하고, 한도를 낮춰 바닥을 앞당겨 재현하고, 바닥난 뒤 엉뚱한 곳에서 나는 오류들을 기록한다. 메모리도 주소 공간 한도로 안전하게 재현해 예외로 받는 것과 죽임당하는 것의 차이를 적고, 고친 판의 기울기가 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 에 네 절로 보고하세요.

참고

세션 처리기 손에 쥐기

/root/exhaust/gen_exhaust.py 를 만들어 실행해 /root/exhaust/leaky.py 를 만드세요. --requests 로 요청 수를 주고 --fixed 로 고친 판을 돌릴 수 있습니다.

이 스크립트를 그대로 저장해 실행하면 됩니다. leaky.py 를 --requests 100 으로 한 번 돌려 보세요. 아무 일도 없어 보입니다 — 기술자는 프로세스 안에서만 쌓이고, 프로세스가 끝나면 커널이 전부 회수하기 때문입니다.

지금 몇 개를 들고 있는지 세기

/root/exhaust/fdcount.py 를 만들어 --pid <번호> 로 그 프로세스의 열린 기술자 수를 세고 pid·open_fds 를 담은 JSON 을 내게 하세요.

리눅스는 proc(5)에 프로세스마다 /proc//fd 디렉터리를 두고, 열린 기술자마다 항목 하나를 놓습니다. 세는 일은 그 디렉터리의 항목 수를 세는 것입니다. 없는 프로세스를 물었을 때 어떻게 답할지도 정해 두세요.

요청 하나당 몇 개가 새는지 기울기로 구하기

/root/exhaust/measure_leak.py 로 요청 수 10·40·80 에서 각각 기술자 수를 재고 /root/exhaust/trend.json 에 target·points·per_request·baseline 을 남기세요. per_request 는 1 이상이어야 합니다.

처리기는 ready 파일을 만든 뒤 pause 파일이 생길 때까지 살아 있습니다. 그 사이에 /proc 으로 세면 됩니다. 점 두 개면 기울기가 나오지만 셋을 찍으면 직선인지도 볼 수 있습니다. 재고 나서 pause 파일을 만들어 처리기를 보내 주는 것을 잊지 마세요.

한도를 낮춰 바닥을 앞당기기

/root/exhaust/run_under_limit.py 로 leaky.py 를 낮은 --nofile 아래에서 돌려 /root/exhaust/nofile.json 에 nofile·cmd·exit_code·errno_name·stderr_tail·stdout_tail 을 남기세요. errno_name 은 EMFILE 이어야 합니다.

resource.setrlimit 은 자기 프로세스와 그 뒤에 생기는 자식에게 적용됩니다. subprocess 의 preexec_fn 에서 낮추면 그 명령만 좁은 방에 넣을 수 있습니다. 요청 수는 한도보다 넉넉히 크게 주세요 — 한도에 닿아야 바닥을 봅니다.

증상이 원인을 가리키지 않는다

/root/exhaust/symptoms.py 로 기술자를 바닥낸 뒤 open·socket·subprocess·sqlite3 네 가지를 시도해 /root/exhaust/symptoms.json 에 nofile·held·observations 와 misleading_ops 를 남기세요. misleading_ops 는 오류 메시지에 기술자 이야기가 없는 항목입니다.

네 가지 모두 기술자를 필요로 하지만 메시지의 얼굴은 다릅니다. 특히 sqlite3 는 '데이터베이스 파일을 열 수 없다' 고만 말합니다. 그 메시지를 보고 디스크와 권한을 뒤지면 몇 시간이 사라집니다. 어느 항목이 원인을 숨기는지 목록으로 남기세요.

메모리는 어떻게 바닥나는가

/root/exhaust/mem.py 로 주소 공간 한도를 넘는 할당과 넘지 않는 할당을 각각 재현해 /root/exhaust/mem.json 에 over·under 두 결과와 note 를 남기세요. over 의 outcome 은 MemoryError, under 는 ok 여야 합니다.

RLIMIT_AS 는 주소 공간의 상한이라 한도를 넘는 할당이 MemoryError 로 올라옵니다. 트레이스백이 남는다는 점이 중요합니다 — 커널의 OOM 킬러는 SIGKILL 이라 아무 자국도 남기지 않습니다. note 에 이 차이와 주소 공간이 실제 사용량과 다르다는 점을 적으세요.

고친 판의 기울기가 0 임을 증명하기

고친 판(--fixed)을 같은 도구로 재 /root/exhaust/fixed.json 에 per_request_before·per_request_after·nofile·requests·exit_code_after 를 남기세요. per_request_after 는 0 이고 exit_code_after 는 0 이어야 합니다.

고쳤다는 주장도 같은 계측으로 해야 합니다. 기울기가 0 이면 요청이 늘어도 기술자가 늘지 않는다는 뜻이고, 낮은 한도에서 많은 요청을 처리하고도 통과한다면 바닥에 닿지 않는다는 뜻입니다. 두 증거를 함께 남기세요.

증상과 원인을 따로 적어 보고하기

/root/exhaust/summary.json 에 per_request_before·per_request_after·nofile·errno_name·misleading_ops·mem_outcome 을 적고, /root/exhaust/exhaust_report.md## 무엇이 바닥났나 ## 증상은 어디서 났나 ## 어떻게 증명했나 ## 남은 위험 네 절로 보고하세요.

보고서의 값어치는 '기술자가 샜습니다' 가 아니라 '요청 하나당 한 개씩 샜고, 고친 뒤에는 0 입니다' 에 있습니다. 증상 목록과 원인을 따로 적어, 다음 사람이 sqlite 메시지를 보고 디스크를 뒤지지 않게 하세요.