포스트모템 - 장애 후 분석
장애는 왜 반복될까
장애가 나면 원인을 찾고 재발을 방지하는 게 중요하다. 그런데 실제로는 "복구했으면 됐지" 하고 넘어가는 경우가 많다. 나도 처음에 그랬다.
Postmortem이라는 문화를 접한 건 구글과 넷플릭스의 엔지니어링 블로그를 읽으면서였다. "장애 후 분석 보고서를 팀 전체에 공개한다"는 내용이 인상적이었다. 실수를 숨기는 게 아니라 오히려 공유해서 조직 전체가 배운다는 접근 방식이 와닿았다.
직접 코드를 짜다 보면 비슷한 실수를 반복하게 된다. DB 커넥션 풀이 고갈되는 건 대표적인 사례다. 커넥션을 제대로 반환하지 않으면 커넥션이 계속 쌓이고 결국 서비스가 멈춘다. 이런 문제는 한 번 겪고 나면 다시는 반복하고 싶지 않다.
그래서 포스트모템을 정리해봤다. 장애 후 분석 보고서를 어떻게 쓰는지, 왜 중요한지.
포스트모템이 뭔가요?
Postmortem은 직역하면 "사후 검시"입니다. 의학 용어인데, 개발 쪽에서는 장애가 발생한 후에 원인을 분석하고, 재발을 방지하기 위한 문서를 말합니다.
처음엔 "장애 보고서 쓰는 게 뭐가 중요해? 복구했으면 됐지" 하고 생각했다. 그런데 구글 SRE 문서를 읽다 보니 이런 내용이 있었다:
"포스트모템을 작성하지 않으면, 같은 실수를 또 하게 된다. 다음번엔 나 혼자의 문제가 아니라 팀 전체의 문제가 될 수 있다."
그 말이 와닿았다. 직접 겪어봐야 안다는 게 이런 거구나 싶었다.
첫 포스트모템 작성하기
구글이나 AWS 같은 큰 회사들이 공개한 포스트모템 형식을 참고해서 직접 써봤다. 구성은 이랬다:
1. 요약 (Summary)
가장 먼저 한눈에 보이는 요약을 씁니다:
날짜: 2025-01-15
시간: 03:00 - 05:00 (KST)
지속 시간: 2시간
영향: 전체 사용자 서비스 불가
심각도: Critical
근본 원인: DB 커넥션 풀 고갈
이렇게 쓰고 나니, 장애의 전체 그림이 한눈에 들어오더라고요.
2. 타임라인 (Timeline)
그다음은 시간 순서대로 무슨 일이 있었는지 적습니다. 제 경우는 이랬어요:
03:00 - 모니터링 알람: 응답 시간 5초 초과
03:02 - 로그 확인: "Cannot get connection from pool" 에러 다수 발견
03:05 - DB 커넥션 풀 상태 확인: 100/100 (모두 사용 중)
03:10 - 서비스 재시작 시도 → 실패 (커넥션 여전히 고갈)
03:20 - 코드 리뷰: finally 블록에서 커넥션 반환 누락 발견
03:30 - 핫픽스 배포 시작
04:00 - 배포 완료
04:10 - DB 커넥션 풀 수동 초기화
04:30 - 서비스 정상화 확인
05:00 - 모니터링 정상 수치 확인, 장애 종료 선언
이렇게 쓰면서 느낀 건, 내가 뭘 잘못했는지가 명확히 보인다는 겁니다. 03:10에 서비스 재시작만 하고 끝낼 뻔했는데, 그랬으면 또 터졌을 거예요.
3. 근본 원인 (Root Cause)
가장 중요한 부분입니다. 왜 이런 일이 발생했는가?
제 경우는 이랬습니다:
// 문제가 있던 코드
async function getUser(userId) {
const connection = await pool.getConnection();
try {
const result = await connection.query('SELECT * FROM users WHERE id = ?', [userId]);
return result[0];
} catch (error) {
console.error(error);
throw error;
}
// 문제: connection.release()를 안 했음!
}
finally 블록에서 connection.release()를 안 해서, 커넥션이 계속 쌓였던 겁니다. 하루에 수천 번 호출되는 함수였는데, 커넥션이 반환 안 되니까 결국 풀이 고갈된 거죠.
근본 원인:
- 직접 원인: DB 커넥션을 사용 후 반환하지 않음
- 근본 원인: 커넥션 관리에 대한 이해 부족
- 시스템 원인: 커넥션 풀 사용량 모니터링 부재
이렇게 여러 레벨의 원인을 찾는 게 중요합니다. "코드 실수"로만 끝내면, 또 비슷한 실수를 하거든요.
4. 해결 방법 (Resolution)
어떻게 고쳤는지 적습니다:
// 수정된 코드
async function getUser(userId) {
const connection = await pool.getConnection();
try {
const result = await connection.query('SELECT * FROM users WHERE id = ?', [userId]);
return result[0];
} catch (error) {
console.error(error);
throw error;
} finally {
// 추가: 반드시 커넥션 반환
connection.release();
}
}
그리고 더 나아가서, 이런 실수를 방지하는 헬퍼 함수도 만들었습니다:
// 커넥션 관리를 자동화하는 헬퍼
async function withConnection(callback) {
const connection = await pool.getConnection();
try {
return await callback(connection);
} finally {
connection.release(); // 자동으로 반환
}
}
// 사용 예시
async function getUser(userId) {
return withConnection(async (conn) => {
const result = await conn.query('SELECT * FROM users WHERE id = ?', [userId]);
return result[0];
});
}
이제 개발자가 release()를 깜빡해도 괜찮습니다. 자동으로 처리되니까요.
5. 액션 아이템 (Action Items)
가장 중요한 부분입니다. 재발 방지를 위해 뭘 할 건가?
제 액션 아이템은 이랬어요:
즉시 (1주일 내):
- 모든 DB 쿼리 코드에
finally블록 추가 (담당: 나, 완료: 1/16) -
withConnection헬퍼 함수 도입 (담당: 나, 완료: 1/17) - 커넥션 풀 사용량 모니터링 추가 (담당: 나, 완료: 1/18)
단기 (1개월 내):
- 커넥션 풀 크기 자동 조정 로직 추가 (담당: 팀, 기한: 2/15)
- 코드 리뷰 체크리스트에 "리소스 반환" 항목 추가 (담당: 팀 리드, 기한: 2/1)
장기 (3개월 내):
- ORM 도입 검토 (커넥션 관리 자동화) (담당: 팀, 기한: 4/15)
- 장애 대응 플레이북 작성 (담당: 팀, 기한: 4/30)
이렇게 담당자와 기한을 명확히 하는 게 중요하다. 안 그러면 "나중에 하자"가 되고, 결국 안 하게 된다.
포스트모템의 원칙 - 비난 금지
포스트모템 문화에서 가장 중요하게 강조하는 원칙이 있다. "No Blame Culture" - 비난하지 않기.
처음엔 이해가 안 됐다. "내가 실수했는데, 내 잘못 아닌가?" 근데 구글 SRE 책에서 이런 내용을 읽었다:
"실수한 것은 맞다. 하지만 왜 그 실수를 할 수밖에 없었는지를 생각해봐야 한다. 커넥션 관리를 제대로 배운 적이 있는가? 모니터링이 있었는가? 코드 리뷰에서 누가 잡아줬는가? 문제는 시스템에 있다."
그 말이 와닿았다. 사람을 탓하면, 실수를 숨기게 된다. 그러면 조직 전체가 배울 기회를 잃는다.
포스트모템의 핵심 원칙:
1. 사람이 아닌 시스템을 개선한다
- ❌ "John이 커넥션 반환을 안 했다"
- ✅ "커넥션 반환을 강제하는 시스템이 없었다"
2. 투명하게 공유한다
- 포스트모템은 전 팀원이 볼 수 있어야 함
- 실수를 숨기지 않고 공개함
- 다른 팀도 같은 실수를 하지 않도록
3. 학습에 집중한다
- "이번에 뭘 배웠나?"
- "다음엔 어떻게 더 잘할 수 있을까?"
포스트모템을 쓰고 나서
처음으로 포스트모템 형식으로 정리해봤을 때, 몇 가지가 달라졌다.
1. 같은 실수를 안 하게 됐다
- 액션 아이템을 실제로 실행하니까, 비슷한 문제가 안 생김
- 모니터링이 생겨서, 문제를 미리 발견할 수 있게 됨
2. 다음 대응이 빨라졌다
- 타임라인을 쓰면서, 어떤 순서로 대응해야 하는지 정리됨
- 다음에 비슷한 상황이 생기면 훨씬 침착하게 대응 가능
3. 원인 분석이 깊어졌다
- "코드 실수"로만 끝내지 않고, 시스템 레벨 원인까지 파고들게 됨
- 재발 방지 액션이 구체적이고 실행 가능해짐
4. 심리적으로 편해졌다
- 실수를 숨기지 않고 정리하는 게 오히려 성장에 도움이 된다는 걸 이해함
- "실수는 배움의 기회"라는 문화가 왜 중요한지 와닿았다
좋은 포스트모템 vs 나쁜 포스트모템
몇 번 포스트모템을 쓰다 보니, 좋은 포스트모템과 나쁜 포스트모템의 차이를 알게 됐습니다.
나쁜 포스트모템
제목: DB 장애
문제: DB가 다운됐음
원인: 커넥션 문제
해결: 재시작함
재발 방지: 조심하기
이건 아무 도움이 안 됩니다. 구체적이지 않고, 배울 게 없어요.
좋은 포스트모템
제목: DB 커넥션 풀 고갈로 인한 2시간 서비스 다운
요약:
- 날짜: 2025-01-15 03:00-05:00
- 영향: 전체 사용자 (약 1,000명)
- 근본 원인: finally 블록에서 커넥션 미반환
타임라인: (상세한 시간대별 기록)
근본 원인 분석:
- 직접 원인: connection.release() 누락
- 시스템 원인: 커넥션 풀 모니터링 부재
- 프로세스 원인: 코드 리뷰에서 리소스 관리 체크 안 함
해결:
- 즉시: finally 블록 추가
- 단기: withConnection 헬퍼 도입
- 장기: ORM 도입 검토
액션 아이템: (담당자, 기한 명시)
배운 점:
- 리소스는 반드시 반환해야 함
- 모니터링이 없으면 문제를 늦게 발견함
- 자동화가 수동 관리보다 안전함
차이가 보이시나요? 좋은 포스트모템은 구체적이고, 실행 가능하고, 배움이 있습니다.
한 줄 요약
포스트모템은 장애 후 원인을 분석하고 재발을 방지하기 위한 문서입니다. 타임라인, 근본 원인, 해결 방법, 액션 아이템을 구체적으로 작성하고, 사람이 아닌 시스템을 개선하는 데 집중하며, 투명하게 공유하여 조직 전체가 학습합니다. 실수를 숨기지 않고 공개하는 문화가 더 강한 시스템을 만듭니다.