주문 처리가 멈춰서 큐에는 메시지가 쌓이는데 소비가 전혀 안 되고 있었다. 소비하는 프로세스는 살아 있었고 자원 사용량도 전부 정상이었다.
Table of contents
Open Table of contents
지표가 전부 정상이던 정지
프로세스가 떠 있고 브로커에 연결돼 있고 메모리 사용량도 정상이었다. 인프라 관점에서 보는 지표로는 아무 문제도 나타나지 않았다.
프로세스가 살아 있는데 메시지만 안 나가는 정지는 그 지표들로는 잡히지 않는다. 그래서 소비자 로그를 처음부터 시간순으로 훑어 내려갔다.
하나가 뒤 전부를 막는 이유
정상 처리가 이어지다가 한 지점에서 역직렬화 예외가 나고 그 뒤로 아무것도 없었다. 순서를 보장하는 큐에서는 한 메시지를 처리하지 못하면 진행 위치가 넘어가지 않는다.
다음 메시지로 넘어갈 수가 없으니 뒤에 쌓인 전부가 그 하나를 기다리게 된다. 하나가 전체를 마비시키는 구조라 처리 못 하는 메시지 한 건이 곧 전면 정지였다.
필드 하나가 늘었을 뿐이었다
원인은 상대 시스템이 응답 구조에 필드를 하나 추가한 것이었다. 우리 객체가 모르는 필드를 만나면 예외를 던지게 돼 있었다.
필드 추가는 보통 하위 호환으로 여겨져서 따로 알리지 않는 경우가 많다. 역직렬화에 관용 옵션을 켜서 모르는 필드를 무시하게 하니 막힘이 풀렸다.
공유 컨슈머가 공유하는 실패
이 소비자는 여러 거래처에서 오는 메시지를 한꺼번에 처리하고 있었다. 문제를 일으킨 것은 한 곳인데 다른 곳의 메시지도 같은 줄 뒤에 서 있었다.
제보가 온 곳은 외부 대사 시스템을 써서 먼저 알아챈 한 곳뿐이었으므로 제보 범위가 장애 범위가 아니었다. 처리 못 하는 메시지를 몇 번 재시도한 뒤 별도 큐로 보내고 진행 위치를 넘기게 했고 큰 거래처는 소비자를 분리했다.
커밋 시점과 발효 시점
이 취약점을 만든 코드 변경은 몇 달 전이었는데 장애는 최근이었다. 상대가 필드를 추가한 시점이 촉발 조건이었다.
우리 코드는 그동안 계속 취약한 상태였고 촉발 조건만 나중에 온 것이다. 최근에 무엇을 바꿨는지로만 찾으면 안 나오므로 외부 변화도 함께 봐야 했다.
정리
- 파싱 실패 한 건이 뒤에 쌓인 전부를 막는다
- 순서를 보장하는 큐에서는 진행 위치가 안 넘어간다
- 프로세스가 살아 있고 지표가 정상인 정지가 있다
- 모르는 필드를 관용하게 두면 외부의 추가에 안 깨진다
- 처리 못 하는 메시지를 격리하고 진행 위치를 넘긴다
- 공유 소비자는 실패도 공유하므로 제보 범위가 장애 범위가 아니다
- 전파 범위와 관리 비용을 견줘 분리 정도를 정한다
- 커밋 시점과 발효 시점이 다르므로 외부 변화도 함께 본다