실제 작동하는 분산 시스템의 멱등성

중복된 사이드 이펙트를 방지하세요

Page content

분산 시스템에서 중복성(Idempotency)은 네트워크 오류, 큐 재시도, 클라이언트 패닉, 그리고 운영자가 재실행을 누른 상황에서도 당신을 구워주는 속성입니다. 프로덕션 시스템에서는 중복 전송이 정상적인 현상입니다. 중복된 사이드 이펙트가 바로 버그입니다.

HTTP는 중복성 있는 메서드를 “여러 번 동일한 요청을 보내도 서버에 미치는 의도된 효과가 하나의 요청을 보낼 때와 동일하다"는 방식으로 정의합니다. 따라서 PUT, DELETE 및 안전(Safe) 메서드는 프로토콜 세미틱스에서 중복성이 있으며, 통신 실패 후 자동으로 재시도할 수 있습니다.

integration message flow: idempotency

이 정의는 유용하지만 충분하지 않습니다. 실제 아키텍처에서 중복성은 HTTP의 사소한 지식이 아닌 비즈니스 보증입니다. 고객이 ‘결제’를 한 번 누르면, 커밋과 응답 사이의 타임아웃 때문에 두 번 청구할 수 없습니다. 작업자가 메시지를 확인(Ack)하기 전에 충돌하여 인벤토리를 업데이트하면, 브로커가 메시지를 다시 전달했기 때문에 재고를 두 번 차감할 수 없습니다. 이것이 기준입니다.

내가 반복적으로 보는 실수는 중복성을 전송의 기능으로扱う하는 것입니다. 큐의 중복 제거, HTTP 메서드, 클라이언트 재시도는 도움이 되지만, 동일한 비즈니스 의도가 두 번째 사이드 이펙트를 생성할 수 있는 디자인은 구해줄 수 없습니다. 이러한 통합 결정이 서비스 경계와 지속성 트레이드오프에 어떻게 부합하는지에 대한 더 넓은 관점을 원하시면, App Architecture in Production: Integration Patterns, Code Design, and Data Access를 시작점으로 삼으십시오.

프로덕션에서 중복성이 발생하는 원인

중복성은 팀이 부주의해서 발생하는 것이 아닙니다. 분산 시스템이 재시도하고, 순서를 바꾸며, 재생하기 때문에 발생합니다.

클라이언트가 생성 요청을 보내고 서버가 이를 커밋하더라도 응답이 네트워크를 통해 완전히 도달하지 않을 수 있습니다. 바로 이것이 HTTP가 중복성 있는 메서드를 구분하는 이유이며, Stripe나 PayPal과 같은 결제 API가 POST와 같은 비안전(Unsafe) 메서드에 명시적인 중복성 메커니즘을 제공하는 이유입니다.

메시지 브로커는 문제를 더욱 명확하게 만듭니다. 최소 한 번 이상 적어도 한 번(At-least-once) 전송은 컨슈머가 동일한 메시지에 대해 여러 번 호출될 수 있음을 의미하며, 핸들러가 데이터베이스 업데이트에 성공하지만 확인(Acknowledgment) 전에 실패하면 브로커가 동일한 메시지를 다시 전달하게 됩니다.

웹훅도 마찬가지입니다. GitHub는 웹훅 전송이 순서대로 도착하지 않을 수 있으며, 실패한 전송은 자동으로 다시 전송되지 않으며, 각 전송에는 재생(Replay) 방지 시 사용해야 하는 고유한 X-GitHub-Delivery GUID가 포함된다고 밝히고 있습니다. 모던 시스템에서 채팅 엔드포인트를 상호작용 경계로 보는 실용적인 아키텍처 관점은 Chat Platforms as System Interfaces in Modern Systems에서 확인할 수 있습니다.

더 강력한 보장을 광고하는 시스템조차도 여전히 수행해야 할 작업을 남겨둡니다. Kafka는 중복성 있는 프로듀서를 사용하여 Kafka 로그에 중복 항목을 방지할 수 있으며, 트랜잭션과 read_committed 컨슈머를 사용하여 Kafka 내부에 머무는 읽기-처리-쓰기(Read-process-write) 흐름에 대해 정확히 한 번 전송(Exactly-once delivery)을 제공할 수 있습니다. 하지만 Kafka 자체의 설계 문서에서는 외부 시스템이 여전히 오프셋 및 출력과 조정을 필요로 한다고 명확히 밝히고 있습니다. Google Cloud Pub/Sub의 정확히 한 번 전송은 풀 구독(Pull subscriptions)으로 제한되며, 클라우드 리전 내에서만 적용되며, 확인이 성공할 때까지 클라이언트가 처리 진행 상황을 추적해야 합니다.

