LiteLLM 프로덕션 환경 설정: general_settings와 운영 최적화

LiteLLM 프로덕션 환경 설정: general_settings와 운영 최적화

개발 머신에서 프록시가 돈다고 프로덕션에서도 똑같이 도는 건 아니에요. 개발 때는 .env에 키를 넣고, 연결 풀 크기를 신경 쓰지 않고, 로그가 쌓여도 상관없죠. 하지만 서비스를 라이브로 올리기 전에 챙겨야 할 설정들이 따로 있습니다. 이 페이지는 general_settings를 중심으로 프로덕션 구성, 머신 크기, 워커 전략, 데이터베이스·Redis 설정을 한 번에 정리한 체크리스트예요. 각 섹션이 독립적이니, 이미 떠 있는 배포를 점검할 때도 섹션별로 훑어볼 수 있습니다.

출처: 공식문서 — Production Best Practices

마스터 키와 프로덕션 모드

master_key는 프록시의 관리자 자격증명이에요. 관리자 API 호출을 인증하고 Admin UI 로그인 비밀번호로도 쓰입니다. 환경 변수로 설정하고(sk-로 시작해야 함), 시크릿 매니저에 보관하세요.

export LITELLM_MASTER_KEY="sk-<long-random-value>"

그리고 LITELLM_MODE="PRODUCTION"을 설정하세요. 이 값은 load_dotenv()를 비활성화해서, 프록시가 로컬 .env 파일에서 자격증명을 자동 로드하는 것을 막아줍니다. 데이터베이스를 쓴다면 저장된 변수를 암호화하는 솔트 키도 추가로 설정해야 해요.

export LITELLM_SALT_KEY="sk-1234"

솔트 키는 LLM API 키 자격증명을 암호화하므로, 모델을 추가한 뒤에는 절대 바꾸면 안 됩니다. 바꾸면 기존 값들을 읽을 수 없게 돼요.

로그와 타임아웃

프로덕션에서는 불필요한 디버그 로그를 끄고 JSON 로그를 켜는 게 좋아요. 또 지나치게 오래 걸리는 요청은 연결을 붙들고 있지 않도록 실패 처리하세요. 기본 타임아웃은 6000초라, 이것만으로는 요청이 한참 매달릴 수 있어요.

general_settings:
  disable_error_logs: True      # LLM 예외를 DB 대신 로깅 스택으로

litellm_settings:
  request_timeout: 600          # 행(hang) 요청 실패 처리
  set_verbose: False
  json_logs: true
export LITELLM_LOG="ERROR"

LLM 예외는 기본적으로 데이터베이스에 기록되는데, 제공자 오류가 지속되면 spend logs 테이블이 부풀어요. disable_error_logs: True로 꺼서 예외를 로깅 스택으로 보내는 게 좋습니다.

워커 전략: 팟 하나에 워커 하나

Kubernetes처럼 팟 레벨 오토스케일러가 CPU를 읽는 환경에서는, 팟당 Uvicorn 워커 하나로 두고 수평(팟 추가)으로 스케일하는 게 기본 권장 사항이에요. 워커당 하나의 프로세스가 지연을 예측 가능하게 만들고, Horizontal Pod Autoscaler가 단일 프로세스에 대해 CPU 임계값을 정확히 읽을 수 있게 합니다.

CMD ["--port", "4000", "--config", "./proxy_server_config.yaml", "--num_workers", "1"]

단일 VM이나 직접 스케일을 관리하는 베어 컨테이너에서는 반대로 NUM_WORKERS를 머신의 vCPU 수로 맞추세요. 그 외에는 그 코어들을 쓸 주체가 없으니까요.

메모리가 점진적으로 늘어나는 게 보이면, --max_requests_before_restart로 워커가 고정된 요청 수를 처리한 뒤 재시작하게 해 메모리 사용을 제한할 수 있어요.

CMD ["--port", "4000", "--config", "./proxy_server_config.yaml", "--num_workers", "1", "--max_requests_before_restart", "10000"]

메모리 최소 발판(floor) 이해하기

프록시의 정상 상태 메모리는 동시 요청 수와 큰 상관이 없어요. Prisma가 쿼리 엔진을 별도 프로세스로 돌리는데, 그 상주 메모리는 '가장 큰 단일 쿼리문'에 맞춰 자라나고, glibc는 그 메모리를 OS에 되돌려주지 않습니다. 그래서 팟의 메모리 최소치는 역대 최악의 쓰기를 기준으로 올라붙고 워커 수명 동안 유지돼요.

