litellm/tests/test_litellm/proxy/common_utils
ryan-crabbe-berri 187e0fab60 feat(proxy): maintain per-window budget spend rows in the spend writer
Multi-window budgets (budget_limits on keys and teams) enforce off Redis
counters with a 60s TTL. Every cold counter, and every authoritative floor
check, aggregates LiteLLM_SpendLogs with a range scan over startTime; that
table has no index on api_key or team_id, so the scan lands on the highest
volume table in the schema and saturates the connection pool (#35766).

Every other budget feature reads a maintained running spend value instead.
This gives windows the same shape by keeping LiteLLM_BudgetWindowSpend up to
date: one row per configured window, whose window_start rolls forward in
place.

The cost callback already iterates a key's and a team's windows with the
window start computed and the actual cost in hand, so it enqueues there, onto
a new WindowSpendUpdateQueue. Increments are enqueued even when the cache
increment is skipped for a reserved counter, since the reservation only
pre-charged an estimate and the row still owes the actual cost. Windows with
no reset_at slide with wall clock and cannot be represented by a single row,
so they are left to the read path's existing aggregate.

The queue flushes alongside the daily spend queues, through the Redis buffer
when one is configured (only the pod-lock winner commits) and directly
otherwise. A flush selects the primary keys that already exist, seeds the
ones that do not from LiteLLM_SpendLogs so a new row cannot undercount spend
that predates it, and applies one batch of upserts ordered by primary key. An
increment at or behind the stored window_start adds into the row, matching how
in-flight requests carry into a window after a reset; a newer one rolls the
window and starts from that increment. The conflict arm adds only the
increment, never the seeded base, so two pods seeding the same new window
cannot double count it.

The seed excludes the requests its own batch is about to apply. Spend logs are
drained by a separate monitor that fires on a ~2s poll whenever anything is
queued, while window increments flush on the much slower batch tick, so by
seed time the batch's log rows are normally already in the table; counting
them in the aggregate and again in the increments made a fresh row land at
exactly twice the true spend. Each increment therefore carries the
LiteLLM_SpendLogs request_id it was recorded under, which update_database now
returns rather than having the callback re-derive it (a cache hit appends
time.time() to that id, so a second derivation would not match).

The reset job rolls each expired window's row alongside the counter it zeroes,
conditional on the stored window_start still being behind the new one so a
pod that already rolled it is not clobbered.
2026-08-04 18:55:48 -07:00
..
html_forms feat(proxy): configurable response headers and login-page hint (#30792) 2026-06-18 18:12:45 -07:00
test_cache_codec.py fix(proxy): stop CacheCodec dropping null fields on cache round-trip (#32207) 2026-07-06 12:53:00 -07:00
test_callback_utils.py fix(proxy): sanitize per-key callback config out of logged metadata 2026-07-24 22:16:50 -07:00
test_config_sync_pubsub.py fix(proxy): persist periodic reload schedule state so status survives restarts and fires without store_model_in_db (#35165) 2026-08-04 15:42:57 -07:00
test_custom_openapi_spec.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_encrypt_decrypt_utils.py fix(proxy): stop the decrypt-failure debug log from leaking the raw value 2026-07-03 13:03:45 -07:00
test_expired_ui_session_key_cleanup_manager.py Handle cleanup delete races and accurate counts 2026-04-25 03:01:34 +03:00
test_get_routes.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_http_parsing_utils.py perf(proxy): bound event-loop blocking from oversized requests (#31497) 2026-06-27 12:06:48 -07:00
test_json_merge_patch.py fix(team): bound json merge patch recursion depth 2026-07-11 09:36:15 -07:00
test_key_rotation_e2e.py Litellm key rotation bug (#27756) 2026-05-12 17:16:37 -07:00
test_key_rotation_integration.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_key_rotation_lock.py Litellm ishaan april4 2 (#25150) 2026-04-04 23:09:42 +00:00
test_key_rotation_manager.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_load_config_utils.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_openai_endpoint_utils.py fix(vector-stores): recurse into nested litellm_params; handle JSON-string shape 2026-04-29 18:56:40 +00:00
test_path_utils.py test: add unit tests for path_utils safe_join and safe_filename 2026-04-16 03:25:42 +00:00
test_periodic_reload_schedule.py fix(proxy): persist periodic reload schedule state so status survives restarts and fires without store_model_in_db (#35165) 2026-08-04 15:42:57 -07:00
test_reset_budget_job.py feat(proxy): maintain per-window budget spend rows in the spend writer 2026-08-04 18:55:48 -07:00
test_static_asset_utils.py fix(static-assets): browser-load remote branding assets 2026-04-30 11:30:57 -07:00
test_timezone_utils.py feat(budgets): add configurable budget_reset_time of day (#31007) 2026-07-21 13:35:01 -07:00
test_upsert_budget_membership.py feat(ui): add budget duration to edit team member form (#29717) 2026-06-06 17:24:55 -07:00
test_user_api_key_cache.py fix(proxy/auth): honor user_api_key_cache_ttl for management-object cache writes (#31504) 2026-06-27 12:19:28 -07:00