google / google/adk-python

Feature request: native SpannerSessionService (no SQLAlchemy dependency)

Open
#4,772 2 comments 1 reaction 2 assignees Claimed by @wuliang229 View on GitHub
needs review services
Dominant language
Python
Stars
21.5k
Forks
4k
Avg merge
1d 14h
Merged PRs (30d)
37

Description

## Summary

Spanner users of `DatabaseSessionService` are blocked from upgrading past ADK 1.18 because there is no official async SQLAlchemy dialect for Spanner. ADK 1.18+ requires `create_async_engine`, but `sqlalchemy-spanner` only provides a synchronous dialect. Users must either maintain a custom async wrapper or stay pinned to old ADK versions.

A native `SpannerSessionService` using the `google-cloud-spanner` client directly would unblock Spanner users and eliminate an entire class of bugs caused by the SQLAlchemy abstraction layer.

## Motivation

### 1. No official async Spanner dialect for SQLAlchemy

`sqlalchemy-spanner` (maintained by Google in `googleapis/python-spanner-sqlalchemy`) only provides a synchronous dialect. Post-1.18, `DatabaseSessionService` requires an async engine (`create_async_engine` + `async_sessionmaker`). Spanner users must write and maintain a custom async wrapper to bridge this gap, which is fragile and unsupported.

### 2. SQLAlchemy abstracts away Spanner-specific features

`DatabaseSessionService` uses a single `async_sessionmaker` for all operations. This means read-only methods (`get_session`, `list_sessions`) run inside read-write transactions, causing `RetryAborted` errors from Spanner's OCC when concurrent writes are in flight (#4771). The `google-cloud-spanner` client natively supports read-only snapshots, but SQLAlchemy has no way to express this.

### 3. Small, well-defined interface

`BaseSessionService` defines only 5 operations: `create_session`, `get_session`, `list_sessions`, `delete_session`, `append_event`. ADK already has multiple implementations of this interface (`InMemorySessionService`, `DatabaseSessionService`, `VertexAiSessionService`). A `SpannerSessionService` fits naturally alongside them.

### 4. Precedent

There is already a feature request for `FirestoreSessionService` (#3776) based on the same reasoning — that `DatabaseSessionService` forces users into managing a SQL database when a purpose-built implementation would be simpler and more reliable.

## Proposed design

A `SpannerSessionService` that:

- Uses `google-cloud-spanner` (AsyncClient or thread-pooled Client) directly
- Uses **read-only snapshots** for `get_session()` and `list_sessions()` (eliminates RetryAborted on reads)
- Uses **read-write transactions** only for `create_session()`, `append_event()`, and `delete_session()`
- Reuses the same table schema as `DatabaseSessionService` (schemas v0/v1) for migration compatibility
- Lives under `google.adk.sessions.spanner_session_service`
- Has `google-cloud-spanner` as an optional dependency (extras: `[spanner]`)

Estimated scope: ~200-300 lines for the 5 CRUD operations, plus schema setup.

## Related

- #4771 — `get_session`/`list_sessions` use read-write transactions unnecessarily (symptom of the SQLAlchemy abstraction)
- #4751 — OCC corruption from concurrent `append_event` (Spanner concurrency semantics lost behind SQLAlchemy)
- #3776 — Feature request for native `FirestoreSessionService` (same pattern: purpose-built > generic ORM)
- #4673 — `get_session` loads all events (easier to optimize with direct Spanner queries)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.