제 주관적인 요약을 단순하게 내리자면 다음과 같습니다. 전송이 재시도할 것이라고 가정하십시오. 운영자가 재생할 것이라고 가정하십시오. 웹훙이 늦게 도착할 것이라고 가정하십시오. 반복된 의도가 두 번째 비즈니스 효과를 생성할 수 없도록 쓰기 경로(Write path)를 설계하십시오. 에러 디자인은 이와 밀접하게 관련되어 있습니다. 에러를 어떻게 감싸고, 변환하며, 재시도 가능한지와 재시도 불가능한 것으로 분류하는지는 동일한 경계 규율(Boundary discipline)의 일부입니다. — Go Error Handling Architecture: Boundaries and Patterns에서는 재시도 가능한 에러 분류, 경계 변환, 그리고 재시도 로직이 올바른 결정을 내릴 수 있게 해주는 센티널 패턴을 다룹니다. 재시도가 건강하지 않은 종속성에 계속 접근하면, circuit breaker at the integration boundary가 재시도 폭풍이 중복 작업을 증폭하기 전에 빠르게 실패합니다.

제가 실제로 신뢰하는 API 계약

중복성 키는 어떻게 중복 API 요청을 방지합니까

변경(Mutating) 작업에 대해 제가 신뢰하는 유일한 API 계약은 호출자가 제공하는 의도(Intent)와 서버 측 지속성(Persistence)입니다.

AWS는 호출자가 제공하는 요청 식별자를 권장하며, 서비스는 중복성 토큰을 변경 작업과 함께 원자적으로(Atomically) 기록해야 한다고 경고합니다. Stripe는 키에 대한 첫 번째 상태 코드와 응답 본문을 저장하고, 나중에 오는 파라미터를 원래 요청과 비교하여 재시도에 대해 동일한 결과를 반환합니다. PayPal은 지원되는 POST API에 PayPal-Request-Id를 사용하며, 해당 헤더로 이전 요청의 최신 상태를 반환합니다.

이는 실용적인 계약으로 이어집니다:

  1. 클라이언트는 비즈니스 작업에 대한 중복성 키를 생성합니다.
  2. 서버는 해당 키를 테넌트와 작업 이름으로 스코핑합니다.
  3. 서버는 요청 해시를 저장하여 동일한 키가 다른 페이로드에 대해 재사용되지 않도록 합니다.
  4. 서버는 pending, completed, failed와 같은 상태를 기록합니다.
  5. 동일한 키로 재시도하면 저장된 결과가 반환되거나 그에 대한 안정적인 포인터가 반환됩니다.
  6. 동일한 키지만 다른 페이로드의 재시도는 명확한 에러로 실패합니다.

IETF에는 Idempotency-Key 헤더 초안이 있지만, 2026-05-09 기준 아직 IETF Datatracker에서 출판된 RFC가 아닌 만료된 인터넷 초안(Expired Internet-Draft)으로 나열되어 있습니다. 실제로 이 헤더 이름은 사실상 표준 관습(de facto convention)으로서 여전히 널리 유용하지만, 표준이 완성된 것처럼 체념하지 말고 자체 API에 계약을 문서화해야 합니다.

키는 무엇을 나타내야 할까요? 의도입니다. HTTP 시도도, TCP 연결도, 재시도 카운터도 아닙니다. 사용자가 “주문 123을 한 번 생성한다"는 의미를 가진다면, 해당 명령어에 대한 모든 재시도는 동일한 키를 재사용해야 합니다. 사용자가 “두 번째 주문을 진행한다"는 의미를 가진다면, 이는 다른 키를 사용해야 합니다.

요청 ID는 추적을 위한 것입니다. 중복성 키는 정확성(Correctness)을 위한 것입니다. 이 둘을 혼동하면 대시보드는 깔끔해 보이지만 돈이 두 번 움직입니다.

왜 PUT만으로는 부족합니까

아니요, HTTP PUT만으로는 작업을 중복성 있게 만들 수 없습니다.

네, RFC 9110은 PUT에 중복성 세미틱스를 부여합니다. 하지만 PUT 핸들러가 새로운 다운스트림 이벤트를 방출하거나, 매 재시도마다 이메일을 보내거나, 외부 제공자에게 다시 청구한다면, 라우트 이름이 괜찮아 보여도 비즈니스 계약을 위반한 것입니다.

