Skip to content

feat(sessions): add optional pagination to list_sessions - #7799

Open
inamdarmihir wants to merge 1 commit into
google:mainfrom
inamdarmihir:feat/4621-list-sessions-pagination
Open

inamdarmihir wants to merge 1 commit into
google:mainfrom
inamdarmihir:feat/4621-list-sessions-pagination

Conversation

@inamdarmihir

Copy link
Copy Markdown
Contributor

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

Problem:
list_sessions returns every session for an app/user with no way to limit the result. The issue reports ~74s with ~10k sessions per app, which makes the sessions endpoint unusable.

Solution:
Optional, backward-compatible keyset pagination. With no new arguments the behavior is exactly as before.

  • list_sessions(..., page_size: Optional[int] = None, page_token: Optional[str] = None) and ListSessionsResponse.next_page_token (also on InMemorySessionService.list_sessions_sync).
  • The token is an opaque cursor on the existing (last_update_time, user_id, id) order, so ties are deterministic and pages stay correct when sessions are added or deleted between calls. It records app_name / user_id; a token from another filter, or a malformed one, raises ValueError. A call fetches page_size + 1 rows, so an exact multiple has no empty trailing page and the last page has no token. page_size must be a positive int.
  • DatabaseSessionService and SqliteSessionService push the cursor condition and LIMIT into SQL (spelled as OR clauses since MSSQL has no row-value comparison); the in-memory service sorts references and copies only the returned page.
  • Vertex AI, Redis, MongoDB and Firestore services accept the same arguments and give the same ordering and cursors but still load and slice client-side (documented in their docstrings); Vertex AI's native page token cannot order by update time. The per-agent database wrapper in the CLI forwards the arguments.
  • Web endpoint GET /apps/{app}/users/{user}/sessions gets optional page_size and page_token query parameters, returns the next token in an X-Next-Page-Token header (added to CORS expose_headers) so the body stays a plain list, and passes the new arguments only when supplied. A service whose list_sessions has the old signature gets a 501 only if a client asks for paging; a ValueError from a supplied token becomes a 400.

A session updated between pages moves to the end of the order and can appear again later; this is documented and tested. adk-js uses offset paging; I chose cursor paging because offsets skip or repeat rows when data changes.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

tests/unittests/sessions/test_list_sessions_pagination.py runs on the shared session_service fixture (in-memory plain and light-copy, database, sqlite, Redis, MongoDB, per-agent database). Cases: default unchanged; page larger than total; page sizes 1, 2, 3, 4, 7, 8, 100; exact multiple; empty app and unknown user; user filter with paging; invalid page_size; 12 malformed token shapes; tokens from another app or user filter; sessions added, deleted and updated between pages; equal update times; app/user state still merged. Plus Vertex AI, Firestore and endpoint tests (paging with header, 422/400, empty token, 501 for an old-signature service).

pytest tests/unittests/sessions tests/unittests/cli/test_fast_api.py tests/unittests/integrations/firestore
1364 passed, 5 skipped, 1 xfailed

Manual End-to-End (E2E) Tests:

Throwaway script paging 23 sessions across three users with page sizes 1, 2, 5, 10, 23, 24 and 100 on the in-memory, database and sqlite services: every session returned exactly once, in the same order as the unpaged listing. Only sqlite was exercised for the SQL path; not run against Postgres, MySQL or MSSQL.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Additional context

The change is large because the base signature is shared by every backend. If maintainers prefer, I can split it into the base/in-memory/SQL services first and the other backends and the endpoint afterwards.

list_sessions returned every session for an app/user, which takes over a
minute for ~10k sessions. Add optional page_size and page_token arguments
and an optional next_page_token on ListSessionsResponse. Without them the
behavior is unchanged.

The token is an opaque keyset cursor on the existing
(last_update_time, user_id, id) order, scoped to the app_name and user_id
it was issued for, so sessions added, updated or removed between pages
never cause others to be skipped or repeated. The database and sqlite
services apply the cursor and LIMIT in SQL; the in-memory service copies
only the returned page. Vertex AI, Redis, MongoDB and Firestore cannot
order by update time natively, so they page the sorted list client-side.

The web endpoint takes optional page_size/page_token query parameters and
returns the next token in the X-Next-Page-Token header, passing them to
the session service only when given so services with the old signature
keep working.

Fixes google#4621
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DatabaseSessionService.list_sessions lacks pagination support

2 participants