google / google/adk-python

Feature request: native SpannerSessionService (no SQLAlchemy dependency)

未关闭
#4,772 2 条评论 1 个 reaction 已指派 2 人 已被 @wuliang229 认领 在 GitHub 查看
needs review services
主要语言
Python
星标
21.5k
派生
4k
平均合并
1 天 14 小时
30 天内合并 PR
37

描述

## 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)

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。