스테이징에서 100번의 드라이 런(dry-run)을 성공했지만, 첫 실전 실행에서 프로덕션(production)을 망가뜨린 경험
요약
스테이징 환경과 프로덕션 환경의 불일치 및 에이전트의 비결정적 동작으로 인해 발생한 데이터 오염 사고와 해결책을 다룹니다. 드라이 런(dry-run) 시 플래그를 전파하여 후속 쓰기 작업을 강제로 제어하는 구조적 설계의 중요성을 강조합니다.
핵심 포인트
- 환경 드리프트와 에이전트의 비결정적 경로로 인해 스테이징 테스트만으로는 프로덕션 오류를 막기 어려움
- 모의 응답(mock responses)이 에이전트를 속여 불완전한 쓰기 작업을 유발할 수 있음
- 드라이 런 플래그를 실행 컨텍스트(runId)에 전파하여 모든 후속 쓰기 작업을 강제해야 함
- 훅(hook) 실패 시 기본 동작이 실행되지 않도록 별도의 알림 및 예외 처리가 필수적임
스테이징(staging)에서 프로덕션(production)으로의 데이터 유출로 인해 4시간 동안 롤백(rollback)을 해야 했습니다. 이 사건은 드라이 런(dry-run)을 사후 고려 사항이 아닌 구조적 요구 사항으로 만들게 된 결정적인 계기가 되었습니다.
일반적인 조언은 스테이징에서 테스트하고, 상태가 양호하면(green) 프로덕션으로 승격시키라는 것입니다. 문제는 환경 드리프트(environment drift)입니다. 저의 D1 스키마(schema)는 일주일에 한두 번씩 변경되는데, 1인 운영자가 스테이징을 완벽하게 동기화 상태로 유지하기란 불가능합니다. 더 나쁜 점은 에이전트(agent)가 고정된 실행 경로를 갖지 않는다는 것입니다. 동일한 입력이라도 다음 실행 시에는 다른 도구 호출(tool call) 시퀀스를 생성할 수 있습니다. 저는 스테이징에서 한 흐름을 100번이나 실행했지만, 프로덕션에서의 첫 실행에서 여전히 새로운 경로를 마주했습니다.
이 시스템을 6개월간 운영하며 배운 가장 놀라운 점은 지연 시간(latency)이 제가 예상했던 문제가 아니었다는 것입니다. KV 쓰기(write)는 평균 12ms로, 기본적으로 인지할 수 없는 수준이었습니다. 진짜 문제는 모의 응답(mock responses)이 에이전트를 속여, 건너뛴 쓰기 작업을 실제 성공으로 간주하게 만든다는 점이었습니다. 제가 R2 put 작업을 드라이 런(dry-run)으로 실행하면, 에이전트는 파일이 업로드되었다고 믿고 나서 드라이 런 범위에 포함되지 않은 D1에 메타데이터를 쓰는 작업을 진행했습니다. 실제 쓰기가 발생했고, 고아 레코드(orphaned record)가 생성된 것입니다.
해결책은 다음과 같습니다: 실행 중 어떤 쓰기 도구(write tool)라도 드라이 런(dry-run)에 걸리면, 해당 runId에 플래그를 전파하여 동일한 실행 내의 모든 후속 쓰기 작업도 드라이 런으로 강제하는 것입니다.
// 첫 번째 드라이 런 쓰기를 가로챈 후
await ctx.env.KV.put(`dryrun_active:${ctx.runId}`, "1", {
expirationTtl: 3600,
...
저를 또 한 번 괴롭혔던 점은, 만약 훅(hook) 자체가 실패할 경우(예: KV가 일시적으로 사용 불가능할 때) Claude Code의 기본 동작이 그대로 통과(fall-through)된다는 것이었습니다. 도구 호출(tool call)은 드라이 런 플래그를 무시하고 그대로 실행됩니다. 지난주 KV 스파이크(spike)로 인해 훅 타임아웃(hook timeout)이 발생했고, 3개의 에이전트가 프로덕션에 직접 데이터를 썼습니다. 해당 작업들이 멱등성(idempotent)을 가졌기에 데이터 손실은 없었지만, 운이 좋았을 뿐입니다. 훅 실패는 에이전트 실패와는 별도로 자체적인 알림(alert)이 필요합니다.
드라이 런(dry-run) 전파의 엣지 케이스(edge cases), R2 + D1 고아 시나리오, 그리고 이 패턴이 완전히 무너지는 지점(읽기-수정-쓰기 루프, 부수 효과가 있는 읽기(side-effectful reads)를 포함하는 API)을 포함한 전체 분석 내용은 riversealab.com에 작성해 두었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기