그래서 팟당 요청/한도로 1 vCPU, 4Gi 메모리를 부여하는 걸 기본으로 합니다. 두 값 모두 워커당 수치라서, 워커 8개를 돌리는 컨테이너는 1 vCPU/4Gi가 아니라 8 vCPU/32Gi가 필요해요. 단일 워커 수치로 멀티 워커 컨테이너를 잡으면 과소 프로비저닝되기 쉽고, 이것이 Kubernetes에서 팟당 워커 하나를 유지하는 주된 이유이기도 합니다.

생각보다 큰 쓰기는 store_prompts_in_spend_logs가 켜진 spend 로깅에서 나와요. 각 행이 카운터가 아니라 전체 프롬프트와 응답을 담기 때문이죠. 프롬프트를 저장한다면 4Gi는 최소값일 뿐이고 그 위로 여유를 줘야 합니다.

resources:
  requests:
    cpu: "1"        # 1*num_workers 여야 함
    memory: "4Gi"   # 4*num_workers 여야 함
  limits:
    cpu: "1"
    memory: "4Gi"

스케일링은 CPU로 하세요. 메모리는 스케일링 신호로 쓰면 안 됩니다. 쿼리 엔진의 상주 메모리가 '지금 하는 일'이 아니라 '역대 최대 쓰기'를 반영하기 때문에, 메모리 대상으로는 한 번의 큰 쓰기 후 레플리카가 올라갔다가 다시 내려오지 않을 테니까요.

데이터베이스 연결 풀 제한

연결 풀 제한은 인스턴스가 아니라 워커 프로세스마다 적용됩니다. 워커가 여러 개면 각 워커가 자기 연결 풀을 만들죠. 그린 값을 정하는 공식은 이렇습니다.

database_connection_pool_limit = MAX_DB_CONNECTIONS ÷ (인스턴스 수 × 인스턴스당 워커 수)

예를 들어 DB가 최대 100 연결을 허용하고, 인스턴스 1개에 --num_workers 8로 8개 워커를 돌린다면: 100 ÷ (1 × 8) = 12.5. 12.5는 쓸 수 없으니 안전 여유를 두고 내려서 10으로 반올림합니다. 그러면 워커 8개 × 연결 10 = 총 80 연결로, DB의 100 한도 아래에 안전하게 머물러요.

general_settings:
  database_connection_pool_limit: 10    # 워커당 Prisma 연결 풀 (기본 10, 권장 10-20)
  database_connection_timeout: 60       # DB 연결 호출에 60초 타임아웃

팟 레벨 오토스케일러의 maxReplicas가 기본 100으로 크면, 기본 풀 한도 10에서 완전히 스케일 아웃된 배포가 대략 1000 연결을 요구할 수 있어요. maxReplicas는 DB가 감당할 수 있는 수준에서 정하세요.

백그라운드 작업 중복 실행 방지

프록시는 시작할 때 백그라운드 작업 스케줄러를 등록하는데, 팟 단위가 아니라 Uvicorn 워커 프로세스마다 한 번씩 해요. --num_workers 4로 시작한 팟은 모든 작업을 4벌씩 돌리고, 그런 레플리카가 10개면 작업량이 40배가 됩니다. 대부분의 작업은 '한 번만 일어나면 되는' 것들이라 문제가 생겨요.

예산 리셋, spend 로그 정리, 사용량 내보내기처럼 효과가 공유되는 작업은 Redis를 통해 단일 주인(owner)을 선출합니다. Redis가 없으면 선출할 곳이 없어 각 프로세스가 무방비로 실행돼요. LITELLM_JOB_ROLE을 쓰면 이 작업들을 모든 서빙 레플리카가 아니라 한 배포에만 등록할 수 있어요. 멀티 팟 배포에서 Redis 없이 단일 실행을 보장하는 유일한 방법입니다.

LITELLM_JOB_ROLE 프로세스가 등록하는 것
미설정 또는 all 모든 작업 (기본값)
worker 모든 작업, 단일 주인 작업 포함
serving 단일 주인 작업 없음

serving으로 설정한 팟은 예산 리셋, spend 로그 정리, 키 회전, 주간/월간 지출 보고 등 공유 상태에 작용하는 작업을 등록하지 않지만, 자기 자신의 in-memory 큐를 비우거나 자기 모델 레지스트리를 갱신하는 작업(spend flush 등)은 계속 등록합니다. 서빙 Deployment는 평소처럼 스케일하고, worker는 레플리카 1개로 유지하면 됩니다. 둘 다 같은 DB와 Redis를 바라보기 때문에 worker가 서빙 팟들이 더는 등록하지 않는 작업을 대신 수행해요.

더 알아보기