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 |
|
|
|
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 단정 금지. 검증된 요약은/ingest후wiki/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_keyunique, 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 와 비교한 표준 정합성
- ca-tmpl 의 3-tuple
메모 / 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_paramsJSONB 비교 → 다르면 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 이 더 짧음 (스토리지·공격면 측면에서 보수적).
Related / 관련
- 같은 주제 다른 raw:
- raw/company-tech-blogs/idempotency-toss-payments-techblog — vendor official 비교 (4-tuple, 15일 TTL, 409 in-flight)
- raw/company-tech-blogs/idempotency-redis-vs-db-storage — Redis vs DB 저장소 trade-off
- 인용하는 branch:
- 인용하는 project:
- 인용한 wiki 요약: (미작성)