서비스 하나가 죽으니 옆에 있던 것들까지 줄줄이 넘어가던 밤

추천 서비스 하나가 느려지기 시작하더니, 몇 분 만에 관련 없어 보이던 다른 서비스까지 줄줄이 응답이 느려졌습니다.
전체 로그를 다시 훑어보다가, 이상하게도 추천 서비스를 호출하던 다른 서비스들이 어느 순간부터 아예 추천 서비스를 부르지 않기 시작한 걸 발견했습니다.

느려지던 서비스를 갑자기 아무도 안 부르기 시작할 때, 서킷브레이커

text
1[circuit-breaker] recommend-service: OPEN
2Calls to recommend-service are now short-circuited
로그에 찍힌 OPEN이라는 상태값을 다시 들여다보다가 이유를 알아챘습니다. 실패율이 일정 수준을 넘어가자, 다른 서비스들이 더 이상 추천 서비스를 부르지 않고 곧바로 실패 처리하도록 스스로 차단하고 있었습니다.
고장 난 회로에 계속 전류를 흘리면 불이 날 수 있어서 두꺼비집을 내리듯, 실패가 반복되는 곳으로는 아예 요청을 보내지 않도록 회로 자체를 끊어버리는 장치였습니다. 덕분에 추천 서비스 하나의 문제가 전체로 번지지는 않았습니다.
실패가 반복되면 아예 요청 자체를 보내지 않도록 회로를 끊어버리는 구조
실패가 반복되면 아예 요청 자체를 보내지 않도록 회로를 끊어버리는 구조
회로가 끊긴 동안 처리되지 못한 메시지들이 어디로 가는지 궁금해서, 마침 옆자리에 있던 동료에게 물어봤습니다.

처리 못 한 메시지가 그냥 사라지지 않을 때, 데드레터 큐

동료가 화면을 보여주며 설명했습니다. "그거 사라진 거 아니야. 여기 따로 쌓여."
text
1dead-letter-queue: recommend-events
2 pending: 1,204 messages
정상적으로 처리하지 못한 메시지를 그냥 버리는 대신, 나중에 다시 살펴볼 수 있도록 따로 모아두는 대기 줄이었습니다. 이름만 보면 완전히 죽어서 못 쓰게 된 편지함 같지만, 실제로는 나중에 사람이 열어서 재처리하거나 원인을 조사할 수 있는 보관함에 가까웠습니다. 1,204건이 여기 쌓여 있었고, 서킷이 닫히면 다시 처리하기로 했습니다.
처리 실패한 메시지가 사라지지 않고 따로 모여 대기하는 구조
처리 실패한 메시지가 사라지지 않고 따로 모여 대기하는 구조
메시지 문제를 정리하고 나서, 배포 설정 문서를 다시 찾아보다가 예전부터 궁금했던 표시 하나를 마주쳤습니다.

파일 하나가 다른 위치를 가리킬 뿐일 때, 심볼릭 링크

bash
1$ ls -l /app/current
2current -> /app/releases/v482
공식 문서를 찾아보니 -> 표시가 있는 파일은 실제 파일이 아니라, 다른 위치를 가리키기만 하는 이름표 같은 것이었습니다.
실제 내용물은 다른 곳에 있고, 이 파일은 그저 그 위치를 가리키는 바로가기였습니다. 배포할 때마다 새 버전 폴더를 통째로 만든 다음, current라는 이름표만 새 폴더 쪽으로 다시 걸어주는 방식으로 배포를 순식간에 전환하고 있었습니다.
파일 자체가 아니라 실제 위치를 가리키기만 하는 이름표 구조
파일 자체가 아니라 실제 위치를 가리키기만 하는 이름표 구조
폴더 아이콘 하나에서 화살표가 다른 폴더로 이어지는 것을 손으로 따라가 보는 장면
배포 방식을 이해하고 나니, 이번엔 서버에 늘 켜져 있는 프로세스 하나가 뭘 하는 건지 궁금해졌습니다.

로그인한 적도 없는데 계속 돌고 있는 프로세스, 데몬

