프로덕션 백엔드 API 캡스톤 · 데이터 무결성과 재시도 · 이론
스키마는 마지막 방어선이다
한 줄 요약
프로덕션 데이터 계약은 애플리케이션의 if가 아니라 PostgreSQL 제약으로 끝까지 보존해야 한다. 주문 API라면 양수 금액, 소유자와 조직, 조직 안에서 유일한 멱등 키, 생성 시각을 데이터베이스가 직접 보장해야 한다.
왜 애플리케이션 검사만으로 부족한가
쓰기 경로는 하나로 시작해도 곧 배치, 관리자 도구, 복구 스크립트와 새 서비스가 추가된다. 첫 API가 금액을 검사해도 다른 경로가 음수 값을 넣으면 데이터는 이미 오염된다. NOT NULL, CHECK, UNIQUE, 외래 키는 어느 클라이언트가 접근하든 같은 규칙을 적용한다. 특히 멱등 키는 전역이 아니라 조직 범위여야 서로 다른 고객이 같은 키를 사용해도 충돌하지 않는다. 그래서 UNIQUE (org_id, idempotency_key)가 비즈니스 경계를 더 정확히 표현한다.
PostgreSQL 16의 GENERATED ALWAYS AS IDENTITY는 SQLite의 정수 기본 키와 다른 실제 운영 문법이다. TIMESTAMPTZ는 저장된 순간을 하나의 시간축으로 비교하게 한다. 인덱스는 조회 패턴을 따라 org_id, owner_id, created_at 순서로 설계한다. 마이그레이션은 BEGIN과 COMMIT 사이에서 실행해 중간 상태를 노출하지 않는다.
현장에서 검증하는 방법
DDL 텍스트에 단어가 있는지만 보지 않는다. 빈 격리 스키마에 마이그레이션을 실제 적용하고, 정상 행을 넣고, 같은 조직과 같은 키의 두 번째 행이 unique_violation인지 확인한다. 음수 금액이 check_violation인지도 실행한다. 이때 운영 데이터와 섞이지 않도록 임시 스키마를 만들고 끝나면 제거한다. 같은 검증을 CI에서 반복할 수 있어야 “내 컴퓨터에서는 됐다”를 배포 증거로 바꿀 수 있다.
실무 판단 기준
제약은 오류를 늦추는 장벽이 아니라 잘못된 상태가 저장되지 않게 하는 계약이다. 오류 코드는 API에서 의미 있는 409나 400으로 번역하되 데이터베이스 제약 자체를 없애지는 않는다. 롤백 전략도 생각해야 하지만, 먼저 순방향 마이그레이션이 새 빈 DB와 기존 데이터 조건을 모두 만족하는지 증명한다. 다음 레슨에서는 이 불변식을 트랜잭션과 재시도 의미에 연결한다.