한 고객사에서 항목이 안 맞는다는 메일이 왔다. 외부 대사 플랫폼이 자동으로 잡아 보낸 것이었고 그 고객사 취소 건이 처리 대기 상태에서 안 빠지고 쌓여 있었다. 마켓 쪽에서는 이미 처리가 끝난 건들이었다.
Table of contents
Open Table of contents
범위 측정과 초록 대시보드
한 고객사 문제로 보일 때 그 고객사만 파면 원인을 못 찾는 경우가 많아서 같은 상태로 멈춰 있는 건을 전부 셌다. 10개가 넘는 채널과 20개 안팎의 고객사에 걸쳐 있었다.
전 고객사 공통 장애였고 그 고객사는 대사 플랫폼을 쓰기 때문에 먼저 알아챈 것뿐이었다. 제보자의 범위가 장애의 범위가 아니다. 이걸 놓치면 한 고객사에 맞춘 개별 처방을 만들고 끝낸다.
인프라를 보니 컨슈머 태스크가 전부 실행 중이었고 재시작 이력도 없었으며 자원 지표도 정상이었다. 아무것도 죽지 않은 상태에서 메시지만 안 나가고 있었다. 인프라 지표로는 잡히지 않는 종류의 정지다.
파티션을 막은 한 건
컨슈머 로그를 시간순으로 내려가다가 역직렬화 예외를 찾았다. 한 마켓이 에러 응답에 필드를 하나 추가하기 시작했는데 우리 쪽 데이터 클래스는 기존 두 필드만 알고 있었고 모르는 필드를 무시하는 설정이 없었다.
// @JsonIgnoreProperties(ignoreUnknown = true) 가 없다
data class ErrorConfirmOrderResponse(
val reason: String,
val errorCode: String,
)
파싱이 실패하면 그 메시지가 처리되지 않고 파티션의 오프셋이 전진하지 않으므로 뒤에 쌓인 메시지가 전부 대기한다. 그리고 그 컨슈머는 전 채널의 취소를 공유해서 처리하므로 그 마켓을 쓰지 않는 고객사의 취소도 같은 줄 뒤에 서 있었다. 한 마켓의 응답 형식 변경이 전 고객사 취소 처리를 멈춘 것이다.
커밋 날짜와 발효 날짜
한 가지가 안 맞았다. 그 마켓 연동은 오래됐는데 왜 특정 시점부터 멈췄는가다.
발단 커밋을 찾으니 취소 클레임의 초기 상태값을 한 단계 바꾼 커밋이 3월에 있었는데 장애는 6월부터였다. 코드는 3월부터 있었고 재배포된 시점에 발효된 것이다. 커밋 날짜와 발효 날짜가 다르다는 것을 놓치면 3월에는 멀쩡했다는 사실에서 막힌다.
상태를 못 바꾸는 처리기
인과를 이어 보니 초기 상태값 변경으로 취소 클레임이 활성 큐에 들어가고, 수집 시점에 마켓이 이미 처리한 취소도 그 상태로 만들어지며, 배치가 매 사이클 그 건들을 큐로 보내고, 처리기가 특정 상태인 것만 처리하면서 이미 완료된 것은 통과만 하고 상태를 안 바꾸는 흐름이었다.
그래서 영구히 잔류한다. 매 사이클 다시 보내지고 다시 통과되고 다시 남는다. 이전 경로에서는 다른 상태로 들어가 이 큐를 아예 안 탔으므로 경로가 바뀌면서 처리 불가능한 것들이 활성 큐에 갇힌 것이다.
세 층의 조치
결함이 두 층이고 조치는 셋이었다. 표층은 미지 필드를 무시하도록 데이터 클래스를 고치는 것이고 심층은 처리기가 이미 완료된 건을 종결 처리하거나 수집 단계에서 애초에 활성 큐에 넣지 않는 것이다.
표층만 고치면 다음에 다른 마켓이 필드를 추가할 때 또 멈추고 심층만 고치면 지금 막힌 것이 안 풀린다. 여기에 구조적인 세 번째가 있다. 파싱에 실패한 메시지를 별도 큐로 보내고 파티션은 계속 전진시키는 것이다. 앞의 둘은 이번 사건을 고치고 세 번째는 다음 사건의 크기를 줄인다.
공유 자원의 실패 공유
이 사건이 남긴 것은 공유 컨슈머가 실패도 공유한다는 점이다. 채널별로 나눠 놓지 않으면 한 채널의 외부 계약 변경이 전체의 가용성 문제가 된다.
외부 계약은 우리가 통제하지 못하고 예고 없이 필드가 늘거나 값이 바뀐다. 그러므로 통제하지 못하는 변화가 어디까지 번지는지를 구조로 제한해야 한다. 미지 필드를 관용하고 파싱 실패를 격리하고 처리 불가한 상태에 종결 경로를 두는 것 셋이 전부 실패가 다음으로 안 번지게 하는 장치다.
정리
- 제보자의 범위가 장애의 범위가 아니므로 전체를 먼저 센다
- 프로세스가 살아 있고 지표가 정상이어도 메시지만 안 나가는 정지가 있다
- 파싱 실패 한 건이 파티션 전체를 막는다
- 공유 컨슈머면 무관한 대상까지 함께 멈춘다
- 외부 계약 변경에 대비해 미지 필드를 관용한다
- 커밋 날짜와 발효 날짜를 구분한다
- 통과만 하고 상태를 못 바꾸는 처리기는 영구 잔류를 만든다
- 표층과 심층과 구조를 나눠 보고 셋째로 다음의 크기를 줄인다