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