11 KiB
11 KiB
title, source_type, status, confidence, tags, related_projects, last_reviewed
| title | source_type | status | confidence | tags | related_projects | last_reviewed | ||||
|---|---|---|---|---|---|---|---|---|---|---|
| Idempotency Key 설계 (triple scope vs Stripe/Square/Toss) | llm-generated | draft | medium |
|
|
2026-05-22 |
Idempotency Key 설계 (triple scope vs Stripe/Square/Toss)
Layer:
wiki/concepts/— 일반 개념. 내 프로젝트 사실은 raw/branch-notes/feature-rate-limit-idempotency-contract / raw/project-notes/ca-skeleton-operational-contract §29 Topic 5 참조.
Summary
Idempotency key는 동일한 mutating request의 재시도를 서버가 인식하도록 클라이언트가 생성하는 고유 값입니다. ca-tmpl은 key shape를 (authenticatedPrincipal, idempotencyKey, useCaseName) triple + DB table + 24h TTL + 200ms in-flight wait + fingerprint mismatch 시 HTTP 422로 정의합니다. 이 설계는 (a) triple scope로 endpoint dimension을 명시해 cross-use-case 충돌을 방지하고, (b) 24h TTL로 스토리지·키 추측 공격면을 최소화하며, (c) 200ms wait로 IETF draft의 즉시 409보다 retry 친화적인 hybrid를 채택하고, (d) body fingerprint mismatch를 409(in-flight)와 분리해 422로 표현한 점이 특징입니다.
Standard (공식 정의)
IETF draft (draft-ietf-httpapi-idempotency-key-header, draft-07, 2025-10)
Idempotency-KeyHTTP request header를 정의 — Stripe / PayPal / Square / Adyen이 공통 참조하는 사실상의 헤더 표준 초안 (정식 RFC 아님).- 인용: "Uniqueness of the key MUST be defined by the resource owner and MUST be implemented by the clients." — key scope 정의는 resource owner의 책임으로 위임.
- 인용: "If there is an attempt to reuse an idempotency key with a different request payload, the resource SHOULD reply with a HTTP
422status code." - 인용: "The request was retried before the original request completed. The resource SHOULD respond with a resource conflict error" (HTTP
409). - TTL은 시간을 명시하지 않고 "정책을 정해 문서화하라"만 강제.
Stripe v1 pair → v2 triple
- v1:
(account, Idempotency-Key)pair. TTL 24h minimum. 5xx 응답까지 그대로 replay됨(결정적 응답). - v2: "idempotent request replay occurs when requests use the same idempotency key, are made to the same API, occur within the scope of the same account or sandbox, and occur within 30 days of each other." →
(account/sandbox, API, key)triple. TTL 30일. - fingerprint mismatch: "The idempotency layer compares incoming parameters to those of the original request and errors if they're not the same." (status code는 명시 안 함).
Square (Common API patterns)
idempotency_key를 body 필드로 받음 (header 표준 미준수). endpoint별 dedup →(merchant_account, endpoint, idempotency_key)사실상 triple.- fingerprint mismatch: "If you use the same idempotency key but change the
CreatePaymentrequest ... you get an error indicating that you used the idempotency key previously." - TTL 미공개, in-flight 동작 미정의.
- 특수 디자인:
cancel-payment-by-idempotency-key— 키 자체를 resource handle로 사용.
PayPal (Idempotency-Replay / PayPal-Request-Id)
- header 이름이
Idempotency-Key가 아닌PayPal-Request-Id(Stripe·IETF와 다름). - scope:
(request-id, API call type). TTL 45일 — 조사된 reference 중 최장.
Toss Payments (기술블로그)
- 4-tuple
(account, key, URL, method)+ TTL 15일. ca-tmpl보다 dimension 1개 많고 TTL 더 김. - header 이름은
Idempotency-Key로 IETF/Stripe와 동일.
AWS Lambda Powertools (idempotency utility)
- key를 server-derived content-hash
(function_name, payload_hash)로 도출 → 클라이언트가 header를 보낼 필요 없음. - 동일 payload면 동일 hash → 자동 dedup. body 변경 = 서로 다른 operation으로 취급.
GitHub REST API
- API-level idempotency dedup을 제공하지 않음. 클라이언트 측 retry 정책에만 의존.
Brandur (Stripe 엔지니어 글) — Postgres locked_at lock
- Postgres 테이블 + atomic phase 모델 +
locked_atcolumn으로 in-flight를 표현. abandoned key 회수는 별도 정책 필요. - Stripe 내부 구현의 가장 자세한 reference 문서.
한계 / 주의점
| 옵션 | 한계 / 주의점 |
|---|---|
Stripe v1 pair (account, key) |
endpoint dimension 부재 → API 추가 시 같은 키가 의도하지 않은 use case에 재사용될 위험. v2에서 API dimension 추가로 직접 보강. |
Stripe v2 triple (account, API, key) |
IETF "resource owner가 정의" 범위 내에서 가장 엄격한 reference. TTL 30일은 보안 surface와 비용에 부담. |
| Square endpoint-scoped (body field) | header 표준 미준수 → 미들웨어/게이트웨이 레벨에서 dedup 불가. URL path 변경 시 endpoint dimension 매핑이 깨질 수 있음. TTL 미공개로 클라이언트가 retry window를 가늠 못 함. |
| PayPal 45일 TTL | 스토리지 비용 크고 키 추측 공격면이 가장 넓음. header 이름이 표준과 달라 멀티 PG 통합 비용 발생. |
Toss 4-tuple (account, key, URL, method) |
URL/method가 scope에 들어가 HTTP path 변경(예: /v1/payments → /v2/payments) 시 같은 의미의 재시도가 다른 키로 인식. version migration에 취약. |
| AWS Powertools content-hash | 클라이언트가 키를 누락해도 동작하는 장점이 있으나, body의 사소한 변경(여백/필드 순서)이 다른 operation으로 분류 — JSON canonicalization 정책 필수. |
Brandur Postgres lock (locked_at) |
locked_at만으로는 process crash 후 stale lock이 남을 수 있음 → abandoned key 회수(timeout-based release) 정책이 별도로 필요. |
| IETF draft 자체 | draft 단계로 정식 RFC 아님. TTL / 저장 layer / lock 정책 등 운영 핵심을 표준이 다루지 않아 구현체별 동작이 제각각. |
| No API-level dedup (GitHub) | 인프라/미들웨어 부담은 없으나 클라이언트가 모든 중복 위험을 책임 → 결제·금융 도메인에는 부적합. |
흔한 오해
- "Stripe pair보다 ca-tmpl이 무조건 안전" — v1 한정 비교. Stripe v2 triple과는 사실상 동등.
- "IETF draft 422는 fingerprint mismatch의 표준" — draft는
SHOULD이지MUST아님. 구현체별로 400/409/422가 혼재. - "TTL은 길수록 안전하다" — 길수록 클라이언트 retry window는 늘지만 스토리지 비용과 키 추측 공격면도 함께 증가.
Project Application
- wiki/projects/ca-tmpl/idempotency-key-design — ca-tmpl 의사결정 기록 (현재
documented-only, Phase C2 미진입). 실제 구현 여부는 project 문서 참조. - raw/branch-notes/feature-rate-limit-idempotency-contract — key shape / TTL / 저장소 SSOT (triple scope + DB table + 24h TTL + 200ms wait + 422 fingerprint mismatch + 409 in-flight 결정의 owning branch).
- raw/branch-notes/feature-api-contract-baseline —
Idempotency-KeyHTTP header 표준 (consume only, shape은 위 branch가 owns). - raw/project-notes/ca-skeleton-operational-contract §29 Topic 5 — 비교표·결정 라인.
Interview Questions
- Q1.
useCaseName(또는 endpoint) dimension을 scope에 포함시키는 이유는? Stripe v1 pair에서 어떤 충돌이 발생할 수 있는가? - Q2. TTL을 24h로 잡은 trade-off는? PayPal 45일·Stripe v2 30일과 비교했을 때 어떤 비용·위험을 줄이고, 어떤 use case(예: 결제·송금 long-running)에서는 부족한가?
- Q3. 동시 도착 요청에 대해 200ms wait를 둔 의미는? IETF draft의 즉시 409와 비교했을 때 client retry 동작이 어떻게 달라지는가?
- Q4. 같은 key + 다른 body를 422로, in-flight 충돌을 409로 분리한 이유는? 두 상황을 같은 코드로 합치면 어떤 클라이언트 버그가 가려지는가?
- Q5. key가 클라이언트 생성 unique value라면 추측 공격면은 어떻게 평가해야 하는가? TTL이 길수록 공격면이 어떻게 변하고, AWS Powertools content-hash 방식은 이 문제를 어떻게 우회하는가?
Do Not Overclaim
- "ca-tmpl triple이 Stripe pair보다 무조건 안전하다"고 말하지 않습니다. v1 pair 한정 비교이며, Stripe v2 triple과는 사실상 동급.
- "ca-tmpl이 IETF Idempotency-Key spec을 완전히 준수한다"고 단정하지 않습니다. draft 단계이고, 422 fingerprint mismatch는
SHOULD이며, ca-tmpl의 200ms wait는 draft의 "즉시 409" 권고와 다른 선택입니다. - "Square가 표준 미준수라서 열등하다"고 단정하지 않습니다. body 필드 방식은
cancel-by-idempotency-key처럼 키를 resource handle로 쓰는 API 디자인의 장점이 있습니다. - "AWS Powertools content-hash가 header 방식의 상위 호환"이라고 말하지 않습니다. body의 사소한 변경(여백/필드 순서/timestamp)이 다른 operation으로 분류되므로 canonicalization 정책이 함께 가야 동작합니다.
- "Brandur lock 패턴을 그대로 채택했다"고 말하지 않습니다. ca-tmpl은 200ms wait + unique constraint hybrid이며 Brandur
locked_atlock의 변형입니다. - ca-tmpl 24h TTL이 "업계 표준"이라고 표현하지 않습니다. Stripe v1 최소값과 일치할 뿐이고, 다른 도메인 reference는 모두 더 길게 잡습니다.
Sources
공식 / 표준
- IETF draft — The Idempotency-Key HTTP Header Field — 422/409 status code 근거, "resource owner가 scope 정의" 권한 위임.
- Stripe API Reference — Idempotent requests — v1 pair / v2 triple scope, 24h–30d TTL, 5xx replay.
- Square API — Idempotency (Common API patterns) — body 필드 방식, fingerprint mismatch error.
- PayPal — Idempotency —
PayPal-Request-Id, 45일 TTL. - AWS Lambda Powertools — Idempotency utility — content-hash 기반.
- GitHub REST API — API-level dedup 없음.
구현 reference
- Brandur Leach — Implementing Stripe-like Idempotency Keys in Postgres — atomic phase +
locked_atlock.
raw 보존본
- raw/official-docs/idempotency-ietf-draft
- raw/official-docs/idempotency-stripe-api-ref
- raw/official-docs/idempotency-square-api
- raw/official-docs/idempotency-paypal-docs
- raw/official-docs/idempotency-aws-lambda-powertools
- raw/official-docs/idempotency-no-api-level-github-rest
- raw/company-tech-blogs/idempotency-brandur-stripe-postgres
- raw/company-tech-blogs/idempotency-toss-payments-techblog
- raw/company-tech-blogs/idempotency-redis-vs-db-storage