버전 관리 없는 저장소, 승인 트랜잭션 완료 뒤 최종 경로로 복사
핵심 요약
- Dev Community 기술 해설은 AI 이미지 생성 재시도 때 같은 저장 키를 쓰면 결과물이 덮어써질 수 있다고 설명했다. 작업 이력 관리와 서비스별 복구·전달 기능 점검도 제시했다.
게임에서 AI 이미지를 생성하다 재시도할 때 같은 저장 키를 쓰면, 먼저 만든 이미지가 덮어써지고 이후 정리 작업에서 결과물까지 삭제될 수 있다. Dev Community에 게재된 Node.js 게임 이미지 저장 해설은 생성 시도마다 고유한 키를 부여하고, 데이터베이스에서 채택할 결과를 관리하는 방식을 제시했다.
핵심은 생성 중인 이미지와 게임에 표시할 최종 이미지의 저장 경로를 분리하는 데 있다. 먼저 변경하지 않을 시도별 키로 이미지를 업로드한다. 이어 해당 시도를 승인 상태로 기록하는 데이터베이스 트랜잭션이 성공한 뒤, 채택된 객체를 최종 경로로 복사한다. 복사를 별도 단계로 두면 데이터베이스 트랜잭션이 실패했는데도 승인되지 않은 이미지가 최종 경로에 노출되는 일을 막을 수 있다. 버전 관리가 없는 객체 저장소는 실수로 덮어쓴 이미지를 자체적으로 복구하지 못한다.
오류는 업로드 성공 여부를 확인하는 과정에서 시작될 수 있다. 작업 프로세스가 이미지 A를 생성해 업로드한 뒤 응답 확인 중 시간 초과를 겪으면, 재시도가 같은 키에 이미지 B를 저장할 수 있다. 데이터베이스가 여전히 A의 생성 기록을 가리키는 상태에서 정리 작업이 폐기된 시도의 키를 삭제하면, 그 자리에 있던 B도 사라진다. 원문 저자는 이 유형의 문제에서 업로드 경로는 200 응답을 반환했지만 나중에 읽을 때는 404 오류가 발생하는 사례를 경험했다고 설명했다.
고정 파일명은 그 자체로 중복 실행을 안전하게 처리하는 멱등성 키가 되지 않는다. player-42/loading-screen.png를 반복 사용하면 같은 이름의 객체를 덮어쓰게 된다. 대신 users/42/jobs/8f31/attempts/0002.png처럼 생성 시도를 구별하는 경로를 쓰고, 데이터베이스에 작업과 프롬프트 해시, 모델 결과, 현재 채택된 시도를 기록해야 한다. 검색할 작업 상태도 저장소 메타데이터에만 맡기지 말고 Postgres나 게임 데이터베이스에서 관리하도록 권고했다.
클라이언트가 504 오류를 받거나 연결이 끊겨 재시도할 때도 첫 PUT 요청이 완료됐는지는 오류만으로 판단할 수 없다. 업로드 전에 키를 만들고 시도 기록과 함께 보관해야 한다. 동일한 시도에 객체를 최대 하나만 두려는 경우에만 그 시도의 키를 다시 사용하고, 생성 단계에서 게임의 최종 표시용 키를 재사용하지 않는 방식이다. 클라이언트가 제공한 시도 식별자는 재시도 이력을 추적하는 데도 쓰인다.
원문 예제는 시도 번호와 UUID를 조합한 경로에 이미지를 올린 뒤 데이터베이스에서 승인 여부를 결정한다. 비교 후 갱신하는 원자적 연산에서 채택 경쟁에 밀린 시도는 감사와 정리를 위해 남기고, 승인된 객체만 users/{user_id}/latest.png로 복사한다. 다만 예제의 승인 함수는 실제 데이터베이스 트랜잭션으로 교체해야 한다. 원문이 다루는 저장 모델에는 If-Match 조건부 쓰기와 객체 버전 관리가 없어, 엄격한 상호 배제는 큐나 데이터베이스의 비교 후 갱신 연산으로 처리해야 한다.
재시도 간격과 응답 상태 확인도 필요하다. 예제는 최대 5차례 요청하며, 429 응답을 받으면 Retry-After 헤더가 지정한 시간만큼 기다리거나 헤더가 없을 때 시도 횟수에 따라 대기 시간을 늘린다. 촘촘한 재시도는 일시적인 요청 제한을 여러 경쟁 업로드로 키울 수 있다. 거부된 쓰기 응답을 객체가 존재한다는 증거로 받아들여서도 안 된다. 금전적·법적으로 중요한 이미지라면 복구용 사본을 별도의 영속 저장 시스템에도 보관하도록 권했다.
저장 서비스에 따라 충돌 방지와 복구에 활용할 수 있는 기능은 달라진다. 원문은 AWS S3가 버전 관리와 조건부 요청을 지원해 최종 키 관리와 복구에 강점이 있지만 IAM과 서비스 설정 부담이 따른다고 비교했다. 클라우드플레어(Cloudflare) R2는 데이터 전송 비용과 S3 호환성이 중요한 경우 매력적인 선택지로 제시했다. 다만 고유 키를 명시적으로 설계하고 클라이언트가 의존하는 S3 동작의 지원 여부를 확인해야 한다. Google Cloud Storage는 객체 세대와 보존 제어 기능을 제공하며, 기존 시스템이 GCP에 있을 때 적합하지만 공급자별 API와 IAM을 고려해야 한다고 설명했다.
Infrai와 같은 통합 API는 AI 생성과 저장을 같은 키 및 과금 체계로 연결하고 하나의 REST 접점에서 연동할 수 있다는 장점이 있다. 원문에 따르면 공개된 기능 안내에는 요청 스키마와 실행 가능한 예제가 포함된다. 그러나 통합의 편의성이 버전 관리 기능을 추가해 주지는 않는다. 원문은 Infrai 저장소에 버전 관리, 객체 잠금, 서버 측 메타데이터 검색이 없으며, 목록 조회도 접두어로만 필터링한다고 설명했다. 프롬프트 해시나 검토 상태를 메타데이터에 넣더라도 검색 가능한 작업 상태는 별도 데이터베이스에 남겨야 한다.
이미지 전달 방식 역시 선택 기준이다. 원문에서 다룬 저장 서비스는 public 또는 public-read ACL을 제공하지 않아 영구 공개 URL을 쓰는 이미지 호스팅을 그대로 대체하기 어렵다. 브라우저 직접 업로드에는 CORS 설정이 필요하지만, 이를 직접 관리할 독립된 설정 경로도 공개돼 있지 않다고 설명했다. 서명된 읽기 요청이나 프록시로 이미지를 전달하는 비공개 게임 백엔드에서는 수용할 수 있어도, 공개 갤러리에는 필요한 전달 제어 기능을 갖춘 공급자를 선택하도록 권고했다.
운영 전에는 업로드마다 작업 ID, 시도 키, 응답 상태, 요청 ID를 기록하고 클라이언트 시간 초과를 강제로 일으켜 재시도 동작을 점검해야 한다. 재시도는 의도한 시도 기록을 생성하거나 이어가야 하며, 파일명 재사용 때문에 다른 이미지가 조용히 선택돼서는 안 된다. 최종 경로로 복사하는 순서가 데이터베이스 커밋 뒤에 오는지도 확인해야 한다.
삭제는 보존 기간과 정리 정책을 정한 뒤 진행해야 한다. 원문 저자는 이용자 개인정보, 검토에 필요한 시간, 고객 지원 수요에 따라 적절한 보존 기간이 달라질 수 있다고 봤다. 접두어를 기준으로 한 정리는 보존 기간이 지난 뒤 수행하고 정책을 테스트하도록 권고했다. 이 저장 모델의 수명 주기 만료는 시간이 아닌 일 단위로 계산되며, 멀티파트 업로드 조각에는 자동 정리 규칙이 없어 별도 정리 작업에도 명시적인 정책이 필요하다. GDPR 제17조에 따라 최종 이미지와 모든 시도 객체를 지워야 할 수 있어 개인정보 삭제 절차와 연계한 점검도 제시했다.
WORM 보존, 리전 간 복제, 임의 출처에서의 브라우저 업로드, 이전 객체로의 즉시 복원이 필수라면 이 저장소만으로는 요구를 충족하기 어렵다는 게 원문의 판단이다. 해당 제어 기능이 반드시 필요하면 S3나 GCS를 사용하거나 별도의 보관·조정 계층을 추가하도록 권했다. 생성 시도마다 고유 키를 부여하는 방식은 어느 저장 서비스를 택하든 적용할 수 있다.
