lifecarelog
백엔드

이벤트 필드 하나 바꿨더니 소비자가 조용히 죽었어요

발행 로그는 전부 성공인데 소비자가 멈춰 있던 이유와, payload 스키마를 바꾸기 전에 확인해야 할 인벤토리 절차를 정리했어요.

7분 읽기

발행 로그만 보면 완벽했어요. 전부 200, 전부 success. 그런데 실제로는 아무 일도 일어나지 않았습니다.

발단은 필드 이름 하나

이벤트 payload에서 member_status 를 status 로 줄였어요. 발행하는 쪽 코드는 깔끔해졌고, 테스트도 통과했고, 발행 로그에도 문제가 없었습니다.

문제는 그 이벤트를 읽는 쪽이었어요. 소비자는 member_status 를 찾다가 undefined 를 받고, 조건문에서 아무 분기에도 걸리지 않고, 예외도 없이 그냥 끝났습니다. 에러를 던지지 않으니 재시도도 안 걸리고, 모니터링에도 안 잡혔어요. 직접 소비자 로그를 열어 보고 나서야 "받긴 받았는데 아무것도 안 했다"는 걸 알았습니다.

발행 성공(200)소비자 수신필드 미스매치분기 통과 실패조용한 종료

발행 성공은 흐름 성공이 아니에요

여기가 핵심이에요. 발행 로그의 success는 "메시지를 큐에 넣었다"는 뜻이지, "누군가 제대로 처리했다"는 뜻이 아닙니다. 이 둘을 같은 걸로 착각하면 대시보드는 초록색인데 사용자 화면은 멈춰 있는 상태가 됩니다.

특히 위험한 조합이 세 가지 있어요.

  • 필드명 변경
  • status 값 추가
  • optional 필드의 필수화

세 경우 모두 소비자가 예외를 안 던지고 조용히 빠져나갈 수 있습니다. 타입 검증이 없는 JSON 경계라면 더 그렇고요.

바꾸기 전에 인벤토리부터

제가 지금 쓰는 순서는 이래요. 4단계면 끝납니다.

  1. 소비자를 먼저 센다 — 이 이벤트를 읽는 코드가 몇 곳인지 검색으로 확인해요. 저는 코드 그래프 인덱스로 한 번에 훑는데, grep 루프로도 됩니다. 중요한 건 "몇 개"라는 숫자를 손에 쥐는 거예요.
  2. 각 소비자의 기대 payload를 적는다 — 어떤 필드를 읽고, 어떤 값을 기대하는지 한 줄씩. 3~4개 넘어가면 표로 만들어 두는 게 편했어요.
  3. 양쪽 호환 기간을 둔다 — 새 필드를 추가하면서 옛 필드도 함께 보내는 구간을 만듭니다. 소비자가 전부 넘어온 걸 확인한 다음에 옛 필드를 지워요.
  4. 최종 상태까지 확인한다 — 발행 로그가 아니라, 사용자나 운영자가 보는 화면까지 가서 값이 바뀌었는지 눈으로 봅니다.

4번을 건너뛰면 이 글의 사고가 그대로 재현돼요. 저도 이번에 그래서 당했습니다.

unknown 레인을 만들어 두세요

가장 효과가 컸던 대비책은 의외로 단순했어요. 소비자 쪽 분기에 "어디에도 안 걸린 경우"를 명시적으로 만들고 로그를 남기는 것입니다.

switch (event.status) {
  case "active":
    return handleActive(event);
  case "paused":
    return handlePaused(event);
  default:
    // 조용히 버리지 말고 남긴다
    logger.warn("unknown event status", {
      status: event.status,
      eventId: event.id,
    });
    return;
}

default 에서 그냥 return 만 하면 그게 바로 조용한 죽음이에요. 한 줄 로그를 추가해 놓고 테스트해 보면, 필드가 안 맞을 때 즉시 보입니다. 저는 이걸 넣은 뒤로 비슷한 미스매치를 두 번 더 잡았어요.

여기에 하나 더 얹으면, 발행 시점에 스키마 검증을 걸어 두는 것도 좋아요. 런타임 스키마로 payload를 한 번 통과시키면 필드명 오타 같은 건 발행 전에 걸립니다.

경계를 넘는 데이터는 계약이에요

같은 레포 안의 함수 호출이면 타입 체커가 잡아 줘요. 그런데 이벤트나 webhook으로 프로세스 경계를 넘어가는 순간, 그 계약을 지켜 주는 건 아무도 없습니다. 컴파일러도, 테스트도, 발행 로그도요.

그래서 저는 요즘 이런 변경을 DB 스키마 변경과 같은 무게로 다룹니다. 플랜비(plan-b)에서 회비 상태 변경 이벤트를 다룰 때도 마찬가지예요. 출전 로테이션이 회비 상태를 읽는 구조라, 필드 하나 바꾸려면 로테이션 쪽 코드를 먼저 열어 봅니다. 귀찮지만 이게 제일 싸요.

같은 프로세스 안의 함수 호출 vs 프로세스 경계를 넘는 이벤트 계약

정리하면 한 문장이에요. 발행 로그의 초록색을 믿지 말고, 실제로 값이 바뀐 화면을 보세요. 대시보드와 현장이 충돌하면 현장이 맞습니다.

#이벤트기반아키텍처#webhook#백엔드

라이프케어로그 서비스가 궁금하신가요?

AI 기반 건강·일정·재활 관리 앱을 직접 써보세요.

서비스 살펴보기

관련 글

댓글

아직 댓글이 없어요. 첫 댓글을 남겨주세요.