TT Lab
시작하기
배우기 러닝패스 코스

디버깅 실전

바닥난 것은 여기인데 오류는 저기서 난다

TT Lab 에서 이어서 보기

한 줄 요약

자원이 바닥나면 새게 만든 코드가 아니라 그 뒤에 자원을 필요로 한 코드가 터진다. 그래서 트레이스백은 무고한 모듈을 가리키고, 조사는 계측으로만 끝난다.

왜 이게 필요했나

"오후 세 시쯤이면 죽습니다" 는 자원 고갈 신고의 전형적인 문장이다. 아침에는 멀쩡하고, 부하와도 정확히 비례하지 않고, 재시작하면 한동안 괜찮다. 마지막 조건이 결정적인 단서다 — 재시작이 고친다면 쌓이는 무언가가 있다.

그런데 로그를 열면 범인이 아니라 피해자가 보인다. 파일 기술자를 흘린 것은 세션 처리기인데, 오류는 데이터베이스 연결에서 나거나, 하위 프로세스 실행에서 나거나, 로그 파일을 여는 자리에서 난다. 기술자를 새게 만든 코드는 이미 자기 몫을 다 가져갔고, 바닥이 드러난 순간 그다음에 자원을 요청한 코드가 실패하기 때문이다.

이 어긋남 때문에 조사가 엉뚱한 곳으로 간다. "sqlite 가 데이터베이스 파일을 열지 못한다" 는 메시지를 보고 디스크와 권한과 경로를 몇 시간 뒤진다. 파일은 멀쩡하고 권한도 맞다. 그 프로세스가 더는 어떤 파일도 열 수 없을 뿐이다.

새게 만든 코드는 멀쩡하고 그다음에 요청한 코드가 터진다

본문은 자원 고갈 신고에서 트레이스백이 범인이 아니라 피해자를 가리킨다고 말한다. 기술자를 흘리는 코드에서 시작해 오류가 엉뚱한 모듈에서 나기까지의 순서를 옮겼다.

  1. 새는 코드가 자기 몫을 가져간다세션 처리기가 요청마다 기술자를 닫지 않는다고 하자. 이 코드는 자기 몫을 이미 다 가져갔으므로 이 시점에는 아무 오류도 내지 않는다.
  2. 한도에 닿아 바닥이 드러난다열린 기술자 수가 RLIMIT_NOFILE 에 닿으면 더 열 수 없다. 기술자는 파일만이 아니라 소켓과 파이프도 같은 한도를 나눠 쓰므로, 바닥난 프로세스는 파일만이 아니라 아무것도 못 한다.
  3. 그다음에 자원을 요청한 코드가 실패한다데이터베이스 연결, 하위 프로세스 실행, 로그 파일 열기처럼 새는 일과 무관한 모듈에서 먼저 오류가 난다. 트레이스백은 그 무고한 모듈을 가리킨다.

여기서 구분할 것 오류 메시지의 낱말을 검색하면 권한과 경로 이야기가 쏟아지지만 이 사건과는 상관이 없다. 자원 고갈을 의심하는 신호는 메시지가 아니라 시간이 갈수록 나빠지고 재시작이 고치는 패턴이다. 오후 세 시에 죽는다는 사례와 이 서술은 레슨 본문의 것이다.

잠깐, 예측해 보세요 어제는 로그 파일을 여는 자리에서, 오늘은 하위 프로세스를 띄우는 자리에서 같은 프로세스가 먼저 실패했다. 트레이스백이 가리키는 모듈이 매번 달라지는 것은 서로 다른 버그라는 뜻일까?

설명 확인 · 채점 없는 자가 점검

그렇게 단정할 수 없다. 바닥이 드러난 뒤에는 그 순간 자원을 요청한 코드가 실패하므로, 어느 모듈이 먼저 걸리는지는 타이밍에 달려 있다. 본문은 서로 다른 모듈이 번갈아 실패하는 것 자체를 고갈의 패턴으로 꼽는다. 확인은 메시지를 읽는 대신 열린 기술자 수를 재는 것으로 한다.

근거 문서

어떻게 동작하나

자원 고갈 조사의 뼈대는 세 가지다.

1. 한도를 안다. 리눅스는 프로세스마다 자원 상한을 걸고, 파이썬에서는 resource 모듈로 읽고 쓴다. RLIMIT_NOFILE 은 동시에 열 수 있는 기술자 수, RLIMIT_AS 는 주소 공간의 크기다. getrlimit(2)가 정하듯 상한은 소프트와 하드 두 값이고, 소프트는 하드 이하에서 프로세스가 스스로 낮출 수 있다. 낮출 수 있다는 점이 조사에 쓰인다 — 열 시간 뒤에 만날 바닥을 지금 만들 수 있다.

2. 쌓이는 것을 센다. 리눅스는 proc(5)에 프로세스마다 /proc/<pid>/fd 디렉터리를 두고 열린 기술자마다 항목 하나를 놓는다. 그 항목 수를 세면 지금 몇 개를 들고 있는지 알 수 있다. 요청 수를 바꿔 가며 이 값을 재면 점 몇 개가 나오고, 그 점들이 직선 위에 있으면 기울기가 곧 요청 하나당 새는 개수다. 기울기가 0 이면 새지 않는다. 이것이 누수를 "느낌" 이 아니라 자료로 만드는 방법이다.

