지출 로그 보존 기간 최대값
지출 로그 보존 기간 최대값 (Maximum Retention Period for Spend Logs)
지출 로그의 최대 보존 기간을 설정하는 방법을 다뤄요. 오래된 로그를 자동으로 삭제해 데이터베이스 크기를 관리하는 데 도움이 돼요.
보존 및 정리는 오픈소스예요. 이 페이지의 모든 설정은 엔터프라이즈 라이선스 없이도 동작하고, 정리 작업은 실행 전에 라이선스 상태를 확인하지 않아요.
요구 사항
- Postgres (로그 저장용)
- Redis (선택): 여러 프록시 인스턴스를 실행하면서 분산 락을 켜려는 경우에만 필요
사용법
설정
proxy_config.yaml의 general_settings 아래에 추가하세요:
proxy_config.yaml
general_settings:
maximum_spend_logs_retention_period: "7d" # Keep logs for 7 days
# Optional: set how frequently cleanup should run - default is daily
maximum_spend_logs_retention_interval: "1d" # Run cleanup daily
# Optional: set exact time for cleanup (Cron syntax)
maximum_spend_logs_cleanup_cron: "0 4 * * *" # Run at 04:00 AM daily
# Optional: bound how much work a single run may do
maximum_spend_logs_cleanup_batch_size: 1000 # Rows per DELETE statement
maximum_spend_logs_cleanup_max_batches: 500 # DELETE statements per table per run
maximum_spend_logs_cleanup_run_budget: "5m" # Wall clock for the whole run
maximum_spend_logs_cleanup_batch_timeout: "30s" # statement_timeout and lock_timeout per batch
litellm_settings:
cache: true
cache_params:
type: redis
작업이 실행되는 시점
정리 작업은 요청했을 때만 존재해요. maximum_spend_logs_retention_period 또는 maximum_autorouter_session_retention_period가 설정되면 프록시 시작 시 등록되고, 그렇지 않으면 등록되지 않아요. 둘 다 설정하지 않으면, 배치·예산·간격 설정이 무엇이든 아무것도 삭제되지 않아요.
보존 기간이 설정되고 maximum_spend_logs_cleanup_cron이 없으면, 일정은 하루 중 특정 시각이 아니라 간격(interval)이에요. 간격은 maximum_spend_logs_retention_interval에서 오며 기본 1d이고, pod 무리가 정확히 동시에 발화하지 않도록 최대 60초의 랜덤 오프셋이 더해져요. 그래서 첫 실행은 자정이나 부팅 시가 아니라 시작 후 대략 한 간격 뒤에 이뤄져요. 작업을 조용한 시간에 고정하고 싶다면 maximum_spend_logs_cleanup_cron을 설정하세요.
삭제되는 것
실행은 보존 설정이 의미하는 기준선(cutoff)에 따라 세 테이블을 정리해요.
| 테이블 | 시간 컬럼 | 보존 설정 |
|---|---|---|
LiteLLM_SpendLogs |
startTime |
maximum_spend_logs_retention_period |
LiteLLM_SpendLogToolIndex |
start_time |
maximum_spend_logs_retention_period |
LiteLLM_AutoRouterSession |
last_turn_at |
maximum_autorouter_session_retention_period |
LiteLLM_SpendLogToolIndex 행은 지출 로그에서 파생되므로, 그들이 가리키는 로그 행과 같은 기준선에서 만료돼요. 자동 라우터 세션 롤업은 자체 보존 설정과 자체 기준선을 가져요. maximum_autorouter_session_retention_period만 설정해도 작업을 등록하기에 충분하며, 이 경우 지출 로그는 손대지 않고 세션 롤업만 정리돼요.
구성 옵션
maximum_spend_logs_retention_period (필수)
삭제 전에 로그를 얼마나 오래 보관할지. 지원 형식:
"7d"– 7일"24h"– 24시간"60m"– 60분"3600s"– 3600초
maximum_spend_logs_retention_interval (선택)
정리 작업이 얼마나 자주 실행될지. 위와 같은 형식을 사용해요. 설정하지 않으면 maximum_spend_logs_retention_period가 설정된 경우에만 24시간마다 정리되요.
maximum_spend_logs_cleanup_cron (선택)
표준 cron 문법으로 정리를 스케줄링해요. maximum_spend_logs_retention_interval보다 우선해요.
예시:
"0 4 * * *"– 매일 04:00에 실행"0 0 * * sun"– 매주 일요일 자정에 실행"*/30 * * * *"– 30분마다 실행
요일 필드에는 요일 이름을 사용하세요
요일 필드에 숫자 대신 요일 이름(sun, mon, ...)을 사용하세요. LiteLLM은 APScheduler로 정리를 스케줄링하는데, APScheduler는 요일을 월요일=0부터 일요일=6까지 번호를 매기는 반면 표준 cron은 일요일=0부터 토요일=6까지 매겨요. 숫자 요일 값은 두 관례 사이에서 변환되지 않으므로 "0 0 * * 0"은 일요일이 아니라 월요일에 발화돼요. 요일 이름은 두 관례에서 같은 의미이므로 항상 기대대로 동작해요.
아래 네 설정은 단일 실행이 수행할 수 있는 작업량을 제한해요. 각각 환경 변수도 가지며, 이 변수가 그 설정의 기본값을 정해요. 둘은 서로 바꿀 수 없어요. 둘 다 있으면 키별로 general_settings 키가 우선해요. 지속 시간 파서가 거부하는 값이나 0 이하의 값은 기본값으로 폴백돼요.
maximum_spend_logs_cleanup_batch_size (선택)
각 DELETE 문이 제거하는 행 수. 기본 1000. 환경 기본값: SPEND_LOG_CLEANUP_BATCH_SIZE
maximum_spend_logs_cleanup_max_batches (선택)
작업이 테이블당·실행당 발행하는 DELETE 문 수. 정확히 그만큼, 더도 덜도 아니에요. 기본 500이므로 기본 배치 크기에서 실행은 정리하는 각 테이블에서 최대 500,000행을 제거해요. 환경 기본값: SPEND_LOG_RUN_LOOPS
maximum_spend_logs_cleanup_run_budget (선택)
전체 실행의 벽시계(wall-clock) 예산. 보존 기간과 같은 지속 시간 형식. 기본 5m. 예산은 각 테이블을 개별적으로 다루는 게 아니라 실행이 만지는 모든 테이블을 함께 덮으므로, 지출 로그가 먼저 예산을 쓰고 남은 것이 도구 인덱스와 세션 롤업에 가요. 예산이 소진되면 실행은 그 지점에서 멈추고 남은 백로그는 다음 틱을 기다려요. 환경 기본값: SPEND_LOG_CLEANUP_RUN_BUDGET_SECONDS, 초 단위.
maximum_spend_logs_cleanup_batch_timeout (선택)
작업이 발행하는 각 문에 적용되는 Postgres statement_timeout과 lock_timeout. 기본 30s. 이 창 안에 끝나지 못하거나 락을 잡지 못하는 문은, 사용자 트래픽이 뒤에서 대기하는 동안 락을 잡고 있는 대신 데이터베이스에 의해 취소돼요. 환경 기본값: SPEND_LOG_CLEANUP_BATCH_TIMEOUT_SECONDS, 초 단위.
배치 타임아웃은 배치 하나가 정당하게 필요한 시간보다 위로 잡으세요
취소된 문은 배치 실패로 집계되고, SPEND_LOG_CLEANUP_MAX_CONSECUTIVE_BATCH_FAILURES(기본 3) 만큼 연속 실패하면 실행이 중단돼요. 타임아웃을 배치가 정직하게 필요로 하는 시간보다 낮게 설정하면 모든 배치가 취소되므로, 실행은 아무것도 삭제하지 못한 채 중단되고 Aborting LiteLLM_SpendLogs cleanup after 3 consecutive batch failures; total deleted before abort: 0이라고 로그를 남겨요. maximum_spend_logs_cleanup_batch_size를 올리면 더 큰 문이 끝낼 여유가 타임아웃에 여전히 있는지 확인하세요.
동작 방식
1단계. 락 획득 (Redis가 있으면 선택)
Redis가 활성화되면 LiteLLM이 이를 사용해 한 번에 한 인스턴스만 정리를 실행하게 해요.
- 락을 획득하면:
- 이 인스턴스가 정리를 진행
- 다른 인스턴스는 건너뜀
- 락이 없으면:
- 정리는 여전히 실행됨 (단일 노드 설정에 유용)