동료가 예전에 서버에 설치해둔 스크립트를 살펴보다가, 이 프로세스가 시스템이 켜질 때부터 자동으로 함께 실행되도록 설정돼 있는 걸 발견했습니다.
누가 로그인해서 실행한 게 아니라, 백그라운드에서 조용히 계속 돌면서 정해진 일을 처리하는 상시 실행 프로세스였습니다. 로그 정리, 헬스체크 응답, 큐 처리 같은 일들이 전부 이런 방식으로 눈에 안 띄게 돌아가고 있었습니다.
아무도 로그인하지 않아도 백그라운드에서 계속 도는 상시 실행 프로세스
아무도 로그인하지 않아도 백그라운드에서 계속 도는 상시 실행 프로세스
거의 다 정리됐다 싶었는데, 새벽 세 시가 넘어가면서 완전히 다른 종류의 에러가 하나 더 튀어나왔습니다.

연결이 갑자기 안 될 때, 바닥나 있던 소켓

text
1Error: EMFILE, too many open sockets
에러 메시지가 스스로 원인까지 설명해주고 있었습니다. 서버끼리 데이터를 주고받는 통로 하나하나가 소켓인데, 아까 회로가 끊겼다 이어졌다를 반복하는 동안 이 통로가 제대로 안 닫힌 채 계속 쌓이고 있었습니다.
전화 통화가 끝나면 수화기를 내려놔야 다음 통화를 받을 수 있는 것처럼, 연결을 다 쓰고도 제대로 닫아주지 않으면 새 연결을 만들 통로 자체가 바닥나 버립니다. 남은 연결을 정리하는 스크립트를 돌리고 나서야 서버가 다시 안정을 찾았습니다.
닫히지 않은 연결이 쌓여 새 연결을 만들 통로가 거의 남지 않은 상태
닫히지 않은 연결이 쌓여 새 연결을 만들 통로가 거의 남지 않은 상태
동이 튼 서버실 창밖, 상태 표시등이 전부 초록불로 돌아온 화면을 확인하는 장면
줄줄이 넘어가던 지연은 남은 연결을 정리하고 나서야 멈췄습니다. 서비스 하나가 죽었을 때 옆의 것들까지 넘어간 이유는 서로 붙어 있어서가 아니라, 넘어지는 쪽을 붙잡아 줄 장치가 중간에 없어서였습니다.

자주 묻는 질문

서킷브레이커가 열리면 사용자는 어떻게 되나요?

해당 기능만 잠시 못 쓰게 되지만, 전체 서비스가 멈추는 것보다는 훨씬 낫습니다. 추천 목록 대신 기본 목록을 보여주는 식으로 대체 응답을 준비해두면 사용자가 느끼는 불편도 줄일 수 있습니다.

데드레터 큐에 쌓인 메시지는 자동으로 재처리되나요?

설정에 따라 다릅니다. 자동으로 몇 차례 재시도하게 만들 수도 있고, 사람이 직접 확인하고 재처리 여부를 결정하도록 남겨두는 경우도 많습니다.

소켓 고갈은 왜 갑자기 새벽에 터졌나요?

평소엔 연결이 금방 정리돼서 티가 안 나다가, 장애로 재시도가 반복되면서 정리되지 않은 연결이 짧은 시간에 몰아서 쌓였기 때문입니다. 평소보다 몇 배 많은 연결이 동시에 열려 있었던 셈입니다.

<a href="/glossary/code-generation" class="glossary-link" title="AI가 자연어 설명이나 기존 코드 문맥을 바탕으로 프로그래밍 코드를 자동으로 작성하는 기술입니다.">AI 코딩</a> 도구 용어 사전 연작의 8편입니다. 다음 편 예고 · 9편 보안팀에서 AI 기능 점검을 요청받은 날 <a href="/glossary/ai-guardrails" class="glossary-link" title="AI 모델이 기업의 정책과 안전 기준을 벗어나지 않도록 입력값과 출력값을 실시간으로 검사하고 제어하는 기술적 보안 계층입니다. 부적절한 답변, 개인정보 유출, 환각 현상을 방지하여 비즈니스 안정성을 확보합니다.">가드레일</a> · 프롬프트 인젝션 · <a href="/glossary/sandbox" class="glossary-link" title="외부 시스템과 격리된 안전한 가상 환경으로, AI가 생성한 코드를 안전하게 실행하여 보안 위협을 차단하거나 새로운 서비스를 법적·기술적 제약 없이 실험하는 독립된 테스트 공간입니다.">샌드박스</a> 탈출 · 포트·로컬호스트 · 스태시

관련 글