생성이 끝나지 않으면 이미 받은 요청도 사용량에서 빠진다

슬라이드 이미지와 영상 사용량은 생성이 완료된 뒤 카운터에 더해진다. 한도가 10장이고 완료 사용량이 8장일 때, 이미 2장을 생성 중이라면 다음 요청이 볼 잔여량은 달라야 한다. 완료 카운터만 읽으면 이 2장이 아직 없는 것처럼 판단한다.

AI Commons에서는 생성 허용 판단에 요청자 본인의 대기·처리 중 작업 예상량을 더했다. 생성 가드와 사전 확인에 같은 계산을 사용하고, 실제 제출 시점에도 다시 검사한다. 완료분 used와 진행분 pending은 따로 유지해, 확정 사용량과 추정을 화면과 응답에서 구분한다.

판정 테스트의 입력과 기대값
검사완료분본인 진행분요청량·한도기대 판정
총량 진입 가드82한도 10총량 도달로 차단
요청량을 아는 사전 확인53요청 3, 한도 10잔여량 부족으로 차단
개인 진입 가드41개인 한도 5개인 한도 도달로 차단

진입 가드는 완료+진행 사용량이 한도에 도달했는지 보고, 작업량을 아는 사전 확인은 완료+진행+요청이 한도를 넘는지 본다. 같은 카운터를 읽어도 요청량을 포함할 수 있는 시점의 판단은 더 구체적이다.

작업 수와 사용량은 같은 단위가 아니다

대상 상태는 QUEUED와 PROCESSING이다. 이미지 일괄 생성은 슬라이드의 totalPages를 최소 1장으로 보정하고, 단일 이미지 재생성은 1장으로 센다. 소유자는 잡 자체의 필드가 아니라 slide.userId 또는 video.userId 관계로 좁힌다.

영상은 TTS_COMPOSE 잡의 슬라이드 스크립트와 언어로 시간을 추정한다. 한국어는 분당 400자, 영어는 분당 150단어를 기준으로 초를 계산한 뒤, 페이지별 초를 합쳐 분 단위로 올림한다. 슬라이드가 연결된 영상의 최소 추정은 1분이며, 슬라이드가 없는 영상은 이 방식으로 추정할 수 없어 제외한다. 완성된 영상의 실제 길이와 같은 값이라고 볼 수는 없다.

총량과 개인 한도는 같은 요청에서 진행량을 한 번만 조회해 재사용한다. 유한 한도를 검사할 때 조회하고, 양쪽 모두 무제한이면 조회를 생략한다. 아래는 이 지연 조회 부분이다.

let pendingCache: number | undefined;
const getPending = async (): Promise<number> => {
  if (!includeOwnInFlight) return 0;
  if (pendingCache === undefined) {
    pendingCache = await estimateInFlightUsage(prisma, user, kind);
  }
  return pendingCache;
};

진행량을 더하지 않는 표시 경로와 무제한 단축 경로는 기존 동작을 유지한다. 강화한 판단에는 잡·스크립트 조회 비용과 시간 추정 오차가 추가된다. 공유한 계산 함수는 사전 확인과 진행량의 산식 차이를 줄여 주지만, 그 추정을 확정값으로 바꾸지는 않는다.

관측한 진행량을 더해도 다음 요청의 몫을 예약하지는 않는다

이 조회가 더하는 값은 본인의 이미 기록된 진행 작업이다. 기관 총량 판단에도 다른 사용자의 모든 진행량을 합쳐 예약하지 않는다. 동시에 들어온 두 요청이 아직 잡을 만들지 않았다면, 양쪽 조회에 상대 요청이 보이지 않을 수 있다. 조회, 승인, 잡 생성은 하나의 원자 연산이 아니다.

더 강한 제한에는 승인과 함께 사용량을 예약하고, 실패·취소 때 해제하며, 완료 뒤 실제 사용량으로 정산하는 규칙이 필요하다. 이번 변경은 완료 시점의 가산 정책과 일시 초과를 허용하는 정책을 유지하면서, 이미 관측할 수 있는 본인 작업의 누락을 줄였다.

판정 테스트는 DB와 추정 함수를 mock으로 두고 위 합산·차단 분기를 검사한다. 별도 추정 테스트는 일괄/단일 이미지, 0페이지 보정, 소유자·상태 필터, 영상 스크립트와 슬라이드 미연결을 다룬다. 이 검사는 실제 두 요청의 동시 승인 경합이나 생성 영상 길이의 추정 오차를 측정하지 않는다.