메서드 선택은 클라이언트가 의도를 이해하는 데 도움이 됩니다. 하지만 의도를 구현하지는 않습니다.

리소스 모델이 전체 교체 또는 Upsert 스타일 작업과 실제로 일치하는 경우 PUT을 사용하십시오. 명령어나 작업을 생성할 때는 POST를 사용하십시오. 하지만 네트워크 경계를 넘어 재시도될 수 있는 변경 작업에 대해서는 명시적인 중복성 계약을 문서화하십시오. 변경 작업이 채팅 워크플로우에서 트리거되는 경우, 동일한 계약이 Slack Integration Patterns for Alerts and WorkflowsDiscord Integration Pattern for Alerts and Control Loops에서 적용됩니다. 숨겨진 사이드 이펙트는 아키텍처가 죽는 곳입니다.

중복성 키는 얼마나 오래 보관해야 하나요

전송 팀이 원하는 것보다 더 오래요.

Stripe는 키를 최소 24시간 후에 정리할 수 있다고 말합니다. PayPal은 보존 기간이 API별로 다르며 최대 45일까지 지속될 수 있는 예시를 제시합니다. Amazon SQS FIFO는 5분 창(Window) 내에서만 중복 제거를 수행합니다. GitHub는 수동 재전송을 위해 최근 전송 3일을 보관합니다. 이러한 수치가 극적으로 다른 이유는 올바른 보존 기간이 프로토콜 기본값이 아닌 비즈니스 결정이기 때문입니다.

큐가 5분이기 때문에 키를 5분만 보관한다면, 당신은 중복성을 설계하는 것이 아닙니다. 전송의 제한사항을 비즈니스 레이어로 복사하는 것입니다.

다음 창 중 최대값보다 적어도 오래게 중복성 기록을 보관하십시오:

  • 클라이언트 재시도 시간(Horizon)
  • 큐 리드라이브 시간
  • 웹훅 재생 시간
  • 운영자 재생 시간
  • 금전 이동 작업에 대한 정산 또는 보상 시간

결제, 예약, 그리고 프로비저닝의 경우, 이는 종종 분이 아닌 시간이나 일을 의미합니다.

AWS는 제가 완전히 동의하는 두 가지 안티패턴도 지적합니다. 키로 타임스탬프를 사용하지 마십시오. 클록 스큐(Clock skew)와 충돌으로 인해 신뢰할 수 없습니다. 모든 요청에 대한 중복 기록으로 전체 요청 페이로드를 무작정 저장하지 마십시오. 이는 성능과 확장성에 해롭습니다. 정규화된 요청 해시와 안전한 재생에 필요한 최소한의 응답 상태만 저장하십시오. 바이트 단위로 첫 번째 응답을 재현해야 한다면, Stripe이这样做하듯이 정형화된 응답 본문(Canonical response body)을 저장하십시오.

중복성을 현실로 만드는 데이터베이스 패턴

지속성 레이어가 경쟁에서 정확히 한 번에 승리할 때 중복성은 현실이 됩니다.

PostgreSQL은 여기서 두 가지 중요한 원시 데이터(Type)를 제공합니다. 고유 제약 조건(Unique constraints)은 하나 이상의 열에서 무결성을 강제하며, INSERT ... ON CONFLICT는 무결성 위반 시 실패하는 대신 대체 작업을 정의할 수 있게 해줍니다. PostgreSQL은 또한 ON CONFLICT DO UPDATE가 동시성 상황에서 원자적인 삽입-업데이트 결과를 보장한다고 문서화하고 있습니다.

이는 중복성 레이어가 보통 다음과 같은 테이블로 시작해야 함을 의미합니다:

create table api_idempotency (
    tenant_id text not null,
    operation text not null,
    idempotency_key text not null,
    request_hash text not null,
    state text not null,
    status_code integer,
    response_body jsonb,
    resource_type text,
    resource_id text,
    created_at timestamptz not null default now(),
    expires_at timestamptz not null,
    primary key (tenant_id, operation, idempotency_key)
);

그리고 처리 흐름은 다음과 같아야 합니다:

begin transaction

try insert (tenant_id, operation, idempotency_key, request_hash, state='pending')
on conflict do nothing

load row for (tenant_id, operation, idempotency_key) for update

if row.request_hash != incoming_request_hash
    fail with conflict or validation error

