Skip to content

feat(sessions): add BufferedFirestoreSessionService#160

Open
enesdemirag wants to merge 4 commits into
google:mainfrom
enesdemirag:feat/firestore-buffered-session-service
Open

feat(sessions): add BufferedFirestoreSessionService#160
enesdemirag wants to merge 4 commits into
google:mainfrom
enesdemirag:feat/firestore-buffered-session-service

Conversation

@enesdemirag

@enesdemirag enesdemirag commented Jun 19, 2026

Copy link
Copy Markdown

Closes #159

Summary

Adds BufferedFirestoreSessionService to google.adk_community.sessions.

The builtin FirestoreSessionService writes one Firestore transaction per event. This service buffers events in memory and flushes them all in a single transaction — collapsing N session-doc + state-doc updates into 1. The event-document count is unchanged; what improves is write amplification and optimistic-lock contention.

Set durable_mode=True for per-event writes (same as builtin) — making this a drop-in replacement with the same data layout.

Changes

  • src/google/adk_community/sessions/firestore_session_service.py
  • src/google/adk_community/sessions/__init__.py (export)
  • pyproject.toml ([firestore] extra, >=2.11,<3 matching ADK's own constraint)
  • tests/unittests/sessions/test_firestore_session_service.py (20 tests)

Constructor options

Param Default Description
client auto firestore.AsyncClient
root_collection adk-session Matches ADK builtin
sessions_collection sessions Customisable
events_collection events Customisable
app_state_collection app_states Customisable
user_state_collection user_states Customisable
flat_layout False True → sessions at root/{session_id} (no {app}/users/{user} nesting)
durable_mode False True → per-event writes, same as builtin
buffer_max_events 10 Flush threshold
flush_interval_seconds 120.0 Background flush cadence
max_retry_attempts 5 Retries on transient errors
retry_base_delay_seconds 0.5 Exponential backoff base

Testing plan

pytest tests/unittests/sessions/test_firestore_session_service.py -v

20 passed (in-memory fake AsyncClient, no external services):
create/get/list/delete, 9 events → 1 transaction (batched flush), threshold + interval + explicit + stop flush, durable mode (per-event), retry then success, permanent error no retry, events during flush survive, get_session merges buffered events, app/user/session/temp state delta scoping.

Formatted with pyink --config pyproject.toml + isort.

Checklist

  • Tests added (20, in-memory fake client)
  • Formatted (pyink + isort)
  • [firestore] optional extra in pyproject.toml
  • Exported from sessions/__init__.py
  • @override on all overridden methods
  • Google CLA signed

@google-cla

google-cla Bot commented Jun 19, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@enesdemirag

Copy link
Copy Markdown
Author

Signed the CLA.

Add a Firestore-backed session service that buffers events in memory and
flushes them in a single transaction per session, collapsing the repeated
session-doc/state-doc updates and per-event transactions from N to 1.

Mirrors the data model of the builtin
google.adk.integrations.firestore.FirestoreSessionService (collection
hierarchy, app/user/session state scoping, optimistic concurrency via a
revision field, idempotent event docs keyed by event.id) and adds:
- in-memory per-session buffering with count/interval/explicit/shutdown flush
- durable_mode to persist every event immediately
- exponential backoff with jitter on retryable errors
- start()/stop()/flush() ADK lifecycle hooks

Gated behind the new optional [firestore] extra. Unit tests use an in-memory
fake AsyncClient (no external services).
- Remove unused _SessionLockKey type alias
- Move import copy to module level (stdlib, no reason to lazy-import)
- Store self._firestore in __init__ to avoid repeated guarded imports inside
  create_session / _persist_batch
- Add sessions_collection, events_collection, app_state_collection, and
  user_state_collection constructor params (keyword-only, with defaults) so
  developers can customise the Firestore collection layout without subclassing
…erarchy

Add flat_layout=True constructor parameter so developers can store sessions
directly in root_collection/{session_id} instead of the default nested
root/{app}/users/{user}/sessions/{session_id} path.

Useful when the session id already encodes the user (e.g. {phone}-{date}),
matching an existing flat Firestore collection layout. list_sessions adds a
userId field filter automatically in flat mode.
…yink

- Drop `from __future__ import annotations`; use X | None syntax directly
  (requires Python >=3.10, already in project metadata)
- Remove Optional import; all annotations now use built-in union syntax
- Remove vague section-header comments
- Simplify is_retryable_error return (single return name in _RETRYABLE_ERROR_NAMES)
- Update firestore extra version range to match ADK's own constraint (>=2.11,<3)
@enesdemirag enesdemirag force-pushed the feat/firestore-buffered-session-service branch from 3f311ca to c411073 Compare June 22, 2026 17:08
@enesdemirag

enesdemirag commented Jun 22, 2026

Copy link
Copy Markdown
Author

Okay I completed the CLA. Waiting for a review.

@enesdemirag

Copy link
Copy Markdown
Author

This buffered approach was my need and I think many more people may want to use it as well.

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.

feat: Add BufferedFirestoreSessionService (batched, buffered Firestore session backend)

1 participant