만드는 기록/서비스 운영기

같은 웹훅이 두 번 왔을 때 알림과 처리를 중복하지 않는 설계

Roslyn 2026. 11. 15. 08:00
반응형

외부 서비스의 이벤트를 받아 자료 이용 권한을 열고 알림을 보내는 기능을 만든다고 해 보겠습니다. 한 번의 요청으로는 잘 작동해도 같은 이벤트가 다시 오면 권한 기간이 두 번 늘거나 같은 안내가 반복될 수 있습니다. 잘못된 사용자의 공격이 없어도 정상 공급자의 재전송만으로 생길 수 있는 문제입니다.

이때 필요한 것은 모든 중복 요청을 오류로 돌려보내는 규칙보다, 어디까지 처리했는지 판단할 기록입니다. 작은 서비스를 운영하는 관점에서 수신, 업무 변경, 후속 알림을 나눠 보겠습니다. 아래 내용은 2026년 11월 1일 확인한 공식 문서와 가상 사례를 바탕으로 한 설계 검토이며 실제 장애 해결이나 운영 검증의 기록은 아닙니다.

같은 이벤트와 같은 업무를 먼저 구분합니다

Stripe 웹훅 문서는 같은 이벤트를 여러 번 받을 수 있으므로 처리한 이벤트 ID를 기록하도록 안내합니다. 별도의 Event 객체가 중복 생성되는 경우에는 대상 객체 ID와 이벤트 유형을 함께 고려하라고 설명합니다. 이 글에서 Stripe는 전달 정책의 사례이며, 국내 서비스에 특정 결제 수단을 도입하라는 제안은 아닙니다.

이벤트 ID는 공급자가 보낸 알림을 구분하는 키이고, 내 서비스의 업무 키는 실제 변경할 대상을 구분합니다. 가상의 자료 주문에 대한 이벤트 두 개가 서로 다른 ID를 갖더라도 같은 이용 권한을 열려는 요청일 수 있습니다. 반대로 한 대상에서 같은 유형의 변경이 여러 번 정당하게 일어날 수도 있으므로 대상 ID와 유형만으로 모든 후속 이벤트를 영구 차단해서는 안 됩니다.

먼저 내가 막으려는 부작용을 문장으로 적습니다. “동일 주문의 자료 이용 권한은 한 번만 생성한다”는 기준과 “같은 이벤트의 처리 코드는 한 번만 시작한다”는 기준은 다릅니다. 전자는 업무 결과를 지키는 규칙이고 후자는 전달 중복을 다루는 규칙입니다. 두 기준을 연결해야 이벤트 ID가 달라지는 경우에도 업무의 중복을 판단할 수 있습니다.

수신 기록에는 공급자, 계정이나 환경의 범위, 이벤트 ID, 이벤트 유형, 업무 대상, 현재 상태를 남길 수 있습니다. 키의 유일성 범위는 공급자의 계약에 맞춰야 합니다. 테스트와 운영 데이터를 같은 키 공간에 넣는다면 구분이 필요한지도 확인합니다. 실제 비밀값이나 불필요한 고객 정보를 중복 검사 기록에 넣을 이유는 없습니다.

받았다는 표시와 끝났다는 표시를 나눕니다

수신하자마자 처리 완료라고 적으면 그 직후 프로세스가 멈췄을 때 실제 업무가 빠질 수 있습니다. 반대로 모든 작업이 끝난 다음에만 기록하면 작업 도중 다시 들어온 요청이 같은 일을 시작할 수 있습니다. 상태 하나에 접수와 성공을 함께 담으면 실패가 발생한 위치를 알기 어렵습니다.

예를 들어 접수, 처리 중, 완료, 재시도 대기라는 상태를 설계할 수 있습니다. 이 이름은 정답 목록이 아니라 다음 행동을 구분하기 위한 예시입니다. 접수는 요청을 내구성 있게 보관했다는 뜻으로, 완료는 정해 둔 업무 변경이 반영됐다는 뜻으로 정의합니다. 알림까지 완료에 포함할지는 별도 기준으로 정해야 합니다.

  • 처음 받은 이벤트: 신뢰 여부를 검증한 뒤 중복 키와 함께 저장합니다.
  • 이미 완료된 이벤트: 업무 변경을 반복하지 않고 공급자의 규칙에 맞게 응답합니다.
  • 처리 중인 이벤트: 또 실행하기 전에 작업 소유권과 중단 여부를 확인합니다.
  • 실패한 이벤트: 재시도 가능 여부와 다음 시도를 맡을 주체를 기록합니다.

동시에 두 요청이 오면 둘 다 “아직 기록이 없다”고 조회할 수 있습니다. 조회 후 삽입하는 코드만으로는 이런 경쟁을 막기 어렵습니다. 저장소의 유일성 제약이나 원자적인 상태 전환으로 한 요청만 작업을 맡도록 설계하고, 충돌한 요청은 기존 상태를 읽는 흐름을 검토해야 합니다. 구체적인 구현은 사용하는 데이터베이스의 동시성 동작에 맞춰 확인합니다.

