Files
llm-wiki/raw/company-tech-blogs/idempotency-brandur-stripe-postgres.md

11 KiB

title, source_type, url, archive_url, status, confidence, tags, related_projects, related_branches, created, last_reviewed
title source_type url archive_url status confidence tags related_projects related_branches created last_reviewed
Brandur — Implementing Stripe-like Idempotency Keys in Postgres company-tech-blog https://brandur.org/idempotency-keys raw high
ca-idempotency
postgres
db-storage
atomic-phases
recovery-points
stripe
ca-skeleton-operational-contract
feature-rate-limit-idempotency-contract
feature-api-contract-baseline
2026-05-22 2026-05-27

Brandur — Implementing Stripe-like Idempotency Keys in Postgres

Layer: raw/company-tech-blogs/ — 전 Stripe 엔지니어 개인 블로그 (engineering-blog 등급). Stripe 내부 구현 패턴을 일반화한 글로 Postgres 기반 idempotency 구현의 reference. Stripe 공식 문서 아님 — best practice 단정 금지. 검증된 요약은 /ingestwiki/concepts/에 별도 작성.

Parent / 활용 branch (필수)

Branch 이 자료가 정당화하는 결정
raw/branch-notes/feature-rate-limit-idempotency-contract DB table 기반 저장 + locked_at lock + reaper 의 reference 구현. ca-tmpl 의 200ms in-flight wait + 24h TTL 결정의 비교 base
raw/branch-notes/feature-api-contract-baseline API contract surface 에 Idempotency-Key 의 fingerprint mismatch 정책 (Brandur 409, IETF 422) 비교 근거
raw/project-notes/ca-skeleton-operational-contract §13. API Contract Surface (Idempotency-Key) + §18. Control Plane Contract (Rate Limit/Idempotency) 의 DB-based 구현 reference

컨텍스트

ca-tmpl 이 "DB table 기반 저장 + 200ms in-flight wait" 를 채택한 직접적 근거가 되는 구현 패턴. Redis 기반 저장(대안 6) vs DB 기반 저장 비교에 결정적 자료. atomic phase / recovery_point 모델은 단순 dedup 을 넘어 부분 실행 후 retry 복구까지 다룬다.

출처 / Source

  • 원본 URL: https://brandur.org/idempotency-keys
  • 아카이브 URL: (미수집)
  • 저자 / 조직: Brandur Leach (전 Stripe 엔지니어, 개인 블로그)
  • 발행일: 본문 명시 없음 (2017~2018 추정)
  • 마지막 확인일: 2026-05-27

핵심 인용 / Key quotes (verbatim)

[§Schema — locked_at] "locked_at: A field that indicates whether this idempotency key is actively being worked."

[§Schema — params] "params: The input parameters of the request. This is stored mostly so that we can error if the user sends two requests with the same idempotency key but with different parameters."

[§Unique constraint] "We've made idempotency_key unique, but across (user_id, idempotency_key) so that it's possible to have the same idempotency key for different requests as long as it's across different user accounts."

[§Mismatched params] "Programs sending multiple requests with different parameters but the same idempotency key is a bug."

[§Lock acquisition] "Only acquire a lock if the key is unlocked or its lock has expired because the original request was long enough ago."

[§Reaper] "I'd suggest a threshold of about 72 hours so that even if a bug is deployed on Friday that errors a large number of valid requests, an app could still keep a record."

[§Atomic phases] "An atomic phase is a set of local state mutations that occur in transactions between foreign state mutations. We say that they're atomic because we can use an ACID-compliant database to guarantee either all occur, or none."

Claims Extracted / 추출된 주장

