CKAD — 쿠버네티스 애플리케이션 개발자 · 배치는 성공했는데 택배는 두 번 나갔다 · 이론
중복을 기억할 곳은 Pod 밖에 있다
한 줄 요약
재시도를 안전하게 만들려면 실행 ID와 업무 ID를 구분하고, 부작용을 저장하는 경계에서 같은 업무 키를 일관되게 처리해야 합니다.
왜 이게 필요했나
배송 프로그램이 시작할 때 무작위 UUID를 만들고 이미 처리한 요청인지 조회하도록 바꿨다고 합시다. UUID 충돌 확률은 작지만 중복 배송은 그대로일 수 있습니다. 새 Pod가 실행될 때마다 새 UUID를 만들기 때문입니다. 같은 주문의 재시도가 다른 업무처럼 보이는 것입니다. 식별자가 유일하다는 성질과 같은 업무를 같은 것으로 알아본다는 성질은 다릅니다.
Pod 안의 임시 파일에 처리 완료를 적어 두는 방법도 재시도 경계를 넘지 못할 수 있습니다. Job이 새 Pod를 만들면 이전 실행의 메모리나 로컬 파일이 그대로 따라온다고 가정할 수 없습니다. 실습은 이 차이를 숨기지 않도록 각 요청에 Pod UID를 기록하되 배송 중복 판단에는 별도의 업무 키를 사용합니다.
어떻게 동작하나
학습용 요청에는 네 가지 값이 있습니다. case_id는 독립된 실험 업무 범위이고 work_id는 그 범위의 논리적 주문입니다. pod_uid는 요청을 보낸 실행을 추적하며 mode는 비교군과 멱등 처리를 구분합니다. 같은 업무를 재실행할 때 case_id와 work_id는 유지하고, 새 Pod UID는 달라져야 합니다. 실제 서비스에서는 계정·테넌트·작업 종류를 포함한 키 범위와 재사용 정책을 따로 정해야 합니다.
원장은 요청 시도와 배송 부작용을 다른 표로 관리합니다. 요청은 두 번 들어왔더라도 배송 결과는 한 번만 만들 수 있습니다. 같은 키를 다시 받으면 첫 배송 번호를 돌려주고 새 요청 시도는 계속 기록합니다. 덕분에 중복을 제거했다고 관측 정보까지 지워 버리지 않습니다.
핵심은 조회와 저장 사이의 경쟁입니다. 두 요청이 거의 동시에 도착해 각각 “없다”를 읽은 뒤 새 배송을 저장하면 중복이 생길 수 있습니다. 실습 원장은 SQLite 쓰기 트랜잭션 안에서 키 조회와 삽입을 처리하고, 멱등 키에는 유일성 제약을 둡니다. 클라이언트의 if 문 하나로 동시성 안전을 대신하지 않습니다. 오류가 나면 해당 트랜잭션은 성공한 배송으로 보고하지 않습니다.
실행 추적: Pod A → 요청 1 Pod B → 요청 2업무 판정: 같은 업무 키 → 같은 배송 번호이것을 임의의 정확히 한 번 전달 보장이라고 부르지는 않습니다. 예제는 원장 내부에서 한 종류의 행 삽입을 한 번으로 합칩니다. 실제 택배사·메일 서버·다른 DB까지 원장의 트랜잭션에 자동으로 참여하지는 않습니다. 외부 시스템의 멱등 API, 결과 조회, 아웃박스, 재조정 같은 추가 계약이 필요한 경우가 있습니다. 어떤 저장 경계를 보호했는지 설명할 수 있어야 합니다.
현장에서 만나는 모습
운영자가 완료된 Job과 다른 이름의 Job을 새로 만들어 같은 주문을 재실행할 수 있습니다. Job UID를 멱등 키로 쓰면 새 실행은 새 키가 되므로 이전 부작용을 알아보지 못합니다. 반대로 업무 키를 유지하면 Kubernetes 객체가 달라도 같은 결과를 재사용할 수 있습니다. 실행 객체를 지우는 것과 업무 처리 이력을 지우는 것이 다른 수명 주기인 이유입니다.
모든 요청을 같은 키로 묶는 것도 잘못입니다. 서로 다른 주문까지 같은 배송으로 합쳐지면 중복은 줄었지만 정상 업무를 잃었습니다. 시험은 같은 키의 반복뿐 아니라 다른 키가 각각 처리되는지도 확인해야 합니다. 같은 키인데 본문 내용이 달라지는 경우를 허용할지 거절할지, 키를 얼마나 오래 보관할지도 실제 계약의 일부입니다. 이 실습의 작은 요청 모델에는 주소·품목이 없으므로 그런 충돌 정책을 구현했다고 주장하지 않습니다.
CronJob은 정해진 시각에 Job을 만드는 역할이고, 멱등 원장의 역할을 대신하지 않습니다. concurrencyPolicy를 Forbid로 정하더라도 같은 CronJob이 만든 Job의 겹침을 제한하는 설정이지 외부 원장에 대한 중복 처리 계약은 아닙니다. 다른 CronJob, 수동 Job, 운영자의 재실행 경로까지 업무 키가 이어지는지 따로 점검해야 합니다.
저장소의 생존성도 구분합니다. 실험의 원장은 별도 Pod의 emptyDir 안 SQLite에 있습니다. 작업 Pod 교체에는 영향을 받지 않지만 원장 Pod 자체가 교체되면 데이터가 사라집니다. 따라서 원장 Pod UID와 프로세스 boot_id가 바뀌지 않았는지 확인합니다. 이 구성으로 영구 보존·다중 노드 고가용성·재해 복구까지 해결한 것은 아닙니다.
다음 실습에서 할 것
같은 저장 후 실패를 유지한 채 원장의 멱등 처리를 켭니다. 요청 두 번과 배송 한 건을 함께 확인하고, 다른 Job으로 같은 업무를 다시 실행해도 배송 번호가 유지되는지 봅니다. 마지막에는 원장 시도 목록을 지우거나 성공 표시만 읽지 말고, 서로 다른 업무는 남고 중복 업무만 합쳐졌는지 설명하세요.
공식 문서와 적용 범위
- [CronJob의 실행 특성](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#job-creation)
- [SQLite 트랜잭션](https://www.sqlite.org/lang_transaction.html)
- [SQLite 유일성 제약](https://www.sqlite.org/lang_createtable.html#unique_constraints)
Kubernetes의 실행 특성과 애플리케이션의 저장 계약을 연결하는 교재입니다. 멱등성을 파일 하나의 존재 여부나 Job 성공 조건으로 축소하지 않습니다.