if row.state = 'completed'
    return stored response

if row.state = 'pending' and row was created by another live request
    either wait briefly, or fail fast with a retryable response

perform local business mutation

store stable result in idempotency row
set state = 'completed'

commit
return result

중요한 부분은 구문이 아닙니다. 중요한 부분은 원자성(Atomicity)입니다. 키를 기록하고 변경을 수행하는 것은 함께 성공하거나 함께 실패해야 합니다. AWS는 API 중복성에 대해 이를 명시적으로 언급하며, 동일한 규칙이 SQL 기반 서비스에도 적용됩니다.

“키를 선택합니다; 없으면 주문을 삽입합니다"와 같은 단순한 확인-후-실행(Check-then-act) 시퀀스를 수행하지 마십시오. 동시성 하에서 두 요청이 확인을 통과하고 모두 사이드 이펙트를 생성할 수 있습니다. 고유 제약 조건은 선택사항이 아닙니다. 이는 부하 하에서 증명할 수 있는 것까지 아키텍처를 낙관적인 민간 설법(Optimistic folklore)에서 끌어올리는 메커니즘입니다.

제가 리뷰에서 사용하는 규칙은 다음과 같습니다. 중복 제거 결정이 변경과 동일한 트랜잭션 경계로 보호되지 않는다면, 당신은 중복성을 가지지 못한 것입니다. 당신은 희망만 가지고 있을 뿐입니다.

메시지, 이벤트, 웹훅은 자체 경계가 필요합니다

컨슈머는 중복 이벤트와 메시지를 어떻게 처리합니까

메시지 컨슈머의 경우, 고전적인 패턴이 여전히 옳습니다. 비즈니스 업데이트와 동일한 데이터베이스 트랜잭션에서 처리된 메시지 ID를 기록하십시오. Chris Richardson은 구독자와 메시지 ID에 대한 기본 키를 사용하여 중복이 깔끔하게 실패하고 무시될 수 있도록 하는 PROCESSED_MESSAGES 테이블 접근 방식을 직접 설명합니다.

많은 팀이 명시적인 processed_messages 저장소를 인박스 테이블(Inbox table)이라고 부릅니다. 레이블은 규칙보다 덜 중요합니다. 수신자는 재시도가 안전하게 아무것도 하지 않도록 하기 전에 이미 메시지를 처리했음을 증명하는 지속성을 가져야 합니다.

최소한의 형태는 다음과 같습니다:

create table processed_messages (
    subscriber_id text not null,
    message_id text not null,
    processed_at timestamptz not null default now(),
    primary key (subscriber_id, message_id)
);

그리고 컨슈머 흐름은 HTTP 흐름만큼이나 엄격합니다:

begin transaction

insert into processed_messages (subscriber_id, message_id)
values (?, ?)
on conflict do nothing

if no row inserted
    rollback
    ack and ignore duplicate

apply business mutation

commit
ack message

그 패턴은 지루합니다. 좋습니다. 중복성은 지루해야 합니다.

또한 브로커의 마케팅 용어를 의존하려는 시도보다 일반적으로 더 낫습니다. Kafka의 정확히 한 번 전송 지원은 Kafka 자체의 트랜잭션 모델 내에서 머무를 때 훌륭하지만, Kafka 문서에서는 외부 대상이 협력을 필요로 한다고 여전히 경고합니다. SQS FIFO는 5분 중복 제거 창 내에서만 중복 전송을 줄입니다. Pub/Sub의 정확히 한 번 전송은 확인이 실패할 때 컨슈머가 진행 상황을 추적하고 중복 작업을 피하도록 여전히 기대합니다.

정확히 한 번 전송은 종종 로컬 최적화입니다. 중복성 있는 사이드 이펙트가 시스템 보증입니다.

아웃박스 패턴과 중복 제거를 쌍으로 사용하십시오

서비스가 로컬 상태를 업데이트하고 이벤트도 게시하는 경우, 중복성 있는 소비만으로는 충분하지 않습니다. 로컬 트랜잭션 커밋 후에 이벤트를 안전하게 꺼내는 방법도 필요합니다.

바로 이것이 transactional outbox pattern이 중요한 이유입니다. Chris Richardson은 기본 아이디어를 비즈니스 업데이트와 동일한 트랜잭션으로 아웃박스 테이블에 이벤트를 작성한 다음 비동기적으로 게시하는 것으로 설명합니다. Debezium은 아웃박스 패턴이 서비스의 내부 상태와 다른 서비스가 소비하는 이벤트 간의 불일치를 피한다고 말합니다. NServiceBus는 아웃박스 처리가 들어오는 메시지를 중복 제거하고 유령 기록과 고스트 메시지를 피하는 방법을 더 자세히 보여줍니다.

