AI 시스템에 백프레셔(Backpressure)가 필요한 이유
요약
AI 시스템에서 모델 성능 저하 시, 과부하로 인한 작업 누적을 방지하기 위해 백프레셔(Backpressure) 메커니즘이 필수적입니다. 이는 다운스트림 용량 제약에 따라 업스트림 프로듀서가 속도를 늦추거나 요청 수용을 중단하게 만듭니다. 적절한 큐 관리와 로드 셰딩은 시스템 안정성을 유지하는 핵심 요소입니다.
핵심 포인트
- 백프레셔는 다운스트림 용량 제약으로 업스트림 프로듀서의 속도를 조절합니다.
- 과부하 시 요청이 누적되어 지연 시간이 길어지고 서비스 품질을 저하시킬 수 있습니다.
- 큐에는 만료 정책(queue age)을 적용하여 유효하지 않은 오래된 작업을 폐기해야 합니다.
- 실행 예산, 입장 제어, 부하 분산 등 다양한 제어 장치를 조합하여 시스템 안정성을 확보할 수 있습니다.
모델이 느려질 때, AI 서비스는 허용된 작업량을 사용 가능한 용량 내로 유지해야 합니다. 작업을 덜 받거나 용량을 더 늘려야 합니다. 그렇지 않으면 미완료된 작업들이 쌓여 제품의 나머지 부분까지 영향을 미칩니다. 결과가 여전히 가치가 있을 때까지만 대기 시간을 제한해야 합니다. 비용이 많이 드는 다운스트림(downstream) 작업을 시작하기 전에 요청을 거부할지 결정해야 합니다.
백프레셔(Backpressure)는 다운스트림 용량 제약으로 인해 업스트림(upstream) 프로듀서가 속도를 늦추거나, 기다리거나, 작업 수용을 중단하게 만드는 현상입니다. 워커(worker)는 읽기 속도를 늦출 수 있고, 프로듀서는 사용 가능한 공간을 기다릴 수 있습니다. API는 요청을 거부함으로써 과부하를 알릴 수 있습니다. 로드 셰딩(Load shedding)은 프로듀서가 부하를 줄임으로써 응답할 때만 효과적인 백프레셔 메커니즘이 됩니다. 호출자들이 즉시 재시도한다면, 서버는 계속해서 부하를 줄이는 동안 프로듀서는 요청을 보내고 있습니다.
느린 호출이 누적된 작업으로 변하는 경우
지원 답변 기능(support-reply feature)을 고려해 봅시다. 이 기능은 티켓을 로드하고, 정책 문서를 검색하며, 초안을 생성하고, 결과를 검증합니다. 배치 워커는 더 오래된 티켓을 요약하기 위해 동일한 모델 배포를 사용할 수 있습니다.
들어오는 트래픽이 꾸준히 유지되는 동안 모델이 느려지면, 완료되는 작업 수가 줄어듭니다. 요청들이 쌓이기 시작합니다. 일부 호출자들은 원래의 작업이 계속 진행되고 있음에도 불구하고 시간 초과가 발생하여 재시도할 수 있습니다.
큐(queue)는 워커가 처리할 수 있을 때까지 미완료된 작업을 용량, 만료 정책, 드롭 정책에 따라 보관할 수 있습니다. 나중에 용량이 확보되면, 이 접근 방식은 급증하는 동안 시간을 벌어줍니다.
예를 들어, 시스템이 초당 8개의 작업을 받지만 실제로 완료되는 것은 5개뿐이라면, 백로그는 초당 3개씩 증가하여 1분 후에는 180개의 작업에 도달합니다. 초당 5개가 완료된다고 가정할 때, 새로운 도착은 약 36초의 대기 처리 시간을 마주하게 됩니다.
만약 도착률이 초당 4개로 떨어지면, 백로그는 초당 1개씩 해소됩니다. 이 경우 백로그를 제거하는 데 약 3분이 걸릴 것입니다. 처리 시간은 다양하지만, 이는 짧은 급증이 어떻게 긴 지연을 유발할 수 있는지 보여줍니다.
Amazon Builders’ Library는 타임아웃(timeouts)과 재시도(retries)가 과부하를 악화시킬 수 있다고 설명합니다. 또한 큐의 만료 기간(queue age)을 제한할 것을 권장하는데, 이렇게 하면 작업자(workers)들이 호출자가 이미 포기한 요청들을 처리하는 대신 더 이상 유용하지 않은 요청들을 폐기할 수 있습니다.
각 제어 장치에 특정 임무 부여하기
런타임 예산(Runtime budgets)은 단일 실행을 제한하는 반면, 공유 용량 제어(shared capacity controls)는 해당 실행과 경쟁하는 작업을 제한합니다.
| 제어 장치 | 담당 결정 사항 | 한계점 |
|---|---|---|
| 실행 예산 (Execution budget) | 이 작업이 사용할 수 있는 시간, 모델 작업량, 지출액은 얼마인가? | 개별적으로 제한된 많은 작업들이 여전히 서비스에 과부하를 일으킬 수 있다 |
| ... |
입장 제어(Admission control)는 용량 및 정책 신호에 기반하여 작업을 수락할지 여부를 결정합니다. 단일 동시성 임계값(concurrency threshold)만으로 충분한 경우가 많습니다. 부하 분산(Load shedding)은 용량을 보호하기 위해 의도적으로 작업을 거부하거나 제거합니다. 백프레셔(Backpressure)는 생산자(producers)가 부하를 줄임으로써 루프를 완성시킵니다.
요청 횟수는 부하의 대략적인 측정치입니다. 짧은 분류와 긴 요약이 매우 다른 양의 모델 시간과 토큰을 소비할 수 있습니다.
비용 차이가 큰 작업들은 분리하세요. 요청 횟수를 세는 토큰 버킷(token bucket)은 LLM 제공업체의 토큰 할당량(quota)을 추적하지 않습니다. 제공업체 호출에 대한 입장은 예상 토큰 예약 및 요청 속도 제한이 필요할 수 있습니다. 애플리케이션은 활성 작업을 제한하기 위해 자체 동시성 한계를 설정할 수 있습니다. 이는 제공업체가 강제하는 할당량과 일치해야 하는 것은 아닙니다.
비용이 많이 드는 작업 시작 전에 HTTP 경계에서 거부하기
앞서 언급된 지원-응답(support-reply) 기능을 고려해 보세요. 만약 이 기능이 티켓 초안을 생성하는 HTTP 엔드포인트를 노출한다고 가정해 봅시다. 이 엔드포인트에 대해 특정 동시성 한계를 설정하고 미들웨어 큐를 사용하지 마세요. 검색 또는 생성이 시작되기 전에 초과 요청들을 거부하고, 이러한 동작을 명확하게 문서화해야 합니다.
이 예제들은 ASP.NET Core 프레임워크를 사용하는 .NET 10을 대상으로 합니다. 시뮬레이션된 지연 시간을 애플리케이션 서비스 호출로 대체하고, 해당 호출의 취소 토큰(cancellation token)을 유지하세요.
using Microsoft.AspNetCore.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
...
이 미들웨어는 요청 파이프라인 전체 기간 동안 허가(permit)를 보유합니다. QueueLimit = 0으로 설정하면 초과 요청은 즉시 거부됩니다. 이 예제에서는 공유 용량 부족 시 503을 사용합니다. 만약 호출자가 자신의 할당량을 초과한 경우라면, 429 Too Many Requests가 더 적절할 수 있습니다. HTTP 사양은 이러한 구분을 요구하지 않습니다. API 계약에 선택 사항을 문서화하세요.
의미 있는 재시도 시간(retry time)이 있을 때만 Retry-After 헤더를 포함하세요. 동시성 제한기(concurrency limiter)는 활성 작업이 언제 완료될지 예측할 수 없으므로 추정치를 제공할 수 없습니다. 허가 개수를 기반으로 복구 시간을 추측하지 마세요. 클라이언트는 이 헤더가 없더라도 유한 재시도(bounded retries)와 백오프(backoff)를 사용해야 합니다.
이 정책 풀은 단일 프로세스 내에 존재합니다. 네 개의 복제본 각각에 8개의 허가를 할당하면 모든 복제본에 걸쳐 최대 32개의 동시 요청을 처리할 수 있습니다. 테넌트별 제한(Per-tenant limits)은 한 테넌트가 용량을 독점하는 것을 방지하는 데 도움이 됩니다. 집계 제한(Aggregate limit)은 여러 테넌트가 각자의 제한 내에 머물러 있더라도 전체 용량을 초과할 수 있으므로 전반적인 용량을 보호합니다.
호출자가 모델을 공유하는 두 번째 게이트 배치하기
엔드포인트 제한은 대화형 요청(interactive requests)에 적용됩니다. 배치 워커는 이 제한을 우회할 수 있으며, 단일 허가된 요청이 여러 개의 모델 호출을 병렬로 시작할 수 있습니다.
각 모델 시도 주변에 게이트를 배치하세요. 다음 클래스는 하나의 프로세스 내에서 단일 용량 풀의 활성 시도를 제한하므로, 해당 프로세스의 대화형 서비스와 워커는 동일한 인스턴스를 공유해야 합니다.
만약 HTTP 서비스와 워커가 별도의 프로세스에서 실행된다면, 각각 자체 게이트를 갖게 됩니다. 예를 들어, 허가 4개를 가진 두 개의 게이트는 총 8개의 동시 시도를 허용합니다. 이 클래스는 프로세스 간의 결합된 제공자 제한(combined provider limit)을 강제하지 않습니다.
의존성 주입(dependency injection)을 통해 공유 인스턴스를 등록합니다:
builder.Services.AddSingleton<ModelCapacity>(
_ => new ModelCapacity(maxConcurrentAttempts: 4));
'4'는 예시적인 값입니다. 요청당 게이트를 생성하면 각 요청이 자체 할당량을 갖게 됩니다. 공유 인스턴스를 싱글톤으로 등록하고 컨테이너가 폐기 처리를 담당하도록 합니다.
AttemptAcquire는 즉시 반환됩니다. 리스(lease)를 획득하지 못하면, 해당 델리게이트는 실행되지 않습니다. using 범위는 시도가 완료되거나, 예외가 발생하거나, 협력적으로 취소될 때까지 획득한 권한을 유지합니다.
각 await되는 시도마다 제공자 어댑터(provider adapter) 내에서 RunAsync를 호출하고 실행 마감 기한 토큰(execution-deadline token)을 전달해야 합니다. 인터랙티브 호출의 경우, 거부(rejection)를 사용 불가능한 결과로 매핑합니다. 워커 작업(worker jobs)의 경우, 작업 정책(job policy) 하에서 제한된 재시도(bounded retry)로 매핑합니다. 빡빡한 재시도 루프는 피하고 재시도 정책을 명시적으로 유지하세요.
스트리밍의 경우, 열거(enumeration) 및 폐기 처리를 통해 리스를 유지해야 합니다. 이 델리게이트에서 IAsyncEnumerable<T>를 반환하면 호출자가 소비하기 전에 권한이 해제됩니다. 이 도우미는 전체 작업을 나타내는 태스크가 필요합니다.
취소(Cancellation)는 작업 중단을 요청하는 것입니다. 이는 원격 제공자(remote provider)가 처리를 멈추거나 청구(billing)를 하지 않음을 증명하지 않습니다. 별도의 시간 초과 경쟁(timeout race)이 먼저 완료되었더라도, 기본 태스크가 실제로 끝날 때까지 로컬 리스를 유지해야 합니다.
프로덕션 게이트는 요청 속도 제한, 토큰 예약, 스로틀 냉각기(throttle cooldowns)를 강제할 수도 있습니다. 독립적인 용량 풀은 분리해서 유지하세요. 프로세스 또는 복제본(replicas) 전반에 걸쳐 호출자가 많아진다고 해서 고정된 다운스트림 할당량을 늘릴 수는 없으므로, 분산 제한(distributed limiting)이나 제어된 집계 워커 디스패치(controlled aggregate worker dispatch)를 사용해야 합니다.
대기 시간이 얼마나 유용한지 결정하기
상호작용 요청은 때때로 짧은 대기를 허용할 수 있습니다. 그 대기 시간을 원래의 마감 시간(deadline)에 포함시키세요. 애플리케이션이 진입 후 런타임 예산(runtime budget)을 시작하면, 호출자는 여전히 경험하지만 제한기 큐(limiter queue)에서의 시간은 예산에서 사라질 수 있습니다.
실행 경로당 의도적인 대기 지점 하나를 선호하세요. 중첩된 HTTP, 서비스 및 제공자 대기는 생성되기 전에 마감 시간을 소모할 수 있습니다. 이들의 결합된 지속 시간을 측정하세요.
지속 가능한 작업(durable jobs)의 경우, 백로그 크기와 나이를 제한하세요. 전송하기 전에 마감 시간, 입력 버전, 취소 상태를 확인하세요. 작업 실행 정책이 요구하는 경우 현재 권한을 재확인하세요. 만료되거나 대체된 작업에 대해 명시적인 최종 결과를 기록하세요.
큐 깊이와 함께 가장 오래된 보류 중인 작업의 나이를 모니터링하세요. 백 개 분류 작업은 백 개 요약 작업보다 훨씬 빨리 처리될 수 있습니다. 예상 완료 시간이 제품의 약속을 더 이상 충족하지 못한다면, 작업을 진입시키는 것을 멈추거나 호출자가 수락할 수 있는 나중 완료 계약을 제시하세요.
202 Accepted를 반환하기 전에 수락된 작업을 지속 가능하게 만드세요. HTTP 제한기 큐는 영속성(durability)을 제공하지 않습니다. AI 요청이 백그라운드 작업이 되어야 하는 경우에서 소유권과 수명 주기(lifecycle)를 다룹니다.
일회성 또는 복구 가능한 로컬 작업의 경우, 경계가 있는 Channel<T>을 사용하세요. 대기, 거부 또는 드롭 정책을 선택하세요. 쓰기(writes)와 경계가 있는 처리 동시성(processing concurrency)을 기다리세요. 또한 필요한 경우 프로듀서도 제한하거나 거부하여 채널이 가득 차는 것이 무한한 보류 쓰기로 변하는 것을 막으세요.
재시도 및 복구 시 동일한 제한 준수하기
공유 제공자 풀(shared provider pool)에 영향을 미치는 스로틀링은 이를 사용하는 모든 호출자에 대한 진입을 변경해야 합니다. 그렇지 않으면 다른 HTTP 요청과 배치 작업이 같은 부족함으로 계속 전송될 것입니다.
어댑터에서 응답을 정규화하고 영향을 받은 풀의 진입(admission) 또는 쿨다운 상태를 업데이트합니다. 프로바이더 스로틀링을 공유 용량 신호로 취급하기가 이 경계를 다룹니다. 호출자별 제한은 관련 없는 트래픽에 영향을 주어서는 안 됩니다.
재시도 지연 전에 시도의 동시성 허가(concurrency permit)를 해제한 다음, 다음 시도를 위해 진입 절차로 다시 들어갑니다. 지연 시간과 반복 작업은 실행 예산 내에 머무릅니다. SDK 및 HTTP 복원력 설정도 확인하세요. 내부 SDK 재시도는 물리적 호출 간의 시도별 속도 제한(rate), 토큰, 쿨다운 또는 시도 횟수 검사를 우회할 수 있습니다. 외부 임대(lease)는 여전히 논리적 작업의 동시성을 제한합니다. 전반적인 실행 마감일은 취소 토큰이 SDK에 도달하고 SDK가 이를 준수할 때 적용됩니다.
복구 시, 공유 제한 하에서 점진적으로 디스패치(dispatch)를 재개합니다. 지터(Jitter)는 재시도를 분산시킵니다. 모든 지연된 호출과 대기열 작업(queued jobs)을 한 번에 해제하면 급증(spike)을 다시 만들 수 있습니다.
배치 작업이 인터랙티브 용량을 압도해서는 안 될 때, 인터랙티브 용량을 예약합니다. 분리된 큐는 결합된 허용 범위 내에서 스케줄링 또는 할당이 필요합니다. 호출자가 승인된 작업을 관리할 수 있도록 포링(polling) 상태와 취소 기능을 포화 상태에서도 사용 가능하게 유지해야 합니다.
거절이 시작되는 지점 너머까지 테스트하기
예상 트래픽을 넘어서 테스트하세요. 프로바이더를 느리게 하고 진입 제한을 초과시키세요. 승인된 요청은 여전히 목표 시간 내에 완료되어야 하며, 과도한 작업은 즉각적인 거절(refusal)을 받아야 합니다.
제공된 부하(offered load), 수락된 처리량(accepted throughput), 그리고 결과를 기록하세요. 성공적인 요청 간의 지연 시간은 건강해 보일 수 있지만, 대부분의 호출자는 거절을 받게 됩니다.
풀별 활성 시도 횟수, 단계 대기 시간, 가장 오래된 작업 연령(oldest-job age), 만료된 작업, 스로틀링, 완료된 작업당 시도를 추적하세요. 워크로드 클래스 및 구성된 풀과 같은 경계가 지정된 레이블을 사용하세요. 프롬프트와 임의 사용자 식별자는 메트릭 차원(metric dimensions)에 포함시키지 마세요.
용량을 변경하는 실패 경로를 연습하세요:
- 모델 시도가 실패하거나 취소됩니다. 해당 로컬 허가(permit)는 시도가 종료된 후에 사용 가능해집니다.
- 대화형 트래픽과 배치 작업이 경쟁합니다. 이들의 결합된 디스패치(dispatch)는 의도된 풀 제한 내에 머무릅니다.
- 큐에 대기 중인 작업이 디스패치되기 전에 만료됩니다. 이는 모델 호출을 시작하지 않으며, 만료된 결과만 노출합니다.
- 복제본(Replicas)이 확장됩니다. 집계 제공자 할당량은 여전히 유지됩니다.
- 제공자가 백로그를 안고 복구됩니다. 디스패치는 무제한 재실행 버스트 없이 증가합니다.
- 호출자는 과부하 응답을 받고 백오프(back off)합니다. 이는 거부된 요청들을 즉시 대체하는 대신 제출 속도를 낮춥니다.
현실적인 입력 및 출력 크기, 스트리밍 지속 시간, 모델 호출 팬아웃(fan-out)을 사용하여 제한을 설정하세요. 아주 작은 프롬프트로만 테스트된 설정은 사용자의 실제 문서에서는 실패할 수 있습니다.
백프레셔를 사용해야 할 때와 큐를 사용하지 않아야 할 때
AI 워크로드가 용량을 두고 경쟁하는 공유 제어(shared controls)를 사용할 때, 특히 대화형 요청이 배치 작업이나 평가와 제공자를 공유할 때 유용합니다. 제품이 허용할 수 있는 경우에만 대기 기능을 추가하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기