107 lines
9.9 KiB
Markdown
107 lines
9.9 KiB
Markdown
---
|
||
title: "company-tech-blog / Brandur Leach — Implementing Stripe-like Idempotency Keys (Client-Generated Key vs Server-Assigned Resource ID)"
|
||
source_type: company-tech-blog
|
||
url: https://brandur.org/idempotency-keys
|
||
archive_url:
|
||
related_branches: [feature-resource-identifier-contract]
|
||
related_projects: [ca-skeleton]
|
||
tags: [company-tech-blog, ca-skeleton, api-design, idempotency, resource-identifier, public-id-separation]
|
||
created: 2026-05-31
|
||
---
|
||
|
||
# Brandur Leach — Implementing Stripe-like Idempotency Keys (Client-Generated Key vs Server-Assigned Resource ID)
|
||
|
||
> Layer: `raw/company-tech-blogs/` — 전 Stripe 엔지니어 Brandur Leach 의 개인 기술 블로그. Stripe 내부 idempotency 구현 패턴을 일반화한 글. **Stripe 공식 문서 아님** — `engineering-blog` 등급 적용. best practice 단정 금지. 가장 널리 인용되는 idempotency key 구현 레퍼런스.
|
||
>
|
||
> **이 파일의 초점**: `feature-resource-identifier-contract` 의 D4 (ID 생성 책임) · D14 (Idempotency-Key vs Resource ID 구분) · D11 (Public ID vs Internal Sequence 분리) 결정 정당화. Postgres/DB 구현 상세(locked_at, atomic phase 등)는 [[raw/company-tech-blogs/idempotency-brandur-stripe-postgres]] 에 별도 보관.
|
||
|
||
## Parent / 활용 branch (필수)
|
||
|
||
| Branch | 이 자료가 정당화하는 결정 |
|
||
|---|---|
|
||
| [[raw/branch-notes/feature-resource-identifier-contract]] | D4 (ID 생성 책임): `Idempotency-Key` 는 *client-generated*, resource ID 는 *server-assigned* — 두 책임의 원천이 다름을 원문으로 뒷받침. D14 (Idempotency-Key vs Resource ID): 명시적 분리 + 수명주기 차이 + 형식 무관 조합 허용. D11 (Public ID vs Internal Sequence): Stripe 가 external-only (단일 public ID) 패턴을 쓰고 idempotency key 를 별도 레이어로 두는 사례 |
|
||
|
||
## 출처 / Source
|
||
|
||
- 원본 URL: https://brandur.org/idempotency-keys
|
||
- 아카이브 URL: (미수집)
|
||
- 저자 / 조직: Brandur Leach (전 Stripe 엔지니어, 개인 기술 블로그 brandur.org)
|
||
- 발행일: 본문 명시 없음 (2017~2018 추정)
|
||
- 마지막 확인일: 2026-05-31
|
||
|
||
## 왜 저장했는지 / Why archived
|
||
|
||
`Idempotency-Key` 가 *client-generated* 임을 원문으로 확인하고, server-assigned resource ID 와의 명시적 분리를 `feature-resource-identifier-contract` (D4/D14/D11) 의 근거로 삼기 위해 보관. 기존 `idempotency-brandur-stripe-postgres.md` 가 DB/Postgres 구현에 초점을 두는 반면, 본 파일은 **ID 생성 책임의 주체(client vs server) 와 수명주기 분리**에 초점.
|
||
|
||
## 핵심 인용 / Key quotes (verbatim, 3~5개)
|
||
|
||
> [§HTTP header example] "A common way to transmit an idempotency key is through an HTTP header:"
|
||
> (원문 코드 예시: `Idempotency-Key: 0ccb7813-e63d-4377-93c5-476cb93038f3`)
|
||
> — line 4 in fetched text
|
||
|
||
> [§Key definition] "An idempotency key is a unique value that's generated by a client and sent to an API along with a request."
|
||
> — line 12 in fetched text
|
||
|
||
> [§Key format hint] "something with good randomness like a UUID"
|
||
> — line 14 in fetched text
|
||
|
||
> [§Key TTL] "Keys are not meant to be used as a permanent request archive but rather as a mechanism for ensuring near-term correctness. Servers should recycle them out of the system beyond a horizon where they won't be of much use – say 24 hours or so."
|
||
> — line 16 in fetched text
|
||
|
||
> [§Fingerprint / params mismatch] "Programs sending multiple requests with different parameters but the same idempotency key is a bug."
|
||
> — line 20 in fetched text
|
||
|
||
## Claims Extracted / 추출된 주장
|
||
|
||
> 이 자료가 **직접 말하는 것만** claim 으로 분리한다. Stripe 공식 문서 아님 — `engineering-blog` strength 이상으로 격상 금지.
|
||
|
||
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|
||
|---|---|---|---|---|---|
|
||
| BRANDUR-IDEMP-C8 | `Idempotency-Key` 는 *client* 가 생성해서 API 요청과 함께 전송하는 unique value 임 | [§Key definition] "An idempotency key is a unique value that's generated by a client and sent to an API along with a request." | `engineering-blog` | Idempotency-Key HTTP header 의 생성 책임이 client 에 있음 (D4 근거) | server 가 Idempotency-Key 를 생성하면 안 된다는 규범적 금지 규칙 (이 블로그는 권고 사례이지 표준이 아님) |
|
||
| BRANDUR-IDEMP-C9 | Idempotency-Key 의 포맷은 "UUID 처럼 난수성이 높은 것" 을 권장 | [§Key format hint] "something with good randomness like a UUID" | `engineering-blog` | key 포맷 선택 가이드 (D4 / D14 보조) | UUID v4 만 허용된다는 뜻이 아님. ULID / NanoID 등 다른 포맷도 동등하게 사용 가능 |
|
||
| BRANDUR-IDEMP-C10 | Idempotency-Key 의 TTL 은 영구 보관이 아닌 단기 정확성 보장 용도이며, 24시간 정도가 적절 | [§Key TTL] "Keys are not meant to be used as a permanent request archive but rather as a mechanism for ensuring near-term correctness. Servers should recycle them out of the system beyond a horizon where they won't be of much use – say 24 hours or so." | `engineering-blog` | idempotency key TTL 정책 설계 (D14 의 수명주기 차이 근거) | resource ID 의 수명주기 (persistent, 영구) 와의 차이를 *명시적으로* 비교하지는 않음 — 대조 추론은 wiki/concepts 에서 |
|
||
| BRANDUR-IDEMP-C11 | Idempotency-Key 는 HTTP header 로 전송하는 것이 일반적 패턴 | [§HTTP header example] "A common way to transmit an idempotency key is through an HTTP header" (코드 예시: `Idempotency-Key: 0ccb7813-e63d-4377-93c5-476cb93038f3`) | `engineering-blog` | HTTP API 에서 idempotency key 전달 방식 (D14 분리 근거) | 이것이 유일한 전송 방법이라는 뜻은 아님 (query param / body 전달도 기술적으로 가능) |
|
||
| BRANDUR-IDEMP-C12 | 동일 key + 다른 request params 요청은 client 측 버그로 명시 — 서버는 이를 거부해야 함 | [§Fingerprint / params mismatch] "Programs sending multiple requests with different parameters but the same idempotency key is a bug." | `engineering-blog` | request fingerprint 비교 정책 (D14 보조 — idempotency key 와 request 내용의 결합 의미) | 거부 시 HTTP status code (409 vs 422) 는 이 인용에 없음 (Brandur 는 409 사용, IETF draft 는 422 권고) |
|
||
|
||
### Strength 허용값 (참고)
|
||
|
||
- `engineering-blog` — 개인/팀 블로그의 엔지니어링 해설 (본 자료의 등급)
|
||
|
||
## Usage Boundaries / 적용 경계
|
||
|
||
- **이 자료가 직접 증명하는 것**:
|
||
- `BRANDUR-IDEMP-C8`: Idempotency-Key 가 client-generated 임 (D4 의 "Idempotency-Key 는 client 생성" 근거)
|
||
- `BRANDUR-IDEMP-C9`: UUID 같은 난수 포맷 권장 (D4/D14 포맷 가이드)
|
||
- `BRANDUR-IDEMP-C10`: Idempotency-Key TTL 이 단기(~24h)이고 영구 보관이 아님 (D14 의 수명주기 차이)
|
||
- `BRANDUR-IDEMP-C11`: Idempotency-Key 가 HTTP header 로 전달됨 — resource ID 는 response body / URL path 에 위치 (D14 분리의 물리적 근거)
|
||
- `BRANDUR-IDEMP-C12`: 같은 key + 다른 params = client bug — fingerprint 검사 의무 (D14 보조)
|
||
- **이 자료가 증명하지 않는 것**:
|
||
- Stripe 의 resource ID 와 idempotency key 를 *명시적으로 대조* 한 서술은 없음 — 원문은 idempotency key 만 집중 서술. resource ID 의 분리는 구조적 추론.
|
||
- D11 (Public ID vs Internal Sequence): 원문은 Stripe 가 single public UUID 만 쓴다고 명시하지 않음 — Stripe 공식 API docs 로 보강 필요.
|
||
- HTTP status 409 vs 422 의 표준 적합성 — IETF draft 별도 확인 필요.
|
||
- 이 자료는 `engineering-blog` 등급 — "Stripe 공식 best practice" 로 표현 금지.
|
||
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
|
||
- ca-skeleton 의 `Idempotency-Key` 24h TTL 이 Brandur 의 "~24 hours or so" 와 일치하는지 (ca-tmpl 결정에서는 24h 채택 — 이 인용이 direct support 가능).
|
||
- Idempotency-Key 포맷 (UUID v4 권장) 과 resource ID 포맷 (ULID/UUID v7 — D1 결정) 의 *다른 형식 조합* 허용 여부 — 이 블로그는 조합에 제약을 두지 않음 (UNSUPPORTED_DECISION 여지 없음).
|
||
- `feature-resource-identifier-contract` D11 (external-only vs dual) 에 대한 Stripe 사례 뒷받침은 Stripe 공식 API doc 별도 보강 권고.
|
||
|
||
## 메모 / Notes
|
||
|
||
- `BRANDUR-IDEMP-C8~C12` 는 기존 `idempotency-brandur-stripe-postgres.md` 의 `C1~C7` 과 Claim ID 연번 충돌 없이 설계됨 (같은 PREFIX 의 다른 raw file 이므로 연번 구분 필요 — 이 파일의 claims 은 C8 부터).
|
||
- D14 (Idempotency-Key vs Resource ID 구분) 에서 이 자료가 직접적인 *대조* 서술은 제공하지 않음. 그러나 `C8` (client-generated) + `C10` (TTL ~24h) + `C11` (HTTP header 전달) 을 조합하면 resource ID (server-assigned, persistent, URL path) 와의 대조 추론이 가능 — 이 추론은 wiki/concepts 또는 branch decision note 에서만 서술.
|
||
- 포맷 조합 자유도 (`C9`): idempotency key 는 UUID v4, resource ID 는 ULID 의 조합이 이 블로그의 내용과 충돌하지 않음.
|
||
- Claim C8 이 D4 의 핵심 direct evidence. "client-generated" 한 단어가 ID 생성 책임 결정의 분기점.
|
||
|
||
## Related / 관련
|
||
|
||
- 같은 URL 의 다른 초점 raw (DB/Postgres 구현):
|
||
- [[raw/company-tech-blogs/idempotency-brandur-stripe-postgres]] — locked_at, atomic phase, recovery point, reaper 72h, scope (user_id, key) 등 구현 상세 (C1~C7)
|
||
- 같은 주제 다른 raw:
|
||
- [[raw/company-tech-blogs/idempotency-toss-payments-techblog]] — Toss Payments 4-tuple scope + 15일 TTL + 409 in-flight
|
||
- [[raw/company-tech-blogs/idempotency-redis-vs-db-storage]] — Redis vs DB 저장소 trade-off
|
||
- 관련 branch:
|
||
- [[raw/branch-notes/feature-resource-identifier-contract]] — D4/D14/D11 결정 (이 자료의 primary consumer)
|
||
- [[raw/branch-notes/feature-rate-limit-idempotency-contract]] — Idempotency-Key 운영 계약 SSOT
|
||
- [[raw/branch-notes/feature-api-contract-baseline]] — fingerprint mismatch 응답 코드 정책
|
||
- 인용한 wiki 요약: (미작성)
|