feat(spend_logs): native Postgres partitioning for SpendLogs retention - #29454
Closed
yassin-berriai wants to merge 1 commit into
Closed
feat(spend_logs): native Postgres partitioning for SpendLogs retention#29454yassin-berriai wants to merge 1 commit into
yassin-berriai wants to merge 1 commit into
Conversation
High-volume deployments see LiteLLM_SpendLogs grow to hundreds of GB in a month because retention via DELETE leaves dead tuples that autovacuum cannot reclaim fast enough, so disk is never returned to the OS. This adds opt-in time-based range partitioning on startTime. When the table is partitioned, the existing spend-log cleanup job reclaims disk by dropping whole partitions (instant, frees disk immediately) instead of batched deletes, and pre-creates upcoming partitions on each run. Detection is automatic via the Postgres catalog, so non-partitioned installs are completely unaffected and keep the existing DELETE behavior. Converting an existing table is a manual, documented operation (db_scripts/partition_spend_logs.sql) since Postgres cannot partition a populated table in place. The partition key must be part of the primary key, so the PK becomes composite (request_id, startTime); the live write path uses INSERT ... ON CONFLICT DO NOTHING, which is compatible.
|
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Contributor
Author
|
Superseded by #29466 (same work on the renamed branch Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Relevant issues
High-volume deployments see
LiteLLM_SpendLogsgrow to hundreds of GB within a month even with request/response payloads excluded. The root cause is that retention viaDELETEdoes not return disk to the OS; it leaves dead tuples that autovacuum cannot reclaim fast enough when writes run at millions of rows per day, so the table bloats indefinitely.Linear ticket
LIT-3502
Pre-Submission checklist
make test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewType
🆕 New Feature
Changes
This adds opt-in time-based range partitioning for
LiteLLM_SpendLogs. With a partitioned table, retention drops whole partitions withDROP TABLE, an instant metadata operation that frees disk immediately, with no tombstones and no vacuum.The design is deliberately non-invasive for existing installs. There is no forced migration and no schema change shipped in a Prisma migration; the default table stays non-partitioned. The cleanup job auto-detects whether the table is partitioned by querying the Postgres catalog. When it is, the job reclaims disk by dropping expired partitions and pre-creates upcoming partitions on each run; when it is not (every existing deployment), it keeps the current batched
DELETEbehavior unchanged. Catalog-detection failures fall back toDELETE, so the job is never left in a broken state.Converting an existing table is a manual, documented operation in
db_scripts/partition_spend_logs.sql, since Postgres cannot partition a populated table in place. The script renames the existing table aside, creates the partitioned parent, the composite primary key, thestartTimeindex, and aDEFAULTpartition as a safety net. The partition key must be part of the primary key, so the key becomes the composite(request_id, startTime). The live spend-log write path usescreate_many(skip_duplicates=True)which compiles toINSERT ... ON CONFLICT DO NOTHINGand is compatible with the composite key.New code is
litellm/proxy/db/db_transaction_queue/spend_logs_partition_manager.py, wired intoSpendLogCleanupby dependency injection. Partition naming, range math, retention selection, and bound parsing are pure functions with direct unit tests; the manager's DB methods and both cleanup branches (partitioned drop vs. non-partitioned delete) are covered with mocked Prisma clients. Granularity and pre-create lookahead are tunable viaSPEND_LOG_PARTITION_INTERVAL(defaultday) andSPEND_LOG_PARTITION_PRECREATE_AHEAD(default7).Docs are in a companion PR on
BerriAI/litellm-docs.Follow-ups (not in this PR, to keep scope isolated)
The legacy
insert_data(table_name="spend")upsert path usesON CONFLICT (request_id), which would not work against the composite key. It has no active spend-log writer today (the live path iscreate_many), so it is out of scope here and noted for a follow-up if it is ever reintroduced.Screenshots / Proof of Fix
Proof of fix against a live partitioned table to follow before this leaves draft. Manual verification plan: convert a dev DB with
db_scripts/partition_spend_logs.sql, setmaximum_spend_logs_retention_period, send real traffic so multiple day-partitions are created, then confirm via\d+ "LiteLLM_SpendLogs"that expired partitions are dropped on the cleanup run while recent ones remain.Generated by Claude Code