django로 만든 웹 서버가 메모리 릭이 있는 것 같아서, 임시로 클라이언트 요청 수에 제한을 걸고 새로 실행되도록 설정했다. 임시가 길어져 1년이 돼가는 동안 잊고 있다가 개발자의 자존심을 건드는 설정인지라 다시 봐야했다.

이 글은 메모리 릭을 잡았다는 것이 아니다.

서버들 중 적당히 스왑을 사용하고 있는 녀석을 골라서 들어갔다. gunicorn 들 중에 제일 pid가 큰 것을 골라낸 후, 해당 PID 에 strace 를 걸어서 GET/POST와 URL PATH에 해당하는 것만 출력하도록 awk 파이프 질을 했다. 동시에 "ps -efl" 로 해당 PID만 골라낸 다음 awk로 적당히 메모리 사용량 부분만 출력한다.

대략 512 개의 요청만 하면 사라지는 gunicorn의 PID이므로, 위 두 작업을 백그라운드로 실행시켜 놓고 fork되어 나간 bash 스크립트의 PID를 저장해 놓는다. 그리고 예의 주시 중인 PID를 1초에 한 번씩 kill -0로 죽었는지를 확인한다. 죽었다면 fork된 녀석들을 죽이고 종료하면 된다.

매우 민첩하게 위 작업들을 생각의 순서대로 뱉어 낸다. 물론 처음에는 바로 쉘상에서 작업하지만, 조금 커질 것을 알고 a.sh 정도로 vim 스크립트로 편집을 하고 실행시키도록 한다.

메모리가 증가되는 순간의 URL을 몇 개 수집한다. 수 분을 기다린다. 화면은 그저 URL과 메모리 사용량만 계속 올라간다.

아뿔싸, 접속한 서버가 오토스케일 정책에 따라 반납되면서 접속이 끊긴다. 뭐야 이거, a.sh 다시작성하기 귀찮은데... 아, 1년만에 들어온 생각인데...

터미널 버퍼를 위로 올려 봐도 vim으로 작업한 것은 남아 있지 않다. 그저 화면에 스크립트의 결과물만 장황하게 넘어간다.

당황하지 말고, terminal에는 vim, less 등이 사용 될 땐 alternative screen으로 작성된다는 것을 알고 있다. ansi terminal이 제공하는 매우 오래된 기능이다.

"ansi show alternative screen" 로 검색을 한다. 

ESC [ Ps ;...; Ps h             Set Mode
ESC [ Ps ;...; Ps l             Reset Mode
      Ps = 4            (A)     Insert Mode
           20           (A)     ‘Automatic Linefeed’ Mode.
           34                   Normal Cursor Visibility
           ?1           (V)     Application Cursor Keys
           ?3           (V)     Change Terminal Width to 132 columns
           ?5           (V)     Reverse Video
           ?6           (V)     ‘Origin’ Mode
           ?7           (V)     ‘Wrap’ Mode
           ?9                   X10 mouse tracking
           ?25          (V)     Visible Cursor
           ?47                  Alternate Screen (old xterm code)
           ?1000        (V)     VT200 mouse tracking
           ?1047                Alternate Screen (new xterm code)
           ?1049                Alternate Screen (new xterm code)

Set은 h로 끝나며, Ps로는 "?47"이면 되겠다. 내 로컬 console은 zsh이니까 몇가지 escape 처리해서 echo 해주면 되겠다. 될까?

echo -e \\e\[\?47h

으허허 보인다. 반납된 서버에 잠시 삽질했던 코드를 다시 치고 싶지 않은 그 임시 코드가 보인다.

#!/bin/bash

P=$(ps -ef | grep guni | grep python2 | sort -k2 -n | awk '($3 != 1) {print $2}' | head -1)
strace -tt -s 100 -p $P 2>&1 | stdbuf -o0 egrep "recvfrom\(.*(GET|POST)" | stdbuf -o0 awk '{print substr($3,2), $4}' &
PID1=$!
while true; do ps -efl | grep gunicorn | grep -w $P | awk '{print $10}'; sleep .5; done &
PID2=$!

trap "kill $PID1 $PID2; exit" SIGINT
while true
do
        if kill -0 $P; then
                sleep 1
                continue
        fi
        kill $PID1 $PID2
        exit
done

귀찮다... 이 시간에 이거 할 게 아닌데.

+ Recent posts