웹후크용 Go vs Node.js: 대량 이벤트에 맞는 선택
웹후크용 Go vs Node.js: 동시성, 처리량, 런타임 비용과 오류 처리를 비교해 이벤트 기반 통합을 안정적으로 운영하세요.

실제로 웹후크가 많은 통합은 어떤 모습일까
웹후크가 많은 시스템은 단순한 콜백 몇 개가 아니다. 애플리케이션이 지속적으로, 종종 예측할 수 없는 파도로 호출되는 통합이다. 분당 20건은 괜찮다가도 배치 작업이 끝나거나 결제 공급자가 전달을 재시도하거나 백로그가 풀리면 분당 5,000건을 볼 수 있다.
일반적인 웹후크 요청은 작지만 그 뒤에 필요한 작업은 작지 않을 때가 많다. 하나의 이벤트가 서명 검증, 데이터베이스 조회 및 갱신, 서드파티 API 호출, 사용자 알림을 의미할 수 있다. 각 단계가 지연을 더하고 버스트는 빠르게 쌓인다.
가장 흔한 장애는 스파이크 동안 발생하는 지루한 원인들 때문이다: 요청이 대기열에 쌓이고 워커가 바닥나며 상류 시스템이 타임아웃을 내리고 재시도한다. 재시도는 전달을 돕지만 트래픽을 곱하기도 한다. 짧은 느려짐이 루프가 되는 상황이 흔하다: 재시도가 늘어나면 로드가 증가하고, 그로 인해 더 많은 재시도가 발생한다.
목표는 명확하다: 발신자가 재시도를 멈추도록 빠르게 인정(ack)하고, 이벤트를 버리지 않고 흡수할 만큼의 처리량을 유지하며, 드문 피크 때문에 매일 과도한 비용을 지불하지 않도록 비용을 예측 가능하게 만드는 것이다.
일반적인 웹후크 소스는 결제, CRM, 지원 도구, 메시지 전달 업데이트, 내부 관리자 시스템 등이 있다.
동시성 기초: 고루틴(goroutine) vs Node.js 이벤트 루프
웹후크 핸들러는 단순해 보이지만 한 번에 5,000건이 들어오면 달라진다. 웹후크용 Go vs Node.js를 비교할 때 동시성 모델이 압박 상황에서 시스템이 응답성을 유지할지 여부를 결정하는 경우가 많다.
Go는 고루틴을 사용한다. 고루틴은 Go 런타임이 관리하는 경량 스레드다. 많은 서버가 사실상 요청당 고루틴을 실행하며 스케줄러가 CPU 코어에 작업을 분배한다. 채널(Channels)은 고루틴 간 안전하게 작업을 전달하기 자연스러워 워커 풀, 속도 제한, 역압력(backpressure)을 설계할 때 도움이 된다.
Node.js는 단일 스레드 이벤트 루프를 사용한다. 핸들러가 주로 I/O(데이터베이스 호출, 다른 서비스로의 HTTP 요청, 큐 등)를 기다리는 상황에서는 강력하다. 비동기 코드는 메인 스레드를 차단하지 않으면서 많은 요청을 동시에 처리할 수 있게 한다. 병렬 CPU 작업이 필요하면 보통 워커 스레드를 추가하거나 여러 Node 프로세스를 실행한다.
CPU 집약적인 단계가 있으면 상황이 빨리 달라진다: 서명 검증(암호화), 큰 JSON 파싱, 압축, 또는 복잡한 변환 등. Go에서는 이런 CPU 작업을 코어 간 병렬로 실행할 수 있다. Node에서는 CPU 바운드 코드는 이벤트 루프를 막아 다른 모든 요청을 느리게 한다.
실용적인 경험칙:
- 대부분 I/O 바운드: Node는 종종 효율적이고 수평 확장에 적합하다.
- I/O와 CPU가 혼합된 경우: Go가 부하하에서 빠름을 유지하기 더 쉽다.
- 매우 CPU 집약적: Go가 유리하며, Node를 택한다면 워커를 추가하거나 병렬성을 초기에 계획하라.
버스트성 웹후크 트래픽에서 처리량과 지연
성능 논의에서 자주 섞이는 두 숫자가 있다. 처리량(throughput)은 초당 완료하는 이벤트 수다. 지연(latency)은 요청 수신부터 2xx 응답까지 한 이벤트가 걸리는 시간이다. 버스트 상황에서는 평균 처리량이 좋아도 최악 지연(테일 레이턴시)이 고통스러울 수 있다(가장 느린 1-5% 요청).
스파이크는 보통 느린 부분에서 실패한다. 핸들러가 데이터베이스, 결제 API, 내부 서비스에 의존하면 그 종속성이 속도를 정한다. 핵심은 역압력이다: 다운스트림이 들어오는 웹후크보다 느릴 때 무슨 일이 일어나는지 결정하는 것.
실무에서는 역압력이 보통 몇 가지 아이디어를 조합한 형태다: 빠르게 인정하고 실제 작업은 나중에 처리하기, 데이터베이스 연결을 소진하지 않도록 동시성 상한 두기, 촘촘한 타임아웃 적용, 정말 감당할 수 없을 때는 명확한 429/503 응답 반환.
연결 처리 방식은 예상보다 중요하다. Keep-alive는 클라이언트가 연결을 재사용하게 해 핸드셰이크 오버헤드를 줄여 버스트 시 성능을 높인다. Node.js에서는 아웃바운드 Keep-alive를 의도적으로 HTTP 에이전트를 사용해서 설정해야 하는 경우가 많다. Go에서는 기본적으로 Keep-alive가 켜져 있는 편이지만, 느린 클라이언트가 소켓을 영원히 점유하지 않도록 합리적인 서버 타임아웃이 필요하다.
비용이 큰 부분이 호출당 오버헤드일 때 배치(batch)는 처리량을 높일 수 있다(예: 한 번에 한 행씩 쓰는 대신 묶어 쓰기). 다만 배칭은 지연을 늘리고 재시도를 복잡하게 만들 수 있다. 흔한 절충안은 마이크로 배칭이다: 느린 다운스트림 단계에 대해서만 50–200ms 정도의 짧은 윈도우로 이벤트를 묶는 것이다.
워커를 더 늘리는 것은 데이터베이스 풀, CPU, 락 경쟁 같은 공유 한계에 도달할 때까지 도움이 된다. 그 지점을 지나면 동시성을 늘릴수록 큐 대기 시간과 테일 레이턴시가 증가한다.
런타임 오버헤드와 스케일링 비용 실전
사람들이 “Go가 운영비가 싸다”거나 “Node.js도 잘 확장된다”라고 말할 때, 보통 같은 맥락을 이야기한다: 버스트를 버티기 위해 필요한 CPU와 메모리, 안전하게 두어야 할 인스턴스 수다.
메모리와 컨테이너 사이징
Node.js는 각 인스턴스가 전체 자바스크립트 런타임과 관리되는 힙을 포함하므로 프로세스당 기본 메모리 요구량이 더 큰 편이다. Go 서비스는 보통 작게 시작하고 같은 머신에 더 많은 레플리카를 넣기 쉽다. 특히 각 요청이 주로 I/O이고 짧은 경우에 그렇다.
이 점은 컨테이너 사이징에서 빠르게 드러난다. 하나의 Node 프로세스가 힙 압력을 피하려면 더 큰 메모리 제한이 필요해 노드당 실행 가능한 컨테이너 수가 줄 수 있다. Go는 하나의 프로세스 안에서 많은 동시 작업을 처리하기 쉬워 같은 하드웨어에서 더 적은 노드로 운영할 수 있다.
콜드 스타트, GC, 그리고 필요한 인스턴스 수
오토스케일링은 단순히 “시작할 수 있느냐”가 아니라 “시작해서 빠르게 안정화될 수 있느냐”다. Go 바이너리는 보통 빠르게 시작하고 워밍업이 크게 필요하지 않다. Node도 빠르게 시작하지만 실제 서비스는 모듈 로딩, 커넥션 풀 초기화 같은 부트 작업을 많이 하여 콜드 스타트가 덜 예측 가능해질 수 있다.
가비지 컬렉션(GC)은 버스트성 웹후크 트래픽에서 중요하다. 두 런타임 모두 GC가 있지만 고통의 양상은 다르다:
- Node는 힙이 커지고 GC가 자주 돌 때 지연이 증가할 수 있다.
- Go는 일반적으로 지연을 더 일정하게 유지하지만, 이벤트당 할당이 많으면 메모리가 상승할 수 있다.
양쪽 모두에서 할당을 줄이고 객체를 재사용하는 것이 끝없는 플래그 튜닝보다 효과적이다.
운영 관점에서 오버헤드는 인스턴스 수로 귀결된다. 처리량을 얻기 위해 머신당 여러 Node 프로세스(또는 코어당 프로세스)를 띄워야 하면 메모리 오버헤드가 곱해진다. Go는 하나의 프로세스 안에서 많은 동시 작업을 처리할 수 있어 동일한 웹후크 동시성으로 더 적은 인스턴스로 운영할 수 있다.
웹후크용 Go vs Node.js를 결정할 때는 평균 CPU만 보지 말고 피크에서 1,000건당 비용을 측정하라.
신뢰성 있는 웹후크를 위한 오류 처리 패턴
웹후크 신뢰성은 대부분 문제가 생겼을 때 어떻게 대응하느냐에 달려 있다: 느린 다운스트림 API, 짧은 중단, 그리고 정상 범위를 벗어난 버스트들.
먼저 타임아웃부터 시작하라. 인바운드 웹후크에는 짧은 요청 데드라인을 정해 이미 포기한 클라이언트 때문에 워커가 묶이지 않게 하라. 이벤트 처리 중에 호출하는 아웃바운드(데이터베이스 쓰기, 결제 조회, CRM 업데이트)는 더 촘촘한 타임아웃을 두고 별도의 측정 가능한 단계로 취급하라. 실무 규칙은 인바운드 요청은 몇 초 이내, 각 다운스트림 호출은 진짜로 더 필요하지 않다면 1초 이내로 유지하는 것이다.
다음은 재시도다. 네트워크 타임아웃, 연결 재설정, 많은 5xx 응답처럼 일시적일 가능성이 높은 경우에만 재시도하라. 페이로드가 잘못됐거나 다운스트림에서 명확한 4xx가 왔으면 빨리 실패하고 이유를 기록하라.
지터를 섞은 백오프(backoff with jitter)는 재시도 폭주(storm)를 막는다. 다운스트림 API가 503을 반환하면 즉시 재시도하지 말라. 200ms, 400ms, 800ms처럼 기다리고 ±20% 정도의 무작위 지터를 더해 재시도를 분산시켜 최악의 순간에 종속성을 몰아치지 않게 한다.
이벤트가 중요하고 실패를 잃을 수 없을 때는 데드 레터 큐(DLQ)를 도입할 가치가 있다. 정의한 횟수만큼 시도한 뒤 실패하면 페이로드와 오류 세부사항을 DLQ로 옮겨 나중에 안전하게 재처리할 수 있게 하라. 이렇게 하면 새로운 트래픽을 막지 않으면서 문제를 해결할 수 있다.
사건을 디버그하기 쉽게 하려면 이벤트 전반에 걸쳐 따라다니는 상관 ID(correlation ID)를 사용하라. 수신 시 로깅하고 모든 재시도와 다운스트림 호출에 포함하라. 또한 시도 번호, 사용된 타임아웃, 최종 결과(ack, retried, DLQ), 그리고 중복을 매칭할 최소한의 페이로드 지문을 기록하라.
멱등성, 중복, 순서 보장
공급자는 생각보다 자주 이벤트를 다시 보낸다. 타임아웃, 500 오류, 네트워크 드롭, 느린 응답 때문에 재시도한다. 일부 공급자는 마이그레이션 중 동일한 이벤트를 여러 엔드포인트로 보내기도 한다. Go vs Node.js 여부와 무관하게 중복을 가정하라.
멱등성(idempotency)은 같은 이벤트를 두 번 처리해도 올바른 결과가 나오게 하는 것이다. 흔한 도구는 공급자의 이벤트 ID 같은 멱등성 키다. 이를 영구 저장소에 보관하고 어떤 부작용을 실행하기 전에 확인한다.
실용적 멱등성 레시피
간단한 접근법은 공급자 이벤트 ID를 키로 하는 테이블을 영수증처럼 사용하는 것이다: 이벤트 ID, 수신 타임스탬프, 상태(processing, done, failed), 그리고 짧은 결과나 참조 ID를 저장하라. 먼저 확인하라. 이미 완료로 표시돼 있으면 200을 빠르게 반환하고 부작용을 건너뛰라. 작업을 시작할 때는 두 워커가 동일 이벤트를 동시에 처리하지 않도록 processing으로 표시하라. 최종 부작용이 성공한 뒤에만 done으로 표시하라. 키 보관 기간은 공급자의 재시도 윈도우를 커버할 만큼 길게 유지하라.
이렇게 하면 이중 청구와 중복 레코드를 피할 수 있다. "payment_succeeded" 웹후크가 두 번 도착해도 인보이스는 적어도 한 건만 생성되고 "paid" 전환은 한 번만 적용돼야 한다.
순서 보장은 더 어렵다. 많은 공급자가 특히 부하 상황에서 전달 순서를 보장하지 않는다. 타임스탬프가 있어도 "updated"가 "created"보다 먼저 올 수 있다. 각 이벤트를 안전하게 적용할 수 있게 설계하거나 최신 버전을 저장하고 오래된 것은 무시하라.
부분 실패도 흔한 골칫거리다: 1단계(DB 쓰기)는 성공했지만 2단계(이메일 전송)가 실패하는 경우. 각 단계를 추적하고 재시도가 안전하도록 설계하라. 흔한 패턴은 먼저 이벤트를 기록하고 후속 작업을 큐에 넣는 것이다. 이렇게 하면 재시도가 누락된 부분만 다시 수행한다.
단계별: 내 워크로드에 맞춰 Go vs Node.js를 평가하는 방법
공정한 비교는 실제 워크로드에서 시작한다. "대량"이란 많은 작은 이벤트일 수도 있고, 몇 개의 큰 페이로드일 수도 있으며, 평상시 속도에 느린 다운스트림 호출이 섞인 경우일 수도 있다.
숫자로 워크로드를 설명하라: 분당 예상 최대 이벤트 수, 평균 및 최대 페이로드 크기, 각 웹후크가 수행해야 할 작업(데이터베이스 쓰기, API 호출, 파일 저장, 메시지 전송). 발신자가 요구하는 엄격한 시간 제한이 있다면 적어라.
"좋음"의 기준을 미리 정의하라. 유용한 측정 항목은 p95 처리 시간, 오류율(타임아웃 포함), 버스트 동안 백로그 크기, 목표 규모에서 1,000건당 비용이다.
재현 가능한 재생(replay) 테스트 스트림을 만들어라. 실제 웹후크 페이로드(비밀은 제거)를 저장하고 시나리오를 고정해 변경 후 테스트를 다시 실행할 수 있게 하라. 꾸준한 트래픽만이 아니라 버스트성 부하 테스트를 사용하라. "2분 조용, 그다음 30초 동안 10배 트래픽" 같은 시나리오는 실제 장애가 시작되는 방식과 가깝다.
간단한 평가 흐름:
- 종속성을 모델링하라(무엇을 인라인으로 실행해야 하고 무엇을 큐로 보낼 수 있는지)
- 지연, 오류, 백로그에 대한 성공 임계값을 설정하라
- 동일한 페이로드 세트를 두 런타임에서 재생하라
- 버스트, 느린 다운스트림 응답, 가끔의 실패를 테스트하라
- 실제 병목 지점(동시성 한계, 큐잉, DB 튜닝, 재시도)을 고쳐라
예시 시나리오: 트래픽 스파이크 동안의 결제 웹후크
흔한 설정은 다음과 같다: 결제 웹후크가 도착하면 시스템은 빠르게 세 가지를 해야 한다 — 영수증 이메일 발송, CRM의 연락처 업데이트, 고객의 지원 티켓 태그 추가.
평상시에는 분당 5–10건을 받을 수 있다. 그런데 마케팅 이메일이 발송되어 트래픽이 20분 동안 분당 200–400건으로 뛰는 경우가 있다. 웹후크 엔드포인트는 여전히 "하나의 URL"이지만 그 뒤의 작업은 곱절로 늘어난다.
약한 고리가 CRM API라 해보자. 평소 200ms였던 응답이 5–10초로 느려지고 가끔 타임아웃이 난다면 문제가 생긴다. 핸들러가 CRM 호출을 기다렸다가 응답을 반환하면 요청이 쌓인다. 곧 느려지는 것뿐 아니라 웹후크 실패와 백로그가 생긴다.
Go에서는 팀이 흔히 "웹후크 수신(accept)"과 "작업 수행(do the work)"을 분리한다. 핸들러가 이벤트를 검증하고 작은 작업 레코드를 쓰고 빠르게 반환한다. 워커 풀은 병렬로 작업을 처리하며 고정 한계(예: 워커 50개)를 둔다. 그래서 CRM 지연이 고루틴이나 메모리의 무한 성장을 일으키지 않는다. CRM이 불안하면 동시성을 낮춰 시스템을 안정화할 수 있다.
Node.js에서도 같은 설계를 쓸 수 있지만 한 번에 시작하는 비동기 작업량을 신중히 결정해야 한다. 이벤트 루프는 많은 연결을 처리할 수 있지만 수천 개의 프로미스를 한꺼번에 발사하면 CRM이나 자체 프로세스를 압도할 수 있다. Node 설정은 보통 명시적 속도 제한과 큐를 추가해 작업 속도를 조절한다.
이것이 실제 시험이다: "한 요청을 처리할 수 있느냐"가 아니라 "종속성이 느려졌을 때 무슨 일이 일어나느냐"다.
웹후크 장애를 일으키는 흔한 실수
대부분의 웹후크 장애는 언어 자체 때문이 아니다. 핸들러 주변의 시스템이 약하고 작은 스파이크나 상류 변경이 폭주로 이어지기 때문에 일어난다.
흔한 함정은 HTTP 엔드포인트를 전체 솔루션으로 취급하는 것이다. 엔드포인트는 단지 현관문일 뿐이다. 이벤트를 안전하게 저장하고 처리 방식에 통제를 두지 않으면 데이터 손실이나 자체 서비스 과부하가 발생한다.
자주 반복되는 실패 사례:
- 내구성 있는 버퍼링 없음: 작업이 즉시 시작되고 큐나 영구 저장소가 없어서 재시작이나 느려짐에 이벤트를 잃음.
- 제한 없는 재시도: 실패가 즉시 재시도를 트리거해 떼거리 요청(thundering herd)을 만듦.
- 요청 내부에 무거운 작업: 비싼 CPU 작업이나 대량 팬아웃이 핸들러 안에서 실행되어 용량을 막음.
- 약하거나 일관성 없는 서명 검증: 검증을 건너뛰거나 너무 늦게 수행함.
- 스키마 변경에 대한 책임자 없음: 페이로드 필드가 버전 관리 없이 변경됨.
자신을 보호하는 간단한 규칙: 빠르게 응답하고 이벤트를 저장하며 제어된 동시성과 백오프 방식으로 별도 처리하라.
런타임을 선택하기 전에 빠른 체크리스트
벤치마크를 비교하기 전에 당신의 웹후크 시스템이 문제가 생겼을 때 안전한지 확인하라. 아래 항목들이 지켜지지 않으면 성능 튜닝이 문제를 해결하지 못한다.
- 멱등성이 실제로 구현되어 있는가: 모든 핸들러가 중복을 견디고 이벤트 ID를 저장하며 반복을 거부하고 부작용이 한 번만 일어나도록 보장하는가?
- 다운스트림이 느릴 때 들어오는 웹후크가 메모리에 쌓이지 않도록 버퍼가 있는가?
- 타임아웃, 재시도, 지터가 정의되어 있고 실패 모드 테스트(스테이징 종속성이 느리게 응답하거나 500을 반환하는 경우 포함)가 되어 있는가?
- 저장된 원시 페이로드와 헤더로 이벤트를 재생할 수 있는가?
- 각 웹후크에 대해 추적 또는 상관 ID, 그리고 비율, 지연, 실패, 재시도에 대한 기본 관측성이 있는가?
구체적 예: 공급자가 엔드포인트가 타임아웃했다며 동일한 웹후크를 세 번 재전송하면 멱등성과 재생이 없을 때 티켓이 세 개 생성되거나 환불이 세 번 처리될 수 있다.
다음 단계: 결정을 내리고 작은 파일럿을 만들어라
선호도에서 시작하지 말고 제약에서 시작하라. 팀 기술은 원시 속도만큼 중요하다. 팀이 JavaScript에 강하고 이미 Node.js를 프로덕션에서 운영 중이면 위험이 줄어든다. 반대로 낮고 예측 가능한 지연과 단순한 스케일링이 최우선이라면 Go가 부하하에서 더 안정적으로 느껴질 수 있다.
코드 작성 전에 서비스 형태를 정의하라. Go에서는 보통 빠르게 검증하고 응답하는 HTTP 핸들러, 무거운 작업을 위한 워커 풀, 필요한 경우 그 사이에 버퍼(큐)를 두는 구조가 많다. Node.js에서는 비동기 파이프라인을 빠르게 반환하고 느린 호출과 재시도를 처리하기 위해 백그라운드 워커(또는 별도 프로세스)를 둔다.
안전하게 실패할 수 있는 파일럿을 계획하라. 빈번한 웹후크 유형 하나를 선택하라(예: "payment_succeeded" 또는 "ticket_created"). 측정 가능한 SLO를 정의하라: 예를 들면 99%는 200ms 이내에 ack, 99.9%는 60초 이내에 처리 완료 등. 처음부터 재생(replay) 지원을 만들어 버그 수정 후 공급자에게 재전송을 요청하지 않고 다시 처리할 수 있어야 한다.
파일럿을 단순하게 유지하라: 웹후크 하나, 다운스트림 시스템 하나, 데이터 저장소 하나; 모든 시도에 대해 요청 ID, 이벤트 ID, 결과를 로깅; 재시도와 데드레터 경로 정의; 큐 깊이, ack 지연, 처리 지연, 오류율 추적; 그런 다음 버스트 테스트(예: 정상 트래픽의 10배를 5분 동안) 실행.
직접 모든 것을 새로 작성하지 않고 워크플로를 프로토타입하고 싶다면 AppMaster (appmaster.io)가 이런 파일럿에 유용할 수 있다: PostgreSQL에 데이터를 모델링하고, 웹후크 처리를 시각적 비즈니스 프로세스로 정의해 프로덕션 준비된 백엔드를 생성해 클라우드에 배포할 수 있다.
SLO와 운영상의 편안함을 기준으로 결과를 비교하라. 새벽 2시에 자신 있게 운영하고 디버그하고 변경할 수 있는 런타임과 설계를 선택하라.
자주 묻는 질문
버스트와 재시도를 염두에 두고 설계하세요. 빠르게 응답(ack)하고 이벤트를 내구성 있게 저장한 뒤, 제어된 동시성으로 처리해 느린 종속성 때문에 엔드포인트가 막히지 않도록 하세요.
이벤트를 검증하고 안전하게 기록한 즉시 성공 응답을 반환하세요. 무거운 작업은 백그라운드에서 처리하면 공급자가 재시도할 가능성이 줄고, 급증 시에도 엔드포인트가 응답성을 유지합니다.
Go는 CPU 집약적인 작업을 코어 간 병렬로 처리해 다른 요청을 블로킹하지 않는 점에서 유리합니다. Node는 I/O 대기 상황에서 많은 동시 요청을 효율적으로 처리하지만, CPU 바운드 작업은 이벤트 루프를 막을 수 있어 워커나 프로세스 분리가 필요합니다.
핸들러가 주로 I/O 위주이고 CPU 작업이 적을 때 Node.js가 잘 맞습니다. 팀이 JavaScript에 익숙하고 타임아웃, 연결 유지(keep-alive), 급증 시 무제한 비동기 작업을 시작하지 않는 규율이 있다면 좋은 선택입니다.
처리량(throughput)은 초당 완료하는 이벤트 수이고, 지연(latency)은 요청 수신부터 2xx 응답까지 걸리는 시간입니다. 버스트 상황에서는 최악의 소수(테일 레이턴시)가 중요합니다. 느린 소수 요청이 공급자 타임아웃과 재시도를 촉발합니다.
데이터베이스와 다운스트림 API를 보호하기 위해 동시성을 제한하고, 모든 것을 메모리에 쌓지 않도록 버퍼링을 추가하세요. 과부하 시에는 타임아웃 대신 429나 503을 명확히 반환해 불필요한 재시도를 막는 것이 좋습니다.
중복은 정상이라고 보고 공급자 이벤트 ID 같은 멱등성 키를 저장한 뒤 부작용을 수행하세요. 이미 처리된 이벤트면 200을 반환하고 작업을 건너뛰어 중복 청구나 중복 레코드를 막습니다.
짧고 명시적인 타임아웃을 사용하고, 네트워크 타임아웃이나 많은 5xx 같은 일시적 실패에 대해서만 재시도하세요. 지수 백오프에 재시도 간 무작위 지터(jitter)를 추가해 재시도 폭주를 막습니다.
이벤트를 잃을 수 없는 경우 데드 레터 큐(DLQ)를 도입하세요. 정의한 횟수만큼 시도한 뒤 실패하면 페이로드와 오류 정보를 DLQ로 옮겨 후속 재처리를 할 수 있게 합니다.
동일한 저장된 페이로드를 양쪽 구현에 재생(replay)해 버스트 테스트와 느린 종속성 상황을 포함한 시험을 실행하세요. 응답 지연, 처리 지연, 백로그 증가, 오류율, 피크당 1,000건당 비용을 비교하세요. 평균만 보지 마세요.


