Repository navigation
docs(spanner): document behavioral differences between regular and multiplexed sessions #16619
Description
Summary
The Python Spanner client library (google-cloud-spanner) added multiplexed session support across PRs #1381, #1383, #1389, and #1394, and made them default-enabled in v3.57.0+.
There is currently no documentation in the library's docs/ RST pages, README, or advanced-session-pool-topics.rst covering the behavioral differences between regular and multiplexed sessions. The references that exist are autogenerated protobuf docstrings and CHANGELOG entries.
The following was verified via REST API testing and source code inspection against google-cloud-spanner v3.63.0 on 2026-04-11.
Behavioral Differences
| Property | Regular Session | Multiplexed Session |
|---|---|---|
| Concurrent transactions | 1 per session | N per session (routed by transaction_id) |
| Labels | Supported | Server returns 400 INVALID_ARGUMENT: Labels are not supported in multiplexed sessions |
| Session ID length | 62 chars | 68 chars |
multiplexed field in CreateSession response |
Absent | "multiplexed": true |
| OTel span name on create | CloudSpanner.CreateSession | CloudSpanner.CreateMultiplexedSession |
| Delete via API | DeleteSession returns 200 OK | DeleteSession returns 400 |
| Lifecycle | Client-managed — create, ping, delete | Auto-rotated every 7 days by library maintenance thread; no keep-alive needed |
| Idle timeout | ~60 min (server GC) | Long-lived, no idle timeout |
| Pool integration | Checked out/in from SessionPool | Singleton per DatabaseSessionsManager; never pooled |
| Pre-commit token | Not used | Required — server sends token with each query/DML; must be included in CommitRequest |
| Mutation-only transactions | Begin normally | Library prepends SELECT 1 to obtain pre-commit token |
| Previous transaction ID | Not used | multiplexed_session_previous_transaction_id passed in BeginTransactionRequest |
| Batch creation | BatchCreateSessions supported | BatchCreateSessions cannot create multiplexed sessions |
| Visibility in ListSessions | Visible | Not returned — server excludes multiplexed sessions from listing |
How the server distinguishes them
The client sets session.multiplexed = True in the CreateSessionRequest protobuf:
# google/cloud/spanner_v1/session.py, lines 173-179
create_session_request = CreateSessionRequest(database=database.name)
if self._labels:
create_session_request.session.labels = self._labels
if self._is_multiplexed:
create_session_request.session.multiplexed = Truelabels and multiplexed are set independently. There is no client-side guard against setting both. The server enforces the restriction.
API verification
Test 1 — Regular session with labels
curl -X POST "https://spanner.googleapis.com/v1/projects/PROJECT/instances/INSTANCE/databases/DB/sessions" \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-d '{ "session": { "labels": {"env":"test", "pod":"test-pod", "service":"test-svc"} } }'Response: 200 OK
{
"name": "projects/.../sessions/AMkeXgpXcn6_fz0Gn3Yrf285Y35OgcWE8z0DZGZ3IaUN9aUVTMC8bLudNLUAiA",
"labels": { "service": "test-svc", "pod": "test-pod", "env": "test" },
"createTime": "2026-04-11T19:35:48.946415Z",
"approximateLastUseTime": "2026-04-11T19:35:48.948744Z"
}Test 2 — Multiplexed session with labels
curl -X POST ".../sessions" \
-d '{ "session": { "labels": {"env":"test"}, "multiplexed": true } }'Response: 400 INVALID_ARGUMENT
{
"error": {
"code": 400,
"message": "Labels are not supported in multiplexed sessions",
"status": "INVALID_ARGUMENT"
}
}Test 3 — Multiplexed session without labels
curl -X POST ".../sessions" \
-d '{ "session": { "multiplexed": true } }'Response: 200 OK
{
"name": "projects/.../sessions/AMkeXgrSFuqnRwBCAsbKWtQZ3-8idoYHiXuCNzkbTKOVdcZ7tApRmh6_0ORRXirCXO5b",
"createTime": "2026-04-11T19:36:06.805225Z",
"multiplexed": true
}Note: Session ID is 68 chars vs 62 for the regular session in Test 1.
Test 4 — Delete behavior
| Target | DeleteSession result | Notes |
|---|---|---|
| Regular session | 200 OK | Deleted |
| Multiplexed session | 400 | Cannot be manually deleted; server manages lifecycle |
Test 5 — ListSessions visibility
A multiplexed session created via CreateSession (confirmed by "multiplexed": true in the response) does not appear in subsequent ListSessions results — neither via the REST API nor gcloud spanner databases sessions list.
This is consistent with the protobuf docstring on the Session message: "You can't delete or list multiplexed sessions."
# Create a multiplexed session
MUX_ID=$(curl -s -X POST ".../sessions" \
-d '{ "session": { "multiplexed": true } }' | python3 -c "import json,sys; print(json.load(sys.stdin)['name'].split('/')[-1])")
# Try to find it in ListSessions
curl -s ".../sessions?pageSize=500" | python3 -c "
import json, sys
sessions = json.load(sys.stdin).get('sessions', [])
found = any(s['name'].endswith('$MUX_ID') for s in sessions)
print(f'Found in ListSessions: {found}') # prints: False
"Implication: The only server-side way to observe multiplexed sessions is through the built-in OTel metrics with the is_multiplexed attribute filter. There is no Admin API method to enumerate active multiplexed sessions.
Library architecture
Regular session flow
SessionPool (PingingPool, TransactionPingingPool, etc.)
├── N sessions pre-created at pool init (via BatchCreateSessions)
├── get() → checks out a Session for 1 transaction
├── put() → returns Session to pool
└── ping() → periodic keep-alive SELECT 1 to prevent idle GC
Multiplexed session flow
DatabaseSessionsManager
├── 1 multiplexed Session singleton (created on first use via CreateSession)
├── get_session() → returns the same Session for all transaction types
├── put_session() → no-op (session is never returned to a pool)
└── maintenance thread:
├── polls every 10 minutes
└── recreates session every 7 days (rotation)
Transaction protocol differences
Regular sessions: each Session.run_in_transaction() uses a dedicated session. Concurrent transactions require separate sessions from the pool.
Multiplexed sessions: all concurrent transactions share one session. The server routes them by transaction_id. Additional protocol requirements:
- Pre-commit token: Server returns a token with each query/DML. The client must include it in
CommitRequest. For mutation-only transactions (no prior query/DML), the library prependsSELECT 1to obtain a token. - Previous transaction ID:
multiplexed_session_previous_transaction_idis passed inReadWritetransaction options for server-side ordering of transactions on the same multiplexed session.
Environment variables
| Variable | Default (v3.57.0+) | Controls |
|---|---|---|
| GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS | true | Read-only transactions use multiplexed sessions |
| GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW | true | Read-write transactions use multiplexed sessions |
| GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_PARTITIONED_OPS | false | Partitioned operations use multiplexed sessions |
Setting any to "false" falls back to pool-based regular sessions for that transaction type. DatabaseSessionsManager._use_multiplexed() reads these at runtime via os.getenv() with a default of "true".
Built-in OTel metrics
Spanner's built-in client metrics include an is_multiplexed attribute (see Spanner docs: View traffic for regular and multiplexed sessions). This can be used in Metrics Explorer to filter between session types. The built-in metrics can be disabled with SPANNER_DISABLE_BUILTIN_METRICS=true.
Request
Add documentation covering the differences listed above. This could be a new RST page (e.g. docs/multiplexed-sessions.rst) or an addition to advanced-session-pool-topics.rst.
Environment
google-cloud-spannerv3.63.0- Python 3.10
Activity