데이터를 소유하고 통합 이벤트를 게시하는 서비스에 제가 권장하는 아키텍처는 다음과 같습니다:

  1. 중복성 키 하에서 명령을 유효성 검사하고 지속합니다.
  2. 비즈니스 상태와 아웃박스 이벤트를 하나의 로컬 트랜잭션에 작성합니다.
  3. CDC 또는 아웃박스 디스패처가 이벤트를 게시하도록 합니다.
  4. 다운스트림 컨슈머도 중복성 있게 만듭니다.

아웃박스는 중복성 있는 컨슈머가 필요하다는 것을 없애지 않습니다. 그것은 일반적으로 불가능한 데이터베이스 커밋과 브로커 게시를 하나의 마법적인 분산 트랜잭션인 것처럼 체념하는 필요성을 없앱니다.

웹훅은 더 나은 브랜드 이름을 가진 메시지일 뿐입니다

들어오는 웹훙을 신뢰할 수 없는 네트워크 엣지에서의 메시지처럼 정확히 취급하십시오.

GitHub는 전송이 순서대로 도착하지 않을 수 있으며, 무결성을 확인하기 위해 X-Hub-Signature-256을 사용하는 것을 권장하며, 고유한 전송 식별자로서 X-GitHub-Delivery를 제공합니다. 또한 재전송이 동일한 전송 ID를 재사용한다고 명시합니다.

따라서 아키텍처는 간단합니다:

  • 먼저 서명을 확인하십시오
  • 중복 제거 키로 전송 GUID를 사용하십시오
  • 사이드 이펙트 전에 영수증을 지속하십시오
  • 도착 순서를 가정하지 말고 핸들러가 순서 인식(Order-aware)이 되도록 하십시오
  • 무거운 작업을 큐에 넣고 빠르게 반환하십시오

웹훅 핸들러가 영수증을 기록하기 전에 비즈니스 테이블에 직접 작성한다면, 프로덕션 준비가 된 것이 아닙니다. 그것은 단순히 중복 실수를 더 빨리 만드는 것입니다.

세이지(Saga)와 워크플로우 엔진도 여전히 중복성이 필요합니다

세이지와 내구성 있는 워크플로우 엔진은 문제를 삭제하지 않습니다. 이를 가시적으로 만듭니다.

Temporal은 실패나 타임아웃 후 Activities가 재시도될 수 있으므로 Activities를 중복성 있게 작성할 것을 권장합니다. 그 문서에서는 외부 사이드 이펙트에 대한 완료를 성공적으로 완료했지만 보고하기 전에 충돌하여 Activity가 다시 실행되는 경우라는 가장자리 케이스까지 지적합니다. Temporal은 또한 다운스트림 서비스에 호출할 때 워크플로우 실행 ID와 Activity ID의 조합을 안정적인 중복성 키로 사용할 것을 제안합니다. 서비스 오케스트레이션에서 이를 적용하는 경우, Go Microservices for AI/ML Orchestration에서 더 넓은 워크플로우 트레이드오프를 다룹니다.

이는 정확히 올바른 정신적 모델입니다. 워크플로우 엔진은 실행 기록을 보존하고 재시조를 조정할 수 있습니다. 그러나 애플리케이션이 중복성 있는 단계와 중복성 있는 보상을 제공하지 않는 한, 카드에 대한 결제를 취소하거나 이메일을 보내지 않도록 할 수는 없습니다.

이는 세이지에도 동일하게 적용됩니다. Temporal의 자체 세이지 가이드는 단계가 실패할 때 실행되는 보상 작업을 설명합니다. 이러한 보상도 중복성 있어야 합니다. “결제 환불"이 두 번 실행되면, 원래 버그를 해결한 대신 새로운 버그를 생성하게 됩니다.

여기서 제 규칙은 잔인하고 단순합니다. 외부 세계에 접촉하는 모든 Activity, 모든 명령 핸들러, 그리고 모든 보상은 자연스럽게 중복성 있거나 다운스트림 시스템에 대한 실제 중복성 키를 가져야 합니다.

프로덕션 전에 중복성을 테스트하는 방법