Claim ID Claim (이 자료가 직접 말하는 것) Evidence quote Strength Applies to Does not prove
BRANDUR-IDEMP-C1 idempotency_keys 테이블에 locked_at 컬럼을 두어 키가 active 처리 중인지 표시 [§Schema — locked_at] "locked_at: A field that indicates whether this idempotency key is actively being worked." engineering-blog Postgres 기반 idempotency 구현 row-level FOR UPDATE lock 대신 컬럼 lock 을 쓰는 이유 (가시성, stale lock 정리)는 본 인용 범위 밖
BRANDUR-IDEMP-C2 params 컬럼에 request 입력을 저장하는 주 목적은 동일 키 + 다른 파라미터 요청을 error 로 반환하기 위함 [§Schema — params] "params: The input parameters of the request. This is stored mostly so that we can error if the user sends two requests with the same idempotency key but with different parameters." engineering-blog DB-based fingerprint mismatch 정책 mismatch 시 정확한 status code (409 vs 422) 는 본 인용에 없음 — Brandur 본문 다른 곳에서 409 언급
BRANDUR-IDEMP-C3 unique 제약은 (user_id, idempotency_key) 2-tuple — 다른 user 면 같은 키 허용 [§Unique constraint] "We've made idempotency_key unique, but across (user_id, idempotency_key) so that it's possible to have the same idempotency key for different requests as long as it's across different user accounts." engineering-blog per-user scope 의 idempotency endpoint/method 까지 분리하지 않는 이유는 본 인용에 없음. Stripe 자체의 운영 정책과 다를 수 있음 (Stripe 공식 문서 확인 필요)
BRANDUR-IDEMP-C4 동일 키로 다른 파라미터 요청은 client 측 버그로 명시 [§Mismatched params] "Programs sending multiple requests with different parameters but the same idempotency key is a bug." engineering-blog client retry 정책 설계 모든 vendor 가 동일하게 취급한다는 뜻은 아님 (IETF draft 는 422 권고, Toss 는 명시 없음)
BRANDUR-IDEMP-C5 lock 획득 조건은 (a) 해제 상태이거나 (b) 충분히 오래 전 요청이라 lock 이 만료된 경우만 [§Lock acquisition] "Only acquire a lock if the key is unlocked or its lock has expired because the original request was long enough ago." engineering-blog locked_at 기반 stale lock 회수 메커니즘 lock 만료 기준 시간 (예: 90초, 5분 등) 의 정확한 값은 인용 범위에 없음
BRANDUR-IDEMP-C6 reaper 의 keep threshold 권장값은 약 72시간 — 금요일 버그 배포 대비 [§Reaper] "I'd suggest a threshold of about 72 hours so that even if a bug is deployed on Friday that errors a large number of valid requests, an app could still keep a record." engineering-blog DB-based idempotency 의 reaper 운영 72시간이 모든 도메인의 표준이라는 뜻은 아님. Toss 15일, Stripe v2 30일, ca-tmpl 24h 등 다양
BRANDUR-IDEMP-C7 atomic phase = "foreign state mutation 사이에 일어나는 local state mutation 의 집합" 으로 ACID DB 가 all-or-none 을 보장 [§Atomic phases] "An atomic phase is a set of local state mutations that occur in transactions between foreign state mutations. We say that they're atomic because we can use an ACID-compliant database to guarantee either all occur, or none." engineering-blog 외부 API 호출이 끼어드는 결제 등 도메인의 recovery 모델 모든 비즈니스 로직이 atomic phase 모델에 적합하다는 뜻은 아님. 외부 호출이 없거나 idempotent 한 작업은 과한 설계

