프로덕션 백엔드 API 캡스톤 · 데이터 무결성과 재시도 · 이론
재시도는 새 명령이 아니다
한 줄 요약
클라이언트가 응답을 받지 못해 같은 요청을 다시 보내도 서버는 같은 명령으로 인식해야 한다. 트랜잭션과 조직 범위 멱등 키를 함께 사용하면 주문은 한 번만 생성되고 재시도는 처음 만들어진 리소스를 돌려준다.
왜 타임아웃이 중복 주문을 만드는가
서버가 커밋한 직후 네트워크가 끊기면 클라이언트는 성공을 모른다. 무조건 재시도하면 두 번째 주문이 생긴다. “요청을 한 번만 보내라”는 해결책은 분산 시스템에서 지킬 수 없다. 대신 클라이언트가 Idempotency-Key를 보내고 서버가 조직과 함께 저장한다. 첫 쓰기와 키 기록은 같은 트랜잭션이어야 하며, 둘 중 하나만 성공하는 상태가 없어야 한다.
PostgreSQL의 INSERT ... ON CONFLICT (org_id, idempotency_key)는 경쟁 요청도 데이터베이스 직렬화 지점에서 합친다. 먼저 조회하고 나중에 삽입하는 코드는 두 요청이 동시에 “없음”을 보고 모두 삽입하는 경쟁 조건이 생긴다. 충돌 경로는 새 입력 금액으로 기존 주문을 덮으면 안 된다. 원래 주문을 반환해 같은 명령의 결과가 안정적으로 유지되어야 한다.
현장에서 실패를 다루는 법
드라이버 연결과 커서는 컨텍스트 매니저로 닫고, 쓰기는 명시적 transaction 경계에 둔다. 예외가 나면 rollback을 보장하고 예외 메시지에 SQL 인자나 연결 문자열을 넣지 않는다. 통합 테스트는 같은 키를 다른 금액으로 두 번 보내 첫 응답이 201, 재시도가 200이며 ID와 원래 금액이 같은지 확인한다. 다른 조직은 같은 키를 독립적으로 쓸 수 있어야 한다.
실무 판단 기준
멱등성은 “중복에서 에러가 난다”보다 강한 계약이다. 사용자가 결과를 안전하게 다시 받을 수 있어야 한다. 키 보존 기간, 요청 본문 해시 충돌 정책, 오래된 키 정리도 실제 서비스에서는 명시한다. 이번 캡스톤은 가장 중요한 생성 경계와 충돌 의미를 먼저 증명하고, 다음 모듈에서 이를 실제 HTTP 상태와 본문 계약으로 외부에 노출한다.