google / google/adk-python

Feature request: native SpannerSessionService (no SQLAlchemy dependency)

Ouverte
#4,772 2 commentaires 1 réaction 2 personnes assignées Réclamée par @wuliang229 Voir sur GitHub
needs review services
Langage dominant
Python
Étoiles
21.5k
Forks
4k
Merge moyen
1 j 14 h
PR mergées (30 j)
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)

Guide de contribution

Ouvrir le guide de contribution

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.