3. 증상과 원인을 갈라 적는다. 바닥난 뒤의 오류 메시지는 자원의 종류를 말해 줄 뿐 누가 썼는지는 말하지 않는다. 그래서 보고서에는 둘을 따로 적는다 — 관측된 증상 목록과, 계측으로 밝힌 원인.

한도                무엇을 막나                 바닥났을 때의 얼굴
RLIMIT_NOFILE      열린 파일·소켓 수           OSError errno 24 (EMFILE),
                                               그리고 자원을 쓰는 남의 코드의 오류
RLIMIT_AS          주소 공간 크기               MemoryError (트레이스백이 남는다)
커널 OOM 킬러      머신 전체의 메모리           SIGKILL (트레이스백이 남지 않는다)

메모리 쪽에서 특히 중요한 구분이 이 표의 마지막 두 줄이다. 한도에 걸린 할당은 예외로 올라와 트레이스백을 남기지만, 커널이 죽이는 경우는 프로세스가 SIGKILL 로 사라져 마지막 로그조차 남지 않는다. "로그가 중간에 끊겼다" 는 신고는 그래서 그 자체로 단서다. 다만 RLIMIT_AS 는 주소 공간의 상한이라 실제 사용량과 같지 않다는 점을 함께 적어야 한다 — 매핑만 하고 건드리지 않은 영역도 주소 공간은 차지한다.

현장에서 만나는 모습

첫째, 오류 메시지의 낱말을 검색한다. "unable to open database file" 로 검색하면 권한과 경로 이야기가 잔뜩 나온다. 전부 맞는 이야기지만 이 사건과는 상관이 없다. 자원 고갈을 의심하는 신호는 메시지가 아니라 패턴이다 — 시간이 지날수록 나빠지고, 재시작이 고치고, 서로 다른 모듈이 번갈아 실패한다.

둘째, 한 번의 관측으로 결론을 낸다. "지금 기술자가 900개입니다" 는 그 자체로 아무 말도 아니다. 원래 몇 개였는지, 요청이 늘 때 어떻게 변하는지를 함께 재야 한다. 두 점만 있어도 기울기가 나온다.

셋째, 한도를 올려 덮는다. 한도를 올리면 죽는 시각이 오후 세 시에서 밤 열 시로 옮겨 갈 뿐이다. 새는 쪽을 고치지 않으면 언젠가 반드시 다시 만난다. 다만 한도를 낮추는 것은 조사 도구로 아주 쓸모 있다 — 열 시간 걸릴 재현을 몇 초로 줄인다.

넷째, 파일만 센다. 기술자는 파일만이 아니다. 소켓, 파이프, 이벤트 알림, 그리고 하위 프로세스를 띄울 때 잠깐 쓰는 것까지 같은 한도를 나눠 쓴다. 그래서 바닥난 프로세스는 파일을 못 여는 것이 아니라 아무것도 못 한다.

실무에서 진짜 중요한 것

한도를 올려 덮을 때와 낮춰 앞당길 때

같은 한도를 만지는 두 방법이 조사에서는 정반대 역할을 한다. 열린 개수는 프로세스마다 있는 fd 디렉터리의 항목 수로 센다.

  • 한도를 올려 덮는다죽는 시각이 오후 세 시에서 밤 열 시로 옮겨 갈 뿐이다. 새는 쪽을 고치지 않으면 언젠가 반드시 다시 만난다.
  • 한도를 낮춰 바닥을 앞당긴다소프트 한도는 하드 이하에서 프로세스가 스스로 낮출 수 있다. 열 시간 뒤에 만날 바닥을 지금 만들 수 있어서 재현과 증명이 시간 단위에서 초 단위로 줄어든다.

여기서 구분할 것 낮춘 한도는 재현 도구이지 수정이 아니다. 그리고 RLIMIT_AS 는 주소 공간의 상한이라 실제 사용량과 같지 않다. 매핑만 하고 건드리지 않은 영역도 주소 공간을 차지한다.

잠깐, 예측해 보세요 한도를 낮춰 재현해 보니 요청 하나당 기술자가 2개씩 늘어 있었다. 한도를 올려 덮는 대응과 견주어, 이 숫자가 해 주는 일은 무엇일까?

설명 확인 · 채점 없는 자가 점검

새는 쪽을 고쳐야 한다는 근거와 고쳤는지 확인할 기준이 생긴다. 한도를 올리면 죽는 시각만 옮겨 갈 뿐이다. 고친 판에서 같은 방법으로 기울기를 다시 재서 0 이 나오면 고쳤다는 증명이 된다.

근거 문서

다음 실습에서 할 것

세션 처리기를 받아 열린 기술자 수를 세는 도구를 만들고, 요청 수를 바꿔 가며 재 요청 하나당 새는 개수를 기울기로 구한다. 한도를 낮춰 바닥을 앞당겨 재현하고, 바닥난 뒤 서로 다른 네 가지 일이 각각 어떤 얼굴로 실패하는지 기록한다. 메모리 쪽도 주소 공간 한도로 안전하게 재현해 예외로 받는 것과 죽임당하는 것의 차이를 적고, 마지막으로 고친 판의 기울기가 0 임을 같은 도구로 증명한다.