Feature request: native SpannerSessionService (no SQLAlchemy dependency)
- 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.