장애 당직을 서던 밤, 알림이 울리기 전에 벌어지고 있던 일들

이번 주는 제가 장애 당직 순번입니다. 새벽 두 시, 휴대폰이 울렸습니다. 결제 서버 하나가 응답을 멈췄다는 알림이었습니다.
대시보드를 열어보니, 알림이 뜨기 5분 전부터 이미 이상 신호가 있었다는 게 눈에 들어왔습니다.

서버가 멈추기 전부터 이미 신호가 있었을 때, 하트비트

text
102:11:00 heartbeat OK
202:11:30 heartbeat OK
302:12:00 heartbeat missed
402:12:30 heartbeat missed → alert fired
동료가 예전에 짜둔 모니터링 설정을 열어보다가, 30초마다 서버에 "살아있어?"라고 확인하는 신호를 보내도록 되어 있는 걸 발견했습니다.
정해진 간격으로 계속 신호를 주고받다가, 응답이 끊기는 순간을 곧바로 잡아내는 방식이었습니다. 사람 맥박을 재듯, 일정한 박자가 끊기는 순간이 곧 이상 신호였습니다. 알림이 울린 시점보다 앞서서 문제가 시작된 지점을 이미 짚어낼 수 있었습니다.
일정한 간격으로 신호를 주고받다가 응답이 끊기는 지점을 곧바로 잡아내는 구조
일정한 간격으로 신호를 주고받다가 응답이 끊기는 지점을 곧바로 잡아내는 구조
원인이 어디서 시작됐는지는 알겠는데, 정확히 어떤 요청 때문에 서버가 멈췄는지는 여전히 안개 속이었습니다.

에러 하나의 원인을 거슬러 올라갈 때, 브레드크럼이 남긴 자취

로그 시스템이 자동으로 남겨둔 흔적을 따라가 보니, 각 요청마다 거쳐 간 경로가 순서대로 남아 있었습니다.
text
1[req-9f21] api-gateway → payment-service → db-pool(대기 중)
요청 하나가 여러 서비스를 거쳐 가는 동안, 지나온 경로를 순서대로 남겨서 문제가 어디서 막혔는지 거꾸로 되짚어갈 수 있게 해주는 흔적이었습니다. 동화 속 아이들이 숲에 빵조각을 떨어뜨려 돌아갈 길을 표시해둔 것과 같은 발상이라, 이름을 알고 나니 오히려 그림이 쉽게 그려졌습니다. db-pool에서 대기가 걸려 있다는 걸 이 흔적 덕분에 바로 찾았습니다.
요청이 지나온 경로를 순서대로 남겨 막힌 지점을 거꾸로 되짚어가는 흔적
요청이 지나온 경로를 순서대로 남겨 막힌 지점을 거꾸로 되짚어가는 흔적
db-pool 설정을 고치려고 배포 이력을 뒤지다가, 몇 달 전 제가 직접 걸어둔 설정 하나를 다시 발견했습니다.

새 버전을 진짜로 켜지 않고도 미리 검증할 때, 그림자 모드

yaml
1mode: shadow
2new_pool: enabled
3serve_traffic: false
몇 달 전, DB 연결 방식을 새로 바꾸면서 겁이 나서 이렇게 설정해뒀던 걸 까맣게 잊고 있었습니다. 새 버전이 실제 사용자에게 응답하지는 않으면서, 똑같은 요청을 뒤에서 조용히 받아 처리해보고 결과만 비교하는 방식이었습니다. 그림자가 실체를 그대로 따라다니듯, 실제 서비스 뒤에 숨어서 같은 일을 몰래 해보고 있었습니다. 이번 장애는 이 그림자 버전이 미처 못 잡아낸 예외 상황이었습니다.
실제 응답은 기존 버전이 하고, 새 버전은 뒤에서 같은 요청을 몰래 처리해보는 구조
실제 응답은 기존 버전이 하고, 새 버전은 뒤에서 같은 요청을 몰래 처리해보는 구조
DB 대기 문제를 고치고 로그를 정리하는데, 팀 채팅방에 남아있던 동료의 옛날 명령어 하나가 계속 헷갈렸습니다.

