재시도에 옵션을 넘겨도 저장된 옵션이 없으면 소용없다

AI Commons의 영상 재시도는 같은 슬라이드와 보이스로 다시 생성한다. 하지만 옵션으로 빈 객체를 보내면 음성 모델과 stability 같은 값이 기본값으로 돌아갈 수 있다. 변경은 이전 VideoJob.tracksJson에서 옵션을 찾아 큐에 넘기고, 새 재시도 행에도 같은 옵션을 저장했다.

두 테스트는 옵션 객체가 든 행과 null 행 뒤에 옵션이 있는 이력을 준비했다. 큐 인자와 새 작업의 옵션 저장은 확인했지만, 워커가 그 컬럼을 나중에 어떻게 쓰는지는 포함하지 않았다. 이 변경만으로 옵션 승계가 완성됐다고 볼 수 없었던 이유다.

진행률 갱신이 생성 옵션을 통째로 교체했다

실제 createProgressUpdater는 tracksJson에 stage, currentStep, totalPages, timestamp 객체를 대입했다. 생성 시 저장한 옵션과 진행 갱신이 같은 컬럼을 공유했다. 워커를 거친 행을 재시도가 읽으면 옵션 대신 진행 정보가 들어온다.

  1. 생성 요청

    tracksJson에 음성 설정과 pageGap을 저장한다.

  2. 워커의 진행 갱신

    같은 컬럼을 진행 정보 객체로 바꿔 옵션을 지운다.

  3. 재시도

    값이 있는 첫 행을 고르면 진행 정보를 옵션으로 넘길 수 있다.

입력 fixture가 이미 원하는 옵션 모양이면 재시도 함수만 검사해서는 이 충돌을 발견할 수 없다. 원인은 JSON 필드의 파싱이 아니라 두 쓰기 경로가 같은 저장 위치를 사용한 데 있었다.

진행 정보는 다른 컬럼에 쓰고 과거 행은 구분해서 읽는다

후속 변경은 새 진행 정보를 metaJson으로 옮겼다. 재시도는 최신 작업부터 확인하되 stage나 timestamp가 문자열이면 과거 진행 정보로 보고 건너뛴다. 아래는 실제 판별식이다.

return typeof obj.stage === "string" || typeof obj.timestamp === "string";

옵션 키의 목록으로 판별하면 새 옵션을 추가할 때마다 복구 규칙도 바꿔야 한다. 진행 정보의 표지를 사용하면 그 결합을 줄일 수 있다. 다만 미래 옵션이 같은 이름을 쓰면 다시 충돌하므로 이 이름의 구분은 계속 유지해야 한다.

저장 위치를 바꾼 뒤의 읽기 계약
데이터새 작업과거 작업
생성 옵션tracksJson 유지진행 모양을 제외하고 남은 옵션 탐색
진행 정보metaJson화면에서 tracksJson fallback

잡 DTO는 두 컬럼을 모두 반환하고 화면은 metaJson ?? tracksJson으로 진행 정보를 고른다. 변경 당시 진행 중이던 과거 행도 표시할 수 있게 한 호환 경로다. 이미 덮어써서 사라진 옵션을 복원하는 마이그레이션은 아니다. 옵션이 남은 행이 없으면 빈 옵션으로 재큐잉한다.

쓰기 결과를 닮은 fixture가 필요한 경계다

후속 테스트는 워커의 실제 진행 모양만 남은 행에서 빈 옵션을 쓰는 경우, 진행 행을 건너뛰고 이전 옵션 행을 선택하는 경우를 추가했다. 진행 updater가 tracksJson을 쓰지 않는지, DTO가 metaJson을 반환하는지, 화면이 새 위치와 과거 위치를 모두 읽는지도 확인한다.

이 검사는 저장 위치와 호환 분기의 계약을 다룬다. 실제 TTS 음색의 동일성이나 지워진 원옵션 복구까지 증명하지는 않는다. 시도와 최종 실패의 상태 계약이 재시도 가능 여부를 정한다면, 여기서는 재시도가 어떤 입력을 이어받는지와 그 입력을 다른 쓰기가 보존하는지를 함께 봐야 했다.