처리 중 상태를 무한히 유지해서도 안 됩니다. 작업자가 멈춘 경우를 찾을 시각과 재개 조건이 필요합니다. 단순히 시간이 지났다고 새 작업자를 시작하면 기존 작업자가 늦게 살아나 같은 일을 할 수 있으므로, 소유권 갱신이나 조건부 변경처럼 오래된 작업자의 쓰기를 제한할 방법도 함께 검토합니다.

업무 변경과 알림 사이의 빈틈을 줄입니다

가상의 자료 제공 서비스에서 권한 저장은 성공했지만 알림 전송 전에 서버가 멈췄다고 해 보겠습니다. 이벤트 전체를 다시 실행하면 권한을 또 만들 수 있고, 무조건 건너뛰면 알림이 빠집니다. 권한 변경의 완료와 알림을 보낼 필요가 있다는 기록을 함께 남겨야 이 둘을 따로 복구할 수 있습니다.

Microsoft Learn의 Transactional Outbox 설명은 업무 데이터와 발행할 이벤트를 같은 데이터베이스 트랜잭션에 저장하는 원리를 다룹니다. 이 문서는 Cosmos DB 구현 사례이지만, 여기서는 두 기록을 함께 확정하고 후속 전송을 분리한다는 개념만 참고합니다. 특정 클라우드의 구현을 그대로 옮겨야 한다는 뜻은 아닙니다.

위 사례에서는 이용 권한과 알림 대기 항목을 같은 트랜잭션으로 저장하는 방식을 검토할 수 있습니다. 별도 작업자는 알림 대기 항목을 읽고 전송한 뒤 상태를 갱신합니다. 업무 변경이 실패하면 알림 대기 기록도 함께 취소되도록 하고, 알림 전송이 실패하면 권한 생성부터 반복하지 않게 경계를 나눕니다.

그러나 이 구조만으로 외부 알림이 반드시 한 번만 도착한다고 보장할 수는 없습니다. 외부 서비스는 알림을 보냈는데 응답을 받기 전에 연결이 끊기면 결과가 불명확해질 수 있습니다. 전송 대상이 중복 방지 키를 지원하는지 확인하고 같은 논리적 알림에 같은 키를 쓰는 정책을 검토합니다. 지원하지 않는 경우에는 결과 조회나 수동 확인이 필요한 상태를 남겨야 합니다.

수신 서버의 빠른 응답도 같은 관점으로 봅니다. Stripe는 서명 검증과 비동기 처리, 복잡한 작업보다 빠른 성공 응답을 안내합니다. 다만 처리할 이벤트를 잃을 수 있는 상태에서 성공 응답부터 보내는 설계는 별개 문제입니다. 어떤 내구성 있는 저장이 끝난 뒤 접수를 인정할지 정하고, 그 저장이 실패한 경우의 응답도 공급자 정책에 맞춰야 합니다.

순서가 바뀌고 중간에 멈추는 경우를 확인합니다

Stripe 문서는 이벤트의 생성 순서대로 전달된다고 보장하지 않습니다. 따라서 앞선 이벤트가 항상 먼저 도착했다는 전제만으로 다음 상태를 계산하면 문제가 생길 수 있습니다. 예를 들어 자료 제공에 필요한 객체가 아직 없으면 그것이 단순 누락인지, 도착 순서의 차이인지 확인할 경로가 필요합니다.

업무마다 현재 상태를 다시 조회할지, 선행 자료가 올 때까지 보류할지, 버전이나 허용된 상태 전환으로 오래된 변경을 거를지 정할 수 있습니다. 모든 이벤트를 최신 상태로 덮어쓰는 방식도 항상 맞지는 않습니다. 과거에 발생한 개별 행위를 보존해야 하는 업무인지, 현재 상태를 맞추는 업무인지 먼저 구별해야 합니다.

검증 시나리오에는 같은 이벤트를 연속으로 보내는 경우뿐 아니라 동시에 도착하는 경우도 넣습니다. 권한 저장 직전과 직후 중단, 알림 전송 성공 후 결과 기록 실패, 앞뒤 이벤트의 순서 변경을 각각 확인합니다. 기대 결과는 “오류가 없다”가 아니라 권한 수, 대기 알림 수, 최종 상태, 남아 있는 재시도 작업처럼 관찰할 수 있는 값으로 적습니다.

보관 기간도 재처리 정책과 연결됩니다. 중복 기록을 너무 일찍 지우면 오래된 이벤트가 다시 들어왔을 때 새 이벤트처럼 처리될 수 있습니다. 반대로 모든 본문을 끝없이 보관하면 운영과 개인정보 관리 부담이 커집니다. 공급자의 재전송 범위와 수동 복구 절차, 업무상 보존 조건을 보고 필요한 식별 기록의 기간을 정합니다.

오늘은 실제로 받을 이벤트 하나를 골라 접수, 업무 변경, 알림 전송 사이에 멈춤 표시를 그려 보시면 좋겠습니다. 각 지점에서 다시 시작할 때 읽을 기록과 반복하면 안 되는 결과를 적으면 설계의 빈틈이 드러납니다. 중복을 없애겠다는 목표보다 같은 요청이 다시 와도 업무 결과를 지키고 실패한 부분만 복구할 수 있는지가 더 구체적인 완료 기준입니다.

반응형