명령어 뒤에 붙는 이상한 기호가 헷갈릴 때, 파이프

bash
1$ cat error.log | grep "db-pool" | wc -l
이 기호를 볼 때마다 뭘 하는 건지 몰라서 하나씩 따로 명령어를 실행하고 결과를 손으로 옮겨 적곤 했습니다. 같은 답답함을 몇 번이나 반복하고 나서야 이 기호의 역할을 제대로 찾아봤습니다.
한 명령어의 출력 결과를 곧바로 다음 명령어의 입력으로 넘겨주는 연결 고리였습니다. 세로줄(|) 모양이 마치 물이 흐르는 관처럼 생겨서 파이프라는 이름이 붙었는데, 이름 자체보다 실제로 손으로 옮겨 적던 걸 한 줄로 줄여준다는 사실이 더 크게 와닿았습니다.
한 명령어의 결과가 그대로 다음 명령어의 입력으로 이어지는 흐름
한 명령어의 결과가 그대로 다음 명령어의 입력으로 이어지는 흐름
터미널 화면에서 세 개의 명령어가 세로줄 기호로 이어져 있는 것을 손가락으로 짚어보는 장면
파이프로 걸러낸 결과를 팀에 공유하려고 파일로 저장하려는데, 이번엔 또 다른 기호가 발목을 잡았습니다.

결과를 화면 대신 파일에 담고 싶을 때, 리다이렉션

bash
1$ cat error.log | grep "db-pool" > incident-report.txt
검색해보니 > 기호의 역할이 파이프와 닮은 듯 달랐습니다. 원래 화면에 뿌려질 출력 결과를, 화면 대신 지정한 파일로 방향을 돌려서 담는 것이었습니다. 수도꼭지 물을 원래 개수대로 흘려보내는 대신 호스를 끼워서 다른 통에 받는 것과 비슷합니다. 새벽 네 시가 다 되어서야 장애 보고서 초안을 이 방식으로 저장하고 자리에서 일어날 수 있었습니다.
화면에 뿌려질 출력을 화면 대신 지정한 파일로 돌려 담는 구조
화면에 뿌려질 출력을 화면 대신 지정한 파일로 돌려 담는 구조
동트기 직전 창가, 노트북을 덮고 소파에 기대 잠시 눈을 붙이는 당직자의 모습
결제 서버는 새벽 네 시쯤 정상으로 돌아왔습니다. 알림이 울린 건 두 시였지만 신호는 그보다 훨씬 전부터 화면 어딘가에 남아 있었습니다. 당직의 절반은 문제를 고치는 일이고, 나머지 절반은 이미 남아 있던 기록을 읽어내는 일이었습니다.

자주 묻는 질문

하트비트 간격은 짧을수록 좋은가요?

짧을수록 문제를 더 빨리 알아챌 수 있지만, 그만큼 서버끼리 주고받는 신호 자체가 부담이 됩니다. 서비스 중요도에 따라 몇 초에서 몇십 초 사이로 절충하는 경우가 많습니다.

그림자 모드로 검증하면 실제 장애를 완전히 막을 수 있나요?

완전히는 아닙니다. 그림자 모드는 평소 트래픽 패턴에서 문제가 없는지 확인해줄 뿐, 이번처럼 드물게 발생하는 예외 상황까지 전부 잡아내지는 못합니다.

파이프와 리다이렉션을 같이 써도 되나요?

네, 실제로 자주 함께 씁니다. 여러 명령어를 파이프로 연결해서 결과를 걸러낸 다음, 마지막에 리다이렉션으로 파일에 저장하는 조합이 흔합니다.

<a href="/glossary/code-generation" class="glossary-link" title="AI가 자연어 설명이나 기존 코드 문맥을 바탕으로 프로그래밍 코드를 자동으로 작성하는 기술입니다.">AI 코딩</a> 도구 용어 사전 연작의 7편입니다. 다음 편 예고 · 8편 서비스 하나가 죽으니 옆에 있던 것들까지 줄줄이 넘어가던 밤 서킷브레이커 · 데드레터 큐 · 심볼릭 링크 · 데몬 · 소켓

관련 글