이 페이지에서 다루는 것
AI timeout retry idempotency
한 번에 끝까지 읽으며 맥락을 쌓을 수 있도록 구성했습니다.
AI가 만든 느린 외부 연동, 결제형 작업, AI 생성 기능을 timeout budget, retry policy, idempotency key, 상태 전이, 중복 방지 테스트로 안전하게 운영하는 VIBE 코딩 실전 루프
학습 유형
주제 심층 학습
핵심 주제
AI timeout retry idempotency
키워드
AI timeout retry idempotency · VIBE 코딩 안정성 · idempotency key · retry policy · timeout budget · 중복 요청 방지
이 페이지에서 다루는 것
AI timeout retry idempotency
한 번에 끝까지 읽으며 맥락을 쌓을 수 있도록 구성했습니다.
예상 학습 시간
16분
본문과 보조 자료(이미지·영상)를 포함한 대략적인 소요입니다.
학습 팁
섹션 순서대로 읽고, 필요한 부분만 다시 찾아보기
표·이미지·영상은 본문 흐름을 돕는 보조 설명입니다.
외부 결제, 알림, 이미지 생성, 메일 발송, 파일 변환, 검색 색인, AI 추론처럼 시간이 걸리는 기능을 AI에게 맡기면 처음에는 데모가 잘 돌아가는 것처럼 보입니다. 그러나 실제 사용자가 동시에 버튼을 누르고, 모바일 네트워크가 끊기고, 외부 서비스가 20초 뒤에 응답하고, 브라우저가 같은 요청을 다시 보내는 순간 문제가 달라집니다. timeout은 너무 길어 화면을 멈추게 만들고, retry는 같은 주문을 두 번 만들며, 실패 처리는 사용자가 새로고침할 때마다 중복 작업을 쌓습니다.
초보자는 이 문제를 “느린 요청을 안전하게 다시 시도하는 법”으로 이해하면 됩니다. 실무자는 더 구체적으로 봐야 합니다. 각 작업은 timeout budget, retry policy, idempotency key, 중복 요청 처리, 작업 상태 저장, 사용자 안내, 로그 상관관계, 승인 기준, 중단 기준을 가져야 합니다. AI timeout·retry·idempotency 루프는 AI가 만든 비동기 기능이 느린 네트워크와 외부 장애에서도 사용자 데이터와 비용을 망가뜨리지 않게 만드는 제작 방식입니다.
AI에게 “실패하면 재시도해 줘”라고 지시하면 위험합니다. 재시도는 성공률을 높이는 도구이지만, 중복 실행이 안전하지 않은 작업에서는 장애를 두 배로 키울 수 있습니다. 결제 승인, 쿠폰 발급, 이메일 발송, 데이터 삭제, AI 크레딧 차감, 파일 업로드 완료 처리처럼 부작용이 있는 작업은 먼저 idempotency key와 작업 상태를 정한 뒤 retry를 붙여야 합니다.
핵심 순서는 단순합니다. 첫째, 요청이 얼마나 기다릴 수 있는지 timeout budget을 정합니다. 둘째, 같은 요청을 식별할 idempotency key를 만듭니다. 셋째, 서버가 같은 key를 다시 받았을 때 새 작업을 만들지 않고 기존 결과나 진행 상태를 돌려주게 합니다. 넷째, 재시도 가능한 오류와 즉시 중단해야 할 오류를 나눕니다. 다섯째, UI는 “완료”, “처리 중”, “다시 시도 가능”, “사람 승인 필요”를 구분해서 보여 줍니다.
이 루프가 있으면 AI가 만든 기능을 운영자가 안심하고 켤 수 있습니다. 사용자는 버튼을 두 번 눌러도 같은 결과를 받습니다. 외부 서비스가 느려도 화면은 영원히 멈추지 않습니다. 로그에는 같은 요청을 추적할 correlation id와 idempotency key가 남습니다. 비용이 드는 AI 호출은 실패한 척하면서 뒤에서 계속 중복 실행되지 않습니다. 제품의 신뢰도는 “성공했을 때 예쁜 화면”보다 “불안정할 때도 같은 약속을 지키는가”에서 결정됩니다.
AI 에이전트는 사용자가 요구한 happy path를 빠르게 구현하는 데 강합니다. 버튼을 누르면 서버 함수를 호출하고, 서버 함수는 외부 서비스를 부르고, 성공하면 결과를 화면에 보여 줍니다. 하지만 실제 운영에서는 성공 경로보다 애매한 상태가 더 어렵습니다. 요청은 서버까지 도착했지만 응답이 브라우저에 오기 전에 네트워크가 끊길 수 있습니다. 외부 서비스는 작업을 접수했지만 30초 뒤에 webhook으로 결과를 줄 수 있습니다. 사용자는 진행 상태를 보지 못해 같은 버튼을 다시 누를 수 있습니다.
이때 timeout과 retry만 추가하면 문제가 해결되는 것처럼 보입니다. 그러나 부작용이 있는 요청에서 retry는 “같은 의도를 한 번 더 실행”하는 것이 아니라 “새 작업을 하나 더 생성”할 수 있습니다. 결제라면 중복 승인, 알림이라면 중복 발송, AI 추론이라면 중복 과금, DB 변경이라면 중복 레코드가 됩니다. 그래서 재시도보다 먼저 같은 의도를 식별하는 idempotency 설계가 필요합니다.
즉시 실패하는 요청은 사용자가 다시 시도할 수 있고 로그도 명확합니다. 반대로 느린 실패는 더 위험합니다. 화면은 로딩 중이고, 서버는 외부 응답을 기다리며, 브라우저는 연결을 끊었고, 백그라운드 작업은 완료되었는지 알기 어렵습니다. 이런 상태가 쌓이면 사용자는 여러 번 누르고 운영자는 중복 처리를 수동으로 정리해야 합니다.
timeout budget은 이 혼란을 줄이는 약속입니다. 예를 들어 사용자가 보는 화면 요청은 8초 안에 “완료 또는 처리 중”을 반환하고, 실제 장기 작업은 job id로 이어 받으며, 외부 서비스 재시도는 백그라운드에서 제한 횟수와 지수 backoff로만 수행한다고 정할 수 있습니다. 이 약속이 있으면 화면, 서버, 작업 큐, 로그, 고객 안내가 같은 기준으로 움직입니다.
AI 기능은 비용이 직접 연결되는 경우가 많습니다. 이미지 생성, 긴 문서 요약, 벡터 색인, 음성 변환, 모델 추론은 한 번 실행할 때마다 돈과 시간이 듭니다. 네트워크 실패 때마다 새 요청을 보내면 사용자는 결과를 하나만 보더라도 내부 비용은 여러 번 발생합니다. 더 나쁘게는 서로 다른 결과가 만들어져 어떤 것을 사용자에게 보여 줄지 판단하기 어려워집니다.
idempotency key는 “같은 사용자의 같은 의도”를 안정적으로 묶습니다. 클라이언트가 key를 만들 수도 있고 서버가 초안 단계에서 key를 발급할 수도 있습니다. 중요한 것은 key가 요청 본문, 사용자, 작업 종류, 시간 범위와 함께 저장되어야 한다는 점입니다. 같은 key로 다른 본문이 들어오면 안전하게 거부해야 하고, 같은 본문이 다시 들어오면 기존 상태를 반환해야 합니다. 이것이 중복 비용과 중복 부작용을 막는 가장 현실적인 장치입니다.
먼저 기능을 네 가지로 나눕니다. 읽기 요청, 부작용이 있지만 빠른 요청, 부작용이 있고 느린 요청, 장기 작업입니다. 검색 자동완성은 읽기 요청이라 짧은 timeout과 취소가 중요합니다. 댓글 작성은 부작용이 있지만 보통 빠른 요청이라 중복 제출 방지가 중요합니다. 결제 확인이나 AI 이미지 생성은 부작용이 있고 느릴 수 있으므로 idempotency와 상태 저장이 필수입니다. 대용량 파일 분석은 장기 작업이므로 즉시 결과를 기다리기보다 job 상태 페이지가 필요합니다.
각 분류마다 사용자 기대 시간을 씁니다. 화면이 3초 안에 반응해야 하는가, 10초까지 기다려도 되는가, 아니면 즉시 “처리 중”을 보여 주고 나중에 알림으로 끝내야 하는가를 정합니다. AI에게 구현을 맡기기 전에 이 시간 기준을 주지 않으면 모델은 임의로 긴 대기, 무한 로딩, 또는 너무 공격적인 재시도를 만들 수 있습니다.
하나의 요청에는 여러 시간 제한이 필요합니다. 브라우저에서 사용자가 기다릴 최대 시간, 서버가 외부 서비스를 기다릴 최대 시간, 백그라운드 작업이 한 번 실행될 최대 시간, 전체 작업이 포기되는 최대 시간이 다릅니다. 예를 들어 화면은 8초, 서버 외부 호출은 6초, 작업 큐의 단일 실행은 45초, 전체 만료는 15분처럼 나눌 수 있습니다.
중요한 원칙은 안쪽 timeout이 바깥 timeout보다 짧아야 한다는 것입니다. 화면이 8초 뒤에 포기하는데 서버가 60초 동안 외부 응답을 기다리면 사용자는 실패를 보지만 서버는 계속 돈을 쓰고 있을 수 있습니다. 반대로 서버가 6초 안에 “처리 중”을 저장하고 job id를 반환하면 화면은 안정적으로 다음 상태를 보여 줄 수 있습니다.
idempotency key는 단순한 랜덤 문자열이 아니라 저장 정책과 함께 설계해야 합니다. key, 사용자 식별자, 작업 종류, 요청 fingerprint, 상태, 결과 요약, 생성 시간, 만료 시간을 저장합니다. fingerprint는 요청 본문에서 순서나 공백 같은 무의미한 차이를 제거하고 핵심 파라미터만 정규화해 만든 비교값입니다. 같은 key인데 fingerprint가 다르면 같은 의도로 볼 수 없으므로 거부해야 합니다.
클라이언트가 key를 만들 때는 버튼 클릭마다 새 key를 만들지, 작성 중인 초안마다 고정 key를 만들지 정해야 합니다. 예를 들어 “주문 제출”은 사용자가 확인 버튼을 누르는 순간 하나의 key를 만들고 성공 또는 실패 확정까지 유지합니다. “AI 초안 다시 생성”은 사용자가 새 결과를 의도할 때마다 새 key를 만들어야 합니다. 같은 UI라도 의도가 다르면 key 수명도 다릅니다.
서버에는 최소한 accepted, processing, succeeded, failed_retryable, failed_final, needs_human_review 같은 상태가 필요합니다. 모든 기능에 같은 이름을 쓸 필요는 없지만 상태 전이가 닫혀 있어야 합니다. accepted에서 processing으로, processing에서 succeeded 또는 failed_retryable로, failed_retryable에서 다시 processing 또는 failed_final로 갈 수 있다는 식입니다. succeeded에서 다시 processing으로 돌아가는 전이는 보통 금지해야 합니다.
AI가 구현할 때는 상태 전이 표를 먼저 주는 것이 좋습니다. “같은 idempotency key로 succeeded 상태가 있으면 새 외부 호출을 하지 말고 저장된 결과를 반환한다. processing 상태면 새 작업을 만들지 말고 현재 상태와 다음 확인 시간을 반환한다. failed_retryable 상태면 사용자가 다시 시도할 수 있지만 같은 key의 재시도 횟수 제한을 넘기면 failed_final로 전환한다.”처럼 적으면 모델이 중복 작업을 만들 가능성이 줄어듭니다.
모든 오류를 재시도하면 안 됩니다. 네트워크 일시 장애, 429, 502, 503, timeout은 제한된 횟수로 재시도할 수 있습니다. 인증 실패, 권한 없음, 잘못된 입력, quota 부족, 정책 위반, fingerprint 불일치, 이미 완료된 작업의 변경 시도는 재시도해도 성공하지 않습니다. 특히 비용이 드는 작업은 “결과를 모르는 timeout”과 “실제로 실패한 응답”을 구분해야 합니다.
재시도에는 상한이 필요합니다. 최대 횟수, 전체 시간, backoff 간격, jitter, 동시 실행 제한을 정합니다. “3회 재시도”만 적으면 세 요청이 동시에 몰릴 수 있습니다. “1초, 3초, 9초 간격에 jitter를 더하고 같은 key는 한 번에 하나만 실행한다”처럼 구체적으로 지시해야 합니다. 사용자가 수동으로 누르는 재시도 버튼도 같은 제한을 따라야 합니다.
화면은 단순히 성공 또는 실패만 보여 주면 안 됩니다. accepted나 processing 상태에서는 “처리 중이며 이 화면을 닫아도 계속됩니다”라고 안내해야 합니다. failed_retryable 상태에서는 사용자가 다시 시도할 수 있는 이유와 예상 영향을 보여 줍니다. failed_final 상태에서는 같은 버튼을 계속 누르지 않게 고객 지원, 입력 수정, 또는 새 요청 생성 경로를 제시합니다.
중요한 것은 사용자가 불안해서 같은 동작을 반복하지 않도록 하는 것입니다. 버튼을 누른 뒤에는 idempotency key가 살아 있는 동안 중복 제출을 막거나, 다시 눌러도 “이미 처리 중인 요청”으로 연결해야 합니다. 새로고침 뒤에도 상태를 복구할 수 있게 job id나 요청 id를 URL, 세션 상태, 서버 조회 경로 중 하나로 연결합니다.
운영자는 사용자 신고를 받았을 때 “실제로 몇 번 실행되었는가”를 알아야 합니다. 로그에는 correlation id, idempotency key의 해시값, 작업 종류, 사용자 범주, 상태 전이, 외부 호출 시도 횟수, timeout 발생 지점, 최종 결과가 남아야 합니다. 민감한 원문 요청이나 인증값은 저장하지 않습니다. key 자체도 외부에 그대로 노출하지 않고 운영 로그에서는 식별 가능한 해시나 짧은 추적값으로 충분한 경우가 많습니다.
대시보드 지표는 성공률만 보면 부족합니다. timeout 비율, retry 후 성공률, 중복 key 재사용률, fingerprint 불일치 건수, failed_retryable에서 failed_final로 넘어간 비율, 평균 처리 시간, p95 처리 시간, 비용성 작업의 중복 방지 건수를 함께 봐야 합니다. 이 지표가 있어야 retry가 실제로 도움이 되는지, 아니면 외부 장애를 숨기며 비용만 늘리는지 판단할 수 있습니다.
사용자가 “이미지 생성”을 누르면 외부 모델 호출이 5초 안에 끝날 수도 있고 40초가 걸릴 수도 있습니다. 화면이 그 시간을 모두 기다리면 모바일 브라우저에서는 실패처럼 보입니다. 안전한 설계는 버튼 클릭 때 generationRequestId를 만들고 서버가 accepted 상태를 저장한 뒤 빠르게 job id를 돌려주는 방식입니다. 서버는 같은 requestId가 다시 오면 새 이미지를 만들지 않고 기존 상태를 반환합니다.
AI에게는 이렇게 지시합니다. “이미지 생성 요청은 idempotency key를 필수로 받고, 같은 사용자와 같은 key의 요청이 processing 또는 succeeded이면 외부 모델을 다시 호출하지 말라. 요청 본문 fingerprint가 다르면 409 계열의 충돌 오류로 처리하라. 화면은 8초 안에 완료되지 않으면 처리 중 상태를 표시하고 상태 조회를 이어가라. 사용자가 새 이미지를 의도할 때만 새 key를 만들라.”
검증은 중복 클릭 테스트로 합니다. 같은 버튼을 빠르게 세 번 눌러도 서버의 외부 모델 호출은 한 번이어야 합니다. 브라우저 새로고침 뒤에도 같은 job 상태가 보여야 합니다. 외부 모델 timeout을 강제로 만들었을 때 UI는 실패가 아니라 처리 중 또는 재시도 가능 상태를 표시해야 합니다. 비용 로그에는 중복 차감이 없어야 합니다.
결제는 가장 조심해야 하는 부작용입니다. 사용자가 결제 버튼을 누른 뒤 응답을 받기 전에 네트워크가 끊길 수 있습니다. 이때 새 결제를 만들면 중복 승인 위험이 있습니다. 안전한 구조는 주문 id와 결제 idempotency key를 먼저 만들고, 결제 제공자의 결과와 내부 권한 부여를 별도 상태로 관리하는 것입니다.
작업 순서는 결제 요청 accepted, 외부 승인 processing, 승인 succeeded, 내부 권한 부여 pending, 권한 부여 succeeded처럼 나눕니다. 외부 결제는 성공했지만 내부 권한 부여가 실패하면 결제를 다시 시도하는 것이 아니라 권한 부여 작업만 재시도해야 합니다. AI가 이 경계를 놓치면 “전체 결제 함수를 다시 호출”하는 코드를 만들 수 있으므로 상태 전이를 명시해야 합니다.
중단 기준도 필요합니다. 같은 주문에 succeeded 결제가 있으면 새 결제 생성은 금지합니다. fingerprint가 다르면 사람 검토가 필요합니다. 결제 제공자가 성공을 반환했는데 내부 DB 저장이 실패한 경우에는 사용자에게 “확인 중”을 보여 주고 운영 재처리 큐로 보냅니다. 사용자가 다시 눌러도 같은 주문 상태를 조회해야 합니다.
외부 서비스는 webhook을 여러 번 보낼 수 있습니다. 이것은 오류가 아니라 정상 동작일 수 있습니다. 서버가 webhook event id를 저장하지 않으면 같은 이벤트를 받을 때마다 알림을 발송하거나 상태를 반복 변경할 수 있습니다. webhook은 event id 또는 서명된 delivery id를 idempotency 기준으로 사용하고, 이미 처리한 이벤트는 200 응답과 함께 무시하는 것이 안전합니다.
AI에게는 “webhook handler는 event id 기준으로 중복 처리 방지 테이블을 확인하고, 처리 전 received 상태를 기록하며, 성공 후 processed로 바꿔라. 이미 processed인 이벤트는 부작용을 다시 실행하지 말고 성공 응답을 반환하라. 처리 중 서버가 죽은 이벤트는 일정 시간 뒤 retryable로 분류하라”라고 지시합니다. 이때 원문 payload 전체를 로그에 남기지 않도록 주의합니다.
검증은 같은 webhook payload를 두 번 보내는 테스트로 합니다. 첫 번째는 상태 변경과 알림 발송이 일어나고, 두 번째는 아무 부작용 없이 중복 처리로 기록되어야 합니다. 처리 중 예외를 강제로 발생시킨 뒤 재전송이 올 때 같은 이벤트가 한 번만 최종 처리되는지도 봐야 합니다.
삭제 작업은 재시도보다 승인 기준이 중요합니다. 사용자가 “삭제”를 눌렀는데 네트워크가 끊겼다고 다시 누르게 만들면 같은 리소스가 이미 사라진 상태에서 이상한 오류를 볼 수 있습니다. 삭제는 soft delete, pending delete, grace period, audit trail을 고려해야 합니다. 즉시 영구 삭제가 필요한 경우라도 같은 delete request id를 기준으로 결과를 반복 반환해야 합니다.
UI는 “삭제 요청 접수”, “삭제 완료”, “이미 삭제됨”을 구분해야 합니다. 서버는 같은 key의 삭제 요청이 이미 succeeded이면 성공으로 응답할 수 있습니다. 그러나 같은 key로 다른 리소스를 삭제하려는 요청은 거부해야 합니다. AI가 범용 retry wrapper를 모든 mutation에 붙이지 않도록 “삭제 계열은 자동 재시도 금지, 상태 조회와 사용자 확인 우선”이라고 지시합니다.
재시도에는 항상 최대 횟수와 전체 만료 시간이 있어야 합니다. 외부 서비스가 장애일 때 모든 사용자의 클라이언트와 서버가 동시에 재시도하면 트래픽 폭탄이 됩니다. 특히 AI 기능은 호출 비용이 크기 때문에 실패를 숨기려고 계속 시도하면 예산과 신뢰를 동시에 잃습니다. retry storm을 막으려면 backoff, jitter, 동시성 제한, 회로 차단 상태가 필요합니다.
회로 차단은 어려운 개념처럼 보이지만 원리는 간단합니다. 최근 5분 동안 timeout과 5xx가 기준을 넘으면 새 외부 호출을 잠시 중단하고 사용자에게 지연 안내를 보여 줍니다. 이미 접수된 작업은 큐에서 천천히 처리하고, 새 요청은 accepted 상태로만 저장하거나 잠시 받지 않습니다. AI에게 “장애 때도 계속 호출하지 말고 중단 조건을 만들라”고 지시해야 합니다.
문제가 느리다고 timeout을 120초로 늘리면 사용자는 더 오래 기다릴 뿐입니다. 서버 자원도 오래 붙잡히고, 브라우저와 프록시의 제한에 걸릴 가능성이 커집니다. 긴 작업은 동기 요청으로 해결하지 말고 상태 기반 비동기 흐름으로 분리해야 합니다. 화면은 빠르게 응답하고, 결과는 상태 조회나 알림으로 이어 받는 방식이 운영에 강합니다.
짧은 timeout도 무조건 좋은 것은 아닙니다. 외부 서비스가 정상적으로 7초가 필요한데 서버 timeout을 3초로 잡으면 실패율이 올라갑니다. 따라서 timeout은 감이 아니라 측정값으로 조정해야 합니다. p50, p95, 최악의 사용자 경험, 비용, 외부 서비스 제한을 함께 보고 결정합니다.
key가 너무 넓으면 사용자가 실제로 새 작업을 의도했는데 이전 결과가 재사용됩니다. 예를 들어 “다시 생성” 버튼이 같은 key를 계속 쓰면 사용자는 새 결과를 얻지 못합니다. key가 너무 좁으면 중복 방지가 되지 않습니다. 버튼을 누를 때마다 새 key를 만들면 더블클릭을 막을 수 없습니다. 의도 단위가 무엇인지 제품 흐름별로 정해야 합니다.
또한 key 저장 기간을 정해야 합니다. 결제나 권한 부여는 긴 보관이 필요할 수 있고, 자동완성 요청은 짧게 버려도 됩니다. 저장 기간이 끝난 key가 다시 들어오면 새 요청으로 볼지, 만료 오류를 줄지 정합니다. 이 정책을 문서화하지 않으면 운영자는 장애 시점에 같은 요청이 왜 다시 실행되었는지 설명하기 어렵습니다.
가장 위험한 상태는 “외부 서비스에 요청을 보냈지만 결과를 받지 못한 timeout”입니다. 이 경우 실제로 외부 작업은 성공했을 수 있습니다. 바로 실패로 표시하고 새 요청을 만들면 중복 결과가 생길 수 있습니다. 안전한 처리는 unknown 상태를 두고 외부 조회, webhook 대기, 또는 사람 검토로 이어 가는 것입니다.
AI에게 “timeout이면 failed로 저장”이라고 지시하지 마십시오. 대신 “외부 호출 전 상태를 processing으로 기록하고, timeout이면 outcome_unknown으로 전환한 뒤 상태 조회 또는 webhook으로 확정하라. 사용자는 새 요청을 만들기보다 기존 요청 확인 화면으로 이동시켜라”라고 지시합니다. 이름은 팀에 맞게 바꿔도 되지만 결과 미확정 상태는 반드시 필요합니다.
운영에는 correlation id와 key 해시가 필요하지만 공개 화면에 내부 식별자를 그대로 보여 줄 필요는 없습니다. 사용자는 “처리 중”, “다시 시도 가능”, “확인 필요” 같은 의미 있는 상태를 원합니다. 고객 지원이 필요할 때는 짧은 문의 코드 정도만 제공하고, 내부 로그와 연결되는 원문 식별자는 안전하게 다룹니다. AI가 디버깅 편의를 위해 내부 값을 화면에 출력하지 않도록 명시해야 합니다.
다음 예시는 그대로 복사하기보다 프로젝트 용어에 맞게 바꾸어 쓰는 것이 좋습니다.
결제형 또는 비용성 작업을 구현하기 전에 timeout·retry·idempotency 설계를 먼저 작성하라. 요청은 idempotency key, 사용자 범위, 작업 종류, 요청 fingerprint를 저장해야 한다. 같은 key와 같은 fingerprint가 다시 들어오면 새 외부 호출을 만들지 말고 기존 상태나 결과를 반환하라. 같은 key와 다른 fingerprint는 충돌로 거부하라.
상태 전이는 accepted, processing, outcome_unknown, succeeded, failed_retryable, failed_final, needs_human_review 중 필요한 것만 사용하라. timeout은 즉시 최종 실패로 단정하지 말고 outcome_unknown 또는 재확인 상태로 기록하라. retry는 네트워크 일시 장애, 429, 502, 503, timeout에만 제한적으로 적용하고, 잘못된 입력과 권한 문제와 quota 문제는 자동 재시도하지 말라.
UI는 8초 안에 완료되지 않는 작업을 처리 중 상태로 전환하고, 사용자가 새로고침해도 같은 작업 상태를 복구해야 한다. 버튼 중복 클릭은 같은 key로 연결하되 사용자가 새 결과를 의도하는 “다시 생성” 흐름은 새 key를 발급하라. 공개 화면에는 내부 추적값을 그대로 노출하지 말고, 고객 지원용 짧은 문의 코드만 제공하라.
테스트를 먼저 작성하라. 같은 key를 두 번 보내도 외부 호출이 한 번만 발생하는 테스트, fingerprint가 다르면 거부되는 테스트, timeout이 outcome_unknown으로 저장되는 테스트, webhook 중복 수신이 한 번만 처리되는 테스트, retry 상한을 넘으면 최종 실패나 사람 검토로 전환되는 테스트를 포함하라. 테스트가 실패하는 것을 확인한 뒤 구현하라.
다음 조건 중 하나라도 있으면 배포나 공개 전환을 멈춥니다. 같은 idempotency key로 외부 부작용이 두 번 실행됩니다. timeout 뒤에 실제 성공 여부를 알 수 없는데 새 작업을 자동 생성합니다. fingerprint가 다른 요청을 같은 key로 받아들입니다. retry가 무제한이거나 동시성 제한 없이 실행됩니다. 공개 화면에 내부 추적값이 그대로 보입니다. 비용성 작업의 중복 실행 여부를 로그로 확인할 수 없습니다.
또한 외부 서비스 장애 때 회로 차단 없이 모든 요청이 계속 몰리는 구조라면 중단해야 합니다. 테스트 환경에서 더블클릭, 새로고침, 모바일 네트워크 끊김, webhook 중복 수신을 재현하지 못했다면 “아직 검증되지 않은 기능”으로 분류합니다. AI가 만든 코드를 사람이 읽었을 때 상태 전이를 설명할 수 없다면 구현이 너무 암묵적이라는 신호입니다.
승인 기준은 구체적이어야 합니다. 같은 key 중복 요청 100회에서 외부 부작용이 정확히 1회만 발생합니다. timeout 주입 테스트에서 작업은 outcome_unknown 또는 처리 중 상태로 남고 사용자는 상태 조회 경로를 받습니다. retry는 정해진 횟수와 시간 안에서만 실행됩니다. 4xx와 권한 문제는 자동 재시도하지 않습니다. 대시보드에서 timeout 비율과 중복 방지 건수를 확인할 수 있습니다.
운영 승인 전에는 실제 사용자 시나리오로 한 번 더 봅니다. 사용자가 버튼을 두 번 눌렀을 때 화면 문구가 불안감을 줄이는가, 새로고침해도 같은 작업으로 돌아오는가, 실패 후 다시 시도 버튼이 새 작업을 만드는지 기존 작업을 이어 가는지 명확한가, 고객 지원이 문의 코드를 받아 상태 전이를 찾을 수 있는가를 확인합니다. 이 기준을 통과하면 timeout·retry·idempotency 루프는 단순 방어 코드가 아니라 제품 신뢰도를 지키는 운영 장치가 됩니다.
이미 운영 중인 기능이 있다면 가장 먼저 비용성 작업과 되돌리기 어려운 mutation부터 점검합니다. 결제, 권한 부여, 파일 변환, AI 생성, 알림 발송, 삭제 작업을 목록화하고 각 기능에 timeout budget, idempotency key, retry policy, 상태 전이, 중복 처리 테스트가 있는지 표시합니다. 없는 항목은 새 기능 개발보다 우선순위를 높입니다.
새 기능을 만들 때는 기획 문서에 “부작용이 있는가”, “사용자가 기다릴 수 있는 시간은 얼마인가”, “같은 의도를 어떻게 식별하는가”, “결과를 모르는 timeout을 어떻게 처리하는가”, “언제 사람 승인으로 넘기는가”를 포함합니다. AI에게는 이 답을 구현 프롬프트와 테스트 조건으로 함께 줍니다. 그러면 VIBE 코딩은 빠른 데모 제작을 넘어 느린 네트워크와 외부 장애에서도 안전하게 반복 가능한 제작 루프가 됩니다.
같은 의도로 같은 요청이 다시 들어와도 결과가 한 번만 만들어지게 하는 약속입니다. 버튼 더블클릭, 새로고침, 네트워크 재시도 때문에 서버가 같은 작업을 두 번 실행하지 않도록 key와 상태를 저장합니다.
네트워크 일시 오류, 429, 502, 503, timeout처럼 시간이 지나면 회복될 수 있는 오류에 제한된 횟수와 backoff로 붙입니다. 입력 오류, 권한 문제, quota 문제, fingerprint 불일치처럼 다시 시도해도 성공하지 않는 오류에는 자동 retry를 붙이지 않습니다.
외부 서비스에 요청이 도착했지만 응답만 못 받은 경우 실제 작업은 성공했을 수 있습니다. 그래서 비용성 작업이나 부작용 작업은 outcome_unknown 또는 처리 중 상태로 두고 외부 조회, webhook, 사람 검토로 확정하는 것이 안전합니다.
아닙니다. 버튼 비활성화는 사용자 경험을 돕지만 네트워크 재전송, 새로고침, 여러 탭, webhook 중복 수신, 서버 재시작을 막지 못합니다. 서버가 idempotency key와 상태 전이를 기준으로 중복 부작용을 막아야 합니다.
짧은 읽기 요청에는 필요하지 않을 수 있습니다. 그러나 외부 호출이 느리거나 비용이 크거나 되돌리기 어려운 부작용이 있다면 동기 요청만으로 버티기보다 상태 저장과 비동기 처리 흐름을 검토하는 것이 안전합니다.
같은 idempotency key로 같은 요청을 두 번 보내도 외부 부작용이 한 번만 실행되는 테스트가 우선입니다. 그다음 fingerprint 충돌, timeout 결과 미확정 상태, webhook 중복 수신, retry 상한 초과 테스트를 추가하면 실전 장애를 많이 줄일 수 있습니다.
다음 학습
AI가 외부 API 연동 코드를 만들 때 가장 자주 놓치는 것은 “정상 응답”이 아니라 “응답이 늦거나, 일부만 실패하거나, 과금 한도 때문에 막히는 순간”입니다. 로컬에서는 버튼을 눌렀을 때 한 번 성공하니 기능이 완성된 것처럼 보입니다. 하지만 실제 서비스에서는 외부 모델 호출, 결제 승인, 이메일 발송, 검색 인덱스 조회, 이미지 생성, 지도 조회처럼 다른 시스템에 의존하는 작업이 많습니다. 이때 타임아웃, 재시도, 폴백, 사용자 안내, 로그 기준이 없으면 AI가 만든 기능은 데모에서는 멋지고 운영에서는 불안한 기능이 됩니다.
이 글의 목표는 외부 API를 “부르면 끝”이 아니라 “실패해도 통제 가능한 제품 흐름”으로 설계하는 것입니다. 초보자는 API 호출 코드보다 먼저 실패 화면을 떠올리면 이해가 쉽습니다. 전문가는 호출 경계, 예산…
AI에게 기능을 맡기면 화면은 빠르게 완성됩니다. 버튼도 보이고, API도 응답하고, 배경 작업도 돌아가는 것처럼 보입니다. 그런데 실제 사용자가 쓰기 시작하면 ‘가끔 저장이 안 된다’, ‘알림은 갔는데 화면에는 없다’, ‘결제는 성공했는데 상태가 대기 중이다’ 같은 애매한 장애가 생깁니다. 이때 로그가 흩어져 있으면 AI도 사람도 추측으로 움직입니다. 프론트엔드 콘솔, 서버 로그, 큐 워커 로그, DB 변경 이력, 외부 API 응답을 서로 다른 시간대와 다른 문장으로 뒤지다가 결국 ‘한 번 더 재현해 보자’로 끝납니다.
상관관계 ID 디버깅 루프는 이 문제를 해결하기 위한 VIBE 코딩 운영 습관입니다. 사용자의 한 행동 또는 한 자동화 실행에 correlation ID를 붙이고, 그 ID가 브라우저 이벤트, API 요청, 서버 핸들러, 백…