シグナルを読み、まず何に手を付けるか決める
한국어 원문으로 표시합니다.
목표
데이터베이스 장애에서 가장 비싼 실수는 피해자를 원인으로 착각하는 것입니다. 막혀 있는 세션 넷을 죽여 봐야 다시 막히고, 정작 막고 있는 하나는 그대로 남습니다.
여기서는 실제 모양의 스냅숏을 읽고 무엇부터 손댈지를 정합니다.
재료
/opt/lab/dbsignals/activity.tsv pg_stat_activity 스냅숏 30줄
/opt/lab/dbsignals/locks.tsv 누가 누구를 막고 있는가
/opt/lab/dbsignals/statements.tsv pg_stat_statements 상위 질의
mkdir -p /root/dbsignals && cp /opt/lab/dbsignals/* /root/dbsignals/ && cd /root/dbsignals
column -t -s $'\t' activity.tsv | head -5
남길 것
01-states.txt 상태별 개수
02-waits.txt 무엇을 기다리는가
03-root.txt 사슬의 뿌리
04-statements.txt 총 시간과 평균 시간
05-decide.md 지금 할 것과 나중에 할 것
06-prevent.md 다시 안 생기게
07-notes.md 왜 그런지
지금 무엇이 돌고 있나
activity.tsv 에서 상태별 개수를 세고, idle in transaction 중 가장 오래된 것이 몇 초인지 01-states.txt 에 적으세요. 그 상태가 왜 위험한지도 한 줄 씁니다.
awk -F'\t' 'NR>1 {print $3}' activity.tsv | sort | uniq -c
idle 은 그냥 놀고 있는 연결이라 대개 문제가 아닙니다. idle in transaction 은 다릅니다 — 트랜잭션을 열어 둔 채로 아무것도 안 하고 있어서, 그동안 쥔 잠금이 안 풀리고 정리(vacuum)도 그 지점을 넘어가지 못합니다.
무엇을 기다리는가
active 인 것들만 골라 wait_event_type 별로 세어 02-waits.txt 에 적고, Lock 대기와 IO 대기의 처방이 어떻게 다른지 적으세요.
awk -F'\t' 'NR>1 && $3=="active" {print ($4=="" ? "(없음)" : $4)}' activity.tsv | sort | uniq -c
대기 유형이 비어 있는 것은 실제로 CPU 를 쓰며 돌고 있는 것입니다.
Lock 은 다른 세션이 놓아 줘야 풀리므로 그 다른 세션을 봐야 합니다. IO 는 저장소가 느리거나 캐시에 없는 것이라 질의나 하드웨어를 봐야 합니다. 같은 "느리다" 인데 볼 곳이 전혀 다릅니다.
사슬의 뿌리를 찾는다
locks.tsv 를 보고 누가 막혀 있고 누가 막고 있는지 03-root.txt 에 적으세요. 뿌리 세션이 무엇을 하고 있는지도 함께 씁니다.
막혀 있는 것들은 피해자입니다. 그것들을 죽여도 다시 막힙니다.
뿌리를 찾았으면 activity.tsv 에서 그 pid 가 어떤 상태이고 어떤 질의를 마지막으로 던졌는지 보세요. 앞 단계에서 본 것과 이어집니다.
총 시간과 평균 시간은 다른 문제다
statements.tsv 에서 총 시간이 가장 큰 질의와 호출이 아주 많아 합이 큰 질의를 각각 찾아 04-statements.txt 에 적고, 둘의 처방이 어떻게 다른지 쓰세요.
평균이 3초인 질의와 평균이 0.2ms 인 질의가 있는데, 뒤의 것이 480만 번 불리면 합은 비슷해집니다.
평균이 큰 것은 그 질의 하나를 고칩니다(인덱스, 집계 미리 계산). 호출이 많은 것은 질의가 아니라 부르는 쪽을 고칩니다(N+1, 캐시, 배치).
지금 할 것과 나중에 할 것
지금 이 순간 무엇을 할지를 05-decide.md 에 적으세요. 지금 손댈 것, 곧 볼 것, 나중에 고칠 것 셋으로 나누고 각각의 이유를 씁니다.
지금 할 일은 하나입니다. 그 세션을 취소할지 끊을지도 정하세요.
pg_cancel_backend(pid)— 지금 돌고 있는 질의만 취소합니다. 연결은 남습니다pg_terminate_backend(pid)— 연결을 끊습니다. 열린 트랜잭션도 함께 되돌아갑니다
idle in transaction 은 돌고 있는 질의가 없으므로 취소로는 안 풀립니다.
다시 안 생기게 한다
같은 사고가 다시 나지 않게 할 설정과 경보를 06-prevent.md 에 적으세요. 값은 숫자로 정합니다.
idle_in_transaction_session_timeout 이 이 사고를 통째로 막습니다. lock_timeout 과 statement_timeout 도 함께 정하세요.
켜라는 말만으로는 아무도 못 켭니다. 값을 정해 주어야 옮길 수 있습니다.
설정만으로는 다음 사고를 못 봅니다. 무엇을 보고 있을지도 적으세요 — 가장 오래된 트랜잭션의 나이 같은 것.
다음에 읽을 사람에게
여기서 본 것 중 넷 이상을 골라 07-notes.md 에 정리하세요. 무엇을 했는지가 아니라 왜 그런지를 적습니다.
새벽에 호출받은 자신이 읽는다고 생각하세요. "pg_stat_activity 를 봤다" 는 도움이 안 되고, "막혀 있는 것들은 피해자다 — 죽여도 다시 막힌다" 는 도움이 됩니다.