대부분의 팀은 Happy path만 테스트한 후 재시도가 발생할 때 놀랍니다. 그것은 충분하지 않습니다. Go 팀의 경우, Testing Concurrent Go Code with testing/synctest에서는 인위적인 지연 시간을 기다리지 않고 재시도 루프와 컨텍스트 데드라인 동작에 대한 빠르고 결정론적인 테스트를 작성하는 방법을 다룹니다.

다음과 같은 경우에 대해 최소한 자동화된 테스트를 가져야 합니다:

  • 서버는 변경을 커밋하지만 응답이 클라이언트에 도달하지 않음
  • 동일한 중복성 키로 두 개의 동일한 요청이 경쟁함
  • 동일한 키가 다른 페이로드와 함께 재사용됨
  • 컨슈머가 데이터베이스 작업을 커밋하고 확인 전 충돌함
  • 동일한 전송 ID로 웹훅이 재생됨
  • 아웃박스 디스패처가 동일한 이벤트를 한 번 이상 게시함
  • 워크플로우 Activity가 외부 호출을 완료하고 완료 보고 전에 충돌함
  • 중복성 기록이 만료되고 진정한 늦은 재시도가 도착함

AWS는 성공한 요청, 실패한 요청, 중복 요청을 모두 포함하는 포괄적인 테스트 스위트의 사용을 명시적으로 권장합니다. 그 조언은 평범하지만 절대적으로 옳습니다.

저는 하나 더 실패 드릴을 추가하겠습니다. 재생된 응답이 첫 번째 결과와 의미론적으로 동일한지 확인하십시오. AWS는 늦게 도착하는 재시도에 대해 논의하며, 기본 상태가 변경된 후에도 원래 의미를 보존하는 응답을 주장합니다. 이것이 “추가 사이드 이펙트가 발생하지 않음"과 “호출자가 여전히 일관된 계약을 가짐"의 차이점입니다.

실제 시스템을 구하는 주관적인 규칙들

아키텍처 리뷰에서 제가 강제했을 법한 규칙들입니다.

첫째, 중복성 키는 전송 시도가 아닌 비즈니스 의도에 속합니다.

둘째, 모든 키를 테넌트와 작업으로 스코핑하십시오. 전역 키 공간은 관련 없는 요청이 충돌하는 방법입니다.

셋째, 중복 제거 결정을 변경과 함께 원자적으로 지속하십시오. 그게 사실이 아니라면, 디자인은 잘못된 것입니다.

넷째, 동일한 키지만 다른 페이로드의 재시도를 거부하십시오. Stripe과 AWS는 좋은 이유로 이를 수행합니다.

다섯째, 가장 짧은 큐 창이 아닌 비즈니스 프로세스의 전체 재생 시간을 위해 키를 유지하십시오.

여섯째, 프로듀서는 아웃박스와 함께 사용하고, 컨슈머는 메시지 ID 추적으로 쌍을 이루십시오. 한쪽만 있는 것은 반쯤 설계된 것입니다.

일곱째, 비즈니스 작업이 동일할 때 다운스트림으로 동일한 작업 ID를 전파하십시오. AWS는 처리 체인 전체에 걸쳐 중복성 토큰을 전달할 것을 명시적으로 권장합니다.

여덟째, 정확히 한 번 전송 마케팅이 중복성 있는 사이드 이펙트 필요성을 제거한다고 절대 가정하지 마십시오.

그것이 엄격하게 들린다면 좋습니다. 중복성은 낙관적인 아키텍처가 프로덕션 현실을 만나는 곳입니다. 모든 곳에 복잡성이 필요한 것은 아닙니다. 하지만 중복된 사이드 이펙트가 돈, 상태 또는 신뢰에 해를 끼칠 수 있는 곳이라면 어디든 중복성은 계약의 첫 번째 클래스(First-class) 구성 요소여야 합니다.

이러한 동일한 규칙은 백그라운드 AI 에이전트에도 직접적으로 적용됩니다. 태스크를 주장하고, 알림을 생성하며, 도구 호출을 트리거하는 폴링 에이전트는 결제 API와 마찬가지로 중복 제거 키와 중복성 있는 주장 프로토콜이 필요합니다. 프로덕션 AI 어시스턴트 내에서 주장-중복 제거 패턴이 어떻게 작동하는지는 Polling Agents in AI Assistants: 11 Implementation Patterns을 참조하십시오.

유용한 링크

구독하기

시스템, 인프라, AI 엔지니어링에 관한 새 글을 받아보세요.