Usage Boundaries / 적용 경계

  • 이 자료가 직접 증명하는 것:
    • BRANDUR-IDEMP-C1 ~ C7: Postgres 기반 idempotency 구현의 schema, lock 메커니즘, reaper 권장 시간, atomic phase 모델
  • 이 자료가 증명하지 않는 것:
    • Stripe 의 실제 internal 구현이 본 글과 동일한지 (저자는 전 Stripe 엔지니어이지만 본 글은 일반화된 패턴, Stripe 공식 문서 아님)
    • Redis 기반 구현이 부적절하다는 결론 (본 글은 DB 기반만 다룸 — 비교 결론은 별도 자료 필요)
    • 72시간 reaper 가 모든 도메인의 표준 (vendor 별로 24h~30일 다양)
    • locked_at 컬럼 lock 이 Redlock 등 distributed lock 보다 안전하다는 일반 결론
  • 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
    • ca-tmpl 의 3-tuple (principal, key, useCaseName) 과 Brandur 의 2-tuple (user_id, idempotency_key) 매핑 시 useCaseName 이 endpoint 분리 역할을 충분히 하는지
    • ca-tmpl 의 200ms wait 가 Brandur 의 locked_at 만료 모델과 호환되는 구현인지 (wait timeout vs lock expiry 별개)
    • fingerprint mismatch 시 ca-tmpl 의 422 vs Brandur 의 409 — IETF draft 와 비교한 표준 정합성

메모 / Notes

검증되지 않은 내 해석은 여기에 두지 말 것 — wiki source-summary 단계에서.

  • key scope (어떤 dimension으로): (user_id, idempotency_key) — Stripe v1 pair scope 의 구체 구현으로 보임 (Stripe 공식 문서로 corroborate 필요). ca-tmpl 의 (principal, key, useCaseName) 는 여기에 endpoint dimension 을 추가한 형태.
  • TTL: 권장 72시간 (C6). ca-tmpl 24h 는 더 짧음.
  • 저장소: Postgres 테이블. Redis 아님. → 결제·상태변경 도메인에서 Redis 보다 DB 가 선호되는 이유의 reference (단 engineering-blog 등급).
  • duplicate 처리:
    • 완료된 동일 key → response_code/body 그대로 replay (블로그 본문에서 별도 설명).
    • in-flight → locked_at 으로 차단 (C5). lock 만료 시 재시도 가능.
  • fingerprint (same key, different body): request_params JSONB 비교 → 다르면 409 Conflict (블로그 본문). Brandur 409, IETF/ca-tmpl 422. 코드 차이만 있고 사상은 같음.
  • recovery points: 단순 dedup 을 넘어 "atomic phase" 모델 (C7) 로 부분 실행 후 retry 복구까지 다룸. STARTED → RIDE_CREATED → CHARGE_CREATED → FINISHED 같은 상태 머신.
  • 장점 (블로그 본문 + 추론):
    • 트랜잭션과 같은 DB 안에 있어 결제 정합성과 한 단위로 묶임 (Redis 면 별도 정합성 관리 필요).
    • atomic phase 로 외부 호출(charge 등) 중간 실패도 안전한 retry 가능.
    • 운영 가시성 (SQL 로 키 조회·디버깅).
  • 단점 (추론, 미검증):
    • Redis 대비 처리량/latency 손해.
    • 테이블 비대화 → 인덱스/Vacuum 운영 비용. Reaper 필수.
    • lock 컬럼 기반이라 connection-level lock 보다 가시성은 좋으나 stale lock 위험 (만료 정책 필수).
  • ca-tmpl 과의 차이:
    • 저장소 선택 (DB) = 일치.
    • lock 모델: Brandur locked_at 컬럼 = ca-tmpl 200ms wait 의 기반 메커니즘. ca-tmpl 이 wait timeout 을 짧게 잡아 client 친화 + 좀비 lock 위험을 줄임.
    • scope: Brandur 2-tuple vs ca-tmpl 3-tuple. ca-tmpl 이 endpoint(useCase) 까지 분리하여 더 안전.
    • fingerprint mismatch status: Brandur 409 vs ca-tmpl 422. IETF draft 는 422 를 권하므로 ca-tmpl 이 더 표준 정합적.
    • TTL: Brandur 72h vs ca-tmpl 24h → ca-tmpl 이 더 짧음 (스토리지·공격면 측면에서 보수적).