feat(spend): raise /spend/logs/v2 page_size cap to 1000

Clients exporting large spend-log ranges were forced into 100-row pages,
which meant a bounded COUNT plus an increasingly deep OFFSET scan per
request. Larger pages reduce both the request count and the cumulative
OFFSET cost for the same result set.

The handler already excludes the heavy JSON columns (messages, response,
proxy_server_request) from the paginated SELECT and bounds the COUNT via
SPEND_LOGS_PAGINATION_COUNT_CAP, so per-row cost does not grow with page
size. 1000 matches the ceiling already used by the user and user-agent
analytics list endpoints.
This commit is contained in:
Yuneng Jiang 2026-07-20 09:55:38 -07:00
parent 067c9bbc96
commit 479e997eed
No known key found for this signature in database
2 changed files with 54 additions and 1 deletions

View file

@ -1647,7 +1647,7 @@ async def ui_view_spend_logs(
description="Time till which to view key spend",
),
page: int = fastapi.Query(default=1, description="Page number for pagination", ge=1),
page_size: int = fastapi.Query(default=50, description="Number of items per page", ge=1, le=100),
page_size: int = fastapi.Query(default=50, description="Number of items per page", ge=1, le=1000),
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
status_filter: str | None = fastapi.Query(
default=None, description="Filter logs by status (e.g., success, failure)"

View file

@ -1467,6 +1467,59 @@ async def test_ui_view_spend_logs_pagination(client, monkeypatch):
app.dependency_overrides.pop(ps.user_api_key_auth, None)
@pytest.mark.parametrize(
"page_size, expected_status, expected_rows",
[
(1000, 200, 1000),
(1001, 422, None),
],
)
@pytest.mark.asyncio
async def test_ui_view_spend_logs_page_size_upper_bound(
client, monkeypatch, page_size, expected_status, expected_rows
):
mock_spend_logs = [
{
"id": f"log{i}",
"request_id": f"req{i}",
"api_key": "sk-test-key",
"startTime": datetime.datetime.now(timezone.utc).isoformat(),
"model": "gpt-4",
}
for i in range(1200)
]
monkeypatch.setattr(
"litellm.proxy.proxy_server.prisma_client",
make_ui_spend_logs_mock_prisma(mock_spend_logs, lambda where: mock_spend_logs),
)
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin_user"
)
try:
start_date, end_date = _default_date_range()
response = client.get(
"/spend/logs/v2",
params={
"page": 1,
"page_size": page_size,
"start_date": start_date,
"end_date": end_date,
},
headers={"Authorization": "Bearer sk-test"},
)
assert response.status_code == expected_status
if expected_status == 200:
data = response.json()
assert data["page_size"] == page_size
assert len(data["data"]) == expected_rows
finally:
app.dependency_overrides.pop(ps.user_api_key_auth, None)
@pytest.mark.asyncio
async def test_ui_view_session_spend_logs_pagination(client, monkeypatch):
mock_spend_logs = [