지출 로그 삭제의 동작 방식
2단계. 배치 삭제
정리가 시작되면:
- 구성된 보존 기간을 사용해 기준선 날짜 계산
- 기준선보다 오래된 로그를 배치로 삭제 (기본 크기
1000) - 데이터베이스에 과부하를 주지 않도록 배치 사이에 짧은 지연 추가

오래된 로그의 배치 삭제
3단계. 제한
경계가 없으면 큰 테이블에 대한 단일 실행은 자체가 정리 중인 데이터베이스를 포화시킬 수 있는 무거운 쓰기 트랜잭션의 긴 흐름이 돼요. 모든 실행은 한 번에 세 가지로 제한되고, 먼저 도달하는 제한에서 멈춰요. maximum_spend_logs_cleanup_batch_size 행/DELETE(기본 1000), maximum_spend_logs_cleanup_max_batches 문/테이블(기본 500), 그리고 실행 전체의 maximum_spend_logs_cleanup_run_budget 벽시계(기본 5m). 기본값에서 테이블당 최대 500,000행, 최대 5분의 작업이 돼요.
예산은 문 사이에서 확인되므로, 작업이 새 작업을 발행하는 것을 언제 멈출지 결정해요. 이미 실행 중인 문을 제한하는 것은 maximum_spend_logs_cleanup_batch_timeout인데, 작업이 발행하는 모든 문(미해결 행 프로브 포함)에 Postgres statement_timeout과 lock_timeout으로 적용돼요. 문 사이에서 확인하는 정직한 결과는 실행이 예산을 배치 타임아웃 하나만큼 초과할 수 있다는 것이므로, 실제 상한은 예산 + 타임아웃 하나라는 걸 알고 예산을 잡으세요. 일찍 멈추는 것은 정상이에요. 실행은 다음 틱에서 같은 기준선에서 재개되므로, 백로그는 여러 실행에 걸쳐 배수(drain)돼요.
각 제한에는 기본값을 정하는 환경 변수가 있고, 아래에 두 실패 처리 손잡이와 함께 나열돼요. 같은 제한에 대한 general_settings 키가 있으면 그 키가 환경 기본값을 오버라이드해요.
| 환경 변수 | 기본값 | 설명 |
|---|---|---|
SPEND_LOG_CLEANUP_BATCH_SIZE |
1000 |
DELETE 문당 삭제되는 행 수 |
SPEND_LOG_RUN_LOOPS |
500 |
실행당 테이블당 최대 DELETE 문 수 |
SPEND_LOG_CLEANUP_RUN_BUDGET_SECONDS |
300 |
전체 실행의 벽시계 예산(초), 정리하는 모든 테이블에 공유 |
SPEND_LOG_CLEANUP_BATCH_TIMEOUT_SECONDS |
30 |
작업이 발행하는 모든 문에 대한 Postgres statement_timeout·lock_timeout(초) |
SPEND_LOG_CLEANUP_REMAINING_COUNT_CAP |
100000 |
미해결 행 프로브의 상한. 백로그 보고 자체가 비싼 스캔이 되지 않게 함 |
SPEND_LOG_CLEANUP_MAX_CONSECUTIVE_BATCH_FAILURES |
3 |
실행이 중단되기 전에 허용되는 연속 배치 실패 |
SPEND_LOG_CLEANUP_BATCH_FAILURE_BACKOFF_SECONDS |
0.5 |
실패한 배치 후 재시도 전 일시 정지 |
메트릭
작업은 Prometheus로 무엇을 했는지 보고하므로, 건강한 정상 상태와 계속 자라는 백로그를 구분할 수 있어요. 모든 메트릭은 마지막 것을 제외하고 table로 라벨이 붙고, 마지막 것은 outcome으로 라벨이 붙어요.
| 메트릭 | 무엇을 알려주는가 |
|---|---|
litellm_spend_log_cleanup_rows_deleted_total |
테이블별 제거된 행 수 |
litellm_spend_log_cleanup_batch_duration_seconds |
개별 배치가 걸리는 시간. 배치 크기를 튜닝할 때 보는 지표 |
litellm_spend_log_cleanup_rows_remaining |
실행 후에도 남아 기다리는 만료된 행 |
litellm_spend_log_cleanup_batch_failures_total |
실패한 배치(배치 타임아웃으로 취소된 문 포함) |
litellm_spend_log_cleanup_runs_total |
outcome별 실행: completed, budget_exhausted, batch_cap_reached, skipped_locked, skipped_disabled, aborted |
rows_remaining은 경보를 걸 곳이에요. 평평하거나 0에 가까우면 보존이 따라잡고 있다는 뜻이고, 올라가는 선이면 그렇지 않다는 뜻이에요. runs_total과 함께 읽으세요. 건강한 배포는 completed에 있고, budget_exhausted나 batch_cap_reached가 꾸준히 나오면 실행이 매 틱 잘려 나가는 것이므로 손잡이를 올려야 해요.
rows_remaining 뒤의 프로브는 SPEND_LOG_CLEANUP_REMAINING_COUNT_CAP(기본 100000)으로 제한돼요. 캡에서 카운팅이 멈추므로 백로그 보고 자체가 큰 테이블의 전체 스캔이 되지 않아요. 캡보다 많은 만료 행이 있는 테이블은 정확히 캡을 보고한다는 뜻이에요. 값이 100000에 있으면 바닥(floor)으로 취급하세요.
큰 테이블
인덱싱
LiteLLM_SpendLogs는 startTime 및 (startTime, request_id) 인덱스를 기본으로 포함하며, 이는 삭제 배치가 필요로 하는 것입니다. 그래서 기본 스키마에 보존용으로 추가할 것이 없어요. 2,000,000행이 시드된 테이블에서 배치별 계획은 LiteLLM_SpendLogs_startTime_idx 인덱스 스캔이 중첩 루프를 공급하는 것이고, 1000행 배치에 약 1.8ms예요.
그래서 배치 자체는 저렴하고, 느린 실행이 나쁜 계획의 신호인 경우는 드물어요. 큰 백로그가 실제로 비용으로 만드는 것은 WAL 볼륨과 삭제가 남기는 죽은 튜플(dead tuple)이 만드는 autovacuum 부하예요. 같은 테이블에서 실행이 111초에 약 500,000행을 제거하면 지출 로그와 도구 인덱스 테이블에 약 1,000,000개의 죽은 튜플이 남아 실행이 끝난 뒤에도 autovacuum을 두 테이블 모두 바쁘게 유지하기에 충분해요. 삭제 계획이 아니라 그 점을 위해 튜닝하세요.
손잡이 크기 조정
배치 크기는 락 지속 시간과 WAL 레코드 크기를 처리량과 맞바꿔요. 더 큰 배치는 문당 더 많은 행을 삭제하고 배치 사이의 고정 일시 정지에 비례적으로 더 적은 시간을 쓰지만, 행 락을 더 오래 잡고 maximum_spend_logs_cleanup_batch_timeout의 여유를 줄여요. 최대 배치 수는 단일 실행이 한 테이블에서 삭제할 수 있는 총량을 캡하고, 실행 예산은 다른 둘이 어떻게 설정됐든 전체 실행이 데이터베이스의 관심을 잡을 수 있는 시간을 캡해요.
배치 사이의 일시 정지(고정 0.1초)는 기본 배치 크기에서 배치별 비용을 지배해요. 그래서 처리량은 초당 약 10,000행에 이르고, 5분 예산이 아니라 500-배치 캡이 기본값에서 제약 한도가 돼요.
하루 5,000,000개의 지출 로그 행을 30일 보존으로 수집하는 배포를 생각해 보세요. 정상 상태에서 매일 실행은 하루치 행을 삭제해야 하므로, 기본 캡 500,000은 보존을 영구히 뒤처지게 하고 테이블은 무한히 자라요. maximum_spend_logs_cleanup_batch_size를 5000으로, maximum_spend_logs_cleanup_max_batches를 2000으로 올리면 상한이 10,000,000행이 되고 5,000,000행에 실행이 약 110초가 걸리며 기본 5m 예산과 30초 배치 타임아웃 안에 여유 있게 들어와요. 몇 일 후 litellm_spend_log_cleanup_rows_remaining을 확인하세요. 평평한 추세면 따라잡고 있는 것이고, 올라가면 배치를 더 크게 만들기보다 maximum_spend_logs_retention_interval로 정리를 더 자주 실행하세요.
큰 백로그 배수(drain)
보존을 처음 켰는데 테이블이 이미 몇 달의 이력을 가지고 있는 경우가 계획할 가치가 있는 경우예요. 실행은 예산에서 멈추고 같은 기준선에서 다음 틱에 재개되므로, 백로그는 많은 실행에 걸쳐 스스로 배수돼요. 기본값과 일일 간격을 두면 100,000,000행 백로그는 몇 달이 걸려요.
더 빨리 배수하려면 유지보수 창 동안 maximum_spend_logs_cleanup_run_budget과 maximum_spend_logs_cleanup_max_batches를 올리고, 실행되는 동안 지출 로그 테이블의 복제 지연과 autovacuum을 지켜본 뒤, 둘 다 기본값으로 되돌리세요. 통제하는 창 안에서 배수하는 것은 피크 시간 한가운데 같은 작업을 발견하는 것보다 훨씬 추론하기 쉬워요.
테이블이 보존이 꺼져 있어서가 아니라 볼륨이 정말 높아서 크다면, 파티셔닝 테이블로 전환하는 것이 더 나은 답이에요. 그러면 보존이 행을 삭제하는 대신 전체 파티션을 떨어뜨려 즉시 디스크를 확보하고 죽은 튜플을 남기지 않아 vacuum이 필요 없으며, 파티션 드롭이 회수하는 데이터에는 배치·예산 손잡이 중 어떤 것도 중요하지 않아요. 아래를 참고하세요.
고성능 배포를 위한 파티셔닝
높은 요청 볼륨(하루 수백만 행)에서는 DELETE를 통한 보존이 문제가 돼요. 행 삭제는 디스크를 운영체제에 반환하지 않아요. 나중에 autovacuum이 회수해야 하는 죽은 튜플("tombstone")을 남기죠. 쓰기 속도가 autovacuum을 앞지르면 테이블은 논리적 행 수는 제한돼도 디스크에서 계속 자라며, LiteLLM_SpendLogs가 한 달에 수백 GB에 달할 수 있어요.
해결책은 startTime에 대한 네이티브 Postgres 범위 파티셔닝이에요. 파티셔닝 테이블에서 보존은 DROP TABLE로 전체 파티션을 떨어뜨리는데, 이는 즉시 디스크를 확보하는 순간적인 메타데이터 연산이고 tombstone도 vacuum도 없어요. general_settings.use_spend_logs_partitioning: true가 설정되고 테이블이 실제로 파티셔닝되면, 같은 정리 작업이 배치 삭제에서 만료된 파티션 드롭으로 전환하고, 각 실행에서 앞으로 올 파티션을 미리 생성해서 쓰기가 항상 착륙할 파티션을 가지게 해요. 두 조건이 모두 필요하고, 탐지만으로는 동작이 전환되지 않아요.
이 기능은 옵트인이에요. 기본 스키마는 파티셔닝되지 않으므로, 테이블을 변환하기 전까지 기존 배포는 영향을 받지 않아요.
파티션 유지보수는 실행 예산과 한 가지 방식으로 상호작용해요. 작업은 실행에 여전히 예산이 남아 있을 때만 파티션 유지보수를 시작하고, 이미 초과한 실행은 다음 틱까지 건너뛰어요. 드롭이 실행되기 시작하면 예산이 그것을 짧게 자를 수 없어요. 파티션 드롭은 ACCESS EXCLUSIVE 락을 잡는 DDL이고 끝까지 실행돼야 하기 때문이에요. 작업이 하는 다른 모든 것은 문 사이에서 중단 가능하므로, 이것이 예산이 비행 중에 제한할 수 없는 유일한 작업이에요.
테이블 변환
데이터가 채워진 테이블은 제자리에서 파티셔닝할 수 없으므로, 변환은 기존 테이블을 옆으로 이름 바꾸고 새 파티셔닝 테이블을 만드는 방식이에요. 파티션 키는 기본 키의 일부여야 하므로 기본 키가 복합 ("request_id", "startTime")이 되고, LiteLLM의 지출 로그 쓰기 경로는 INSERT ... ON CONFLICT DO NOTHING을 사용하므로 이와 호환돼요.
db_scripts/partition_spend_logs.sql의 런북을 데이터베이스에 실행하세요(스테이징 사본에서 테스트하고 백업 먼저). 파티셔닝 부모, 복합 기본 키, startTime 인덱스, 그리고 범위를 벗어난 행을 위한 안전망인 DEFAULT 파티션을 만들어요.
변환 후 위와 같이 보존 기간을 설정하면 정리 작업이 파티션을 관리해 줘요.
튜닝
| 환경 변수 | 기본값 | 설명 |
|---|---|---|
SPEND_LOG_PARTITION_INTERVAL |
day |
파티션 세분화: day, week, month. 고볼륨 테이블은 day를 사용해 보존이 정밀하고 개별 파티션이 관리 가능하게 |
SPEND_LOG_PARTITION_PRECREATE_AHEAD |
7 |
각 정리 실행에서 미리 만들 미래 파티션 수 |
파티션은 전체 시간 범위가 보존 기준선보다 오래된 경우에만 드롭되므로, 실효 보존은 파티션 세분화로 올림(round up)돼요.
출처: 문서
더 알아보기 (Learn more)
- Redis 분산 락과 여러 인스턴스 조정 살펴보기
- Prometheus 메트릭으로 정리 진행 상태 모니터링하기