비동기(큐) 추론 — submit으로 대량 요청을 안정적으로

비동기(큐) 추론 — submit으로 대량 요청을 안정적으로

fal에서 프로덕션 규모 워크로드에 권장되는 방식이 바로 비동기(큐) 추론이에요. 요청을 큐에 쌓아두고 상태를 폴링하거나 웹훅으로 결과를 받는 구조라서, 병렬 처리가 필요하거나 요청이 많아질 때도 안정적으로 돌릴 수 있어요. 갤러리의 사전 훈련된 모델을 부르든 Serverless로 배포한 자신의 앱을 부르든 동작은 완전히 동일해요 — 요청 제출, 상태 확인, 스트리밍 업데이트, 결과 조회, 취소, 웹훅 설정까지 전부 같은 규칙을 따르죠.

출처: fal.ai Asynchronous Inference (Queue)

큐가 동작하는 방식

요청은 들어오는 순서대로 큐에 쌓이고 상태가 단계별로 바뀌어요.

Status SDK 타입 (Python / JS) 의미
IN_QUEUE Queued(position) / "IN_QUEUE" 요청을 받아 저장함. 쓸 수 있는 러너를 기다리는 중
IN_PROGRESS InProgress(logs) / "IN_PROGRESS" 러너가 요청을 처리 중
COMPLETED Completed(logs, metrics) / "COMPLETED" 결과가 저장돼 조회 가능하거나 웹훅으로 전송됨

큐에 있는 요청은 절대 버려지지 않아요. 러너가 없으면 요청은 대기하고, 그 사이 fal이 새 러너를 자동으로 스케일업해요. 큐 크기 상한도 없어요. 처리 중 러너가 실패(503, 504, 연결 에러)하면 요청은 자동으로 다시 큐에 들어가 최대 10회까지 재시도돼요.

submit — 요청 제출

submit으로 요청을 큐에 보내면 즉시 반환돼요. Python에서는 submit()SyncRequestHandle 객체를 돌려주고(submit_async()AsyncRequestHandle), JavaScript에서는 request_id가 담긴 객체를 돌려줘요.

import fal_client

handler = fal_client.submit(
    "fal-ai/flux/schnell",
    arguments={"prompt": "a sunset over mountains"},
)
print(handler.request_id)

제출 응답에는 request_id와 함께 추적용 URL들이 딸려 나와요.

{
  "request_id": "764cabcf-b745-4b3e-ae38-1200304cf45b",
  "response_url": "https://queue.fal.run/fal-ai/flux/schnell/requests/764cabcf.../response",
  "status_url": "https://queue.fal.run/fal-ai/flux/schnell/requests/764cabcf.../status",
  "cancel_url": "https://queue.fal.run/fal-ai/flux/schnell/requests/764cabcf.../cancel"
}

request_id는 나중에 상태를 확인하거나 결과를 다시 가져와야 할 때 쓰이니 꼭 저장해 두세요. 다른 프로세스에서도 이 값만 있으면 계속 추적할 수 있어요.

상태 확인과 결과 조회

폴링으로 현재 상태를 확인할 수 있어요. 러너 로그까지 받으려면 with_logs=True(Python), logs: true(JS), REST에서는 ?logs=1을 붙이면 돼요. REST의 스트리밍 엔드포인트는 text/event-stream(SSE) 형식으로 상태를 보내는데, 상태가 COMPLETED에 도달할 때까지 커넥션이 열려 있어요.

결과는 처리가 끝난 뒤 가져와요. Python의 get()은 내부적으로 Completed가 될 때까지 폴링한 뒤 응답을 받아오고, JavaScript는 상태가 COMPLETED가 되면 fal.queue.result()를 호출해요.

result = handler.get()
print(result["images"][0]["url"])

비디오 생성 모델은 video 객체를, 오디오·음성 모델은 audio_url이나 audio 객체를 돌려줘요. 정확한 출력 스키마는 각 모델의 API 페이지에서 확인할 수 있어요.

취소와 웹훅

취소 요청은 HTTP 202 Accepted를 돌려주고 응답 본문은 {"status": "CANCELLATION_REQUESTED"}예요. 이미 처리 중이었으면 요청이 끝까지 완료될 수도 있어요. 존재하지 않는 ID로 취소하면 404{"status": "NOT_FOUND"}를 받아요.

웹훅을 쓰면 폴링 없이 결과가 서버로 자동 전송돼요. submit()webhook_url을 넘기면 돼요.

import fal_client

handler = fal_client.submit(
    "fal-ai/flux/schnell",
    arguments={"prompt": "a sunset over mountains"},
    webhook_url="https://your-server.com/webhook",
)
print(f"Request submitted: {handler.request_id}")

처리가 끝나면 fal이 POST로 웹훅 URL에 요청을 보내요. 페이로드에는 request_id·gateway_request_id·status·payload(전체 모델 출력)가 들어 있어요. 웹훅의 status"OK"(성공, HTTP 200) 또는 "ERROR"(실패)로, 큐 상태 값(IN_QUEUE 등)과는 별개예요. 알림을 받았다는 표시로 200을 빨리 돌려주는 게 좋고, fal이 실패한 전송을 재시도할 수 있으니 request_id로 멱등성을 처리하세요.

submit() 파라미터

  • path — 모델 ID 뒤에 붙는 엔드포인트 경로예요. 대부분의 모델은 루트 엔드포인트 하나만 노출하니 비워 두면 돼요. 모델이나 자신의 앱이 하위 경로에 추가 엔드포인트를 정의한 경우에만 써요.
  • start_timeout — 러너가 처리를 시작할 때까지의 서버 측 데드라인(초)이에요.
  • hint — 같은 러너로 라우팅하게 하는 세션 어피니티 힌트예요.
  • priority — 큐 우선순위예요. 엔드포인트별 큐에 적용돼서 "low"로 두면 normal 우선순위 요청들 뒤로 밀려요.
  • webhook_url — 처리 완료 시 결과를 보낼 URL이에요. 설정하면 폴링 없이 결과가 도착해요.
  • headersX-Fal-No-Retry 같은 플랫폼 제어용 추가 헤더예요.

subscribe()submit()의 위 파라미터들을 모두 받으면서, 추가로 client_timeout/timeout(클라이언트가 포기하는 총 시간 상한)을 지원해요.

재시도 끄기

X-Fal-No-Retry 헤더를 1·true·yes로 설정하면 재시도 가능한 에러가 나도 요청을 재시도하지 않아요. REST로는 헤더를 직접 붙이면 되고, SDK에서는 headers={"X-Fal-No-Retry": "1"}로 전달해요.

curl -X POST "https://queue.fal.run/fal-ai/flux/dev" \
  -H "Authorization: Key $FAL_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Fal-No-Retry: 1" \
  -d '{"prompt": "a cat"}'

더 알아보기