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 = True
labels 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 prepends SELECT 1 to obtain a token.
- Previous transaction ID:
multiplexed_session_previous_transaction_id is passed in ReadWrite transaction 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-spanner v3.63.0
- Python 3.10
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, oradvanced-session-pool-topics.rstcovering 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-spannerv3.63.0 on 2026-04-11.Behavioral Differences
multiplexedfield in CreateSession response"multiplexed": trueHow the server distinguishes them
The client sets
session.multiplexed = Truein theCreateSessionRequestprotobuf:labelsandmultiplexedare set independently. There is no client-side guard against setting both. The server enforces the restriction.API verification
Test 1 — Regular session with labels
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
Response:
400 INVALID_ARGUMENT{ "error": { "code": 400, "message": "Labels are not supported in multiplexed sessions", "status": "INVALID_ARGUMENT" } }Test 3 — Multiplexed session without labels
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
Test 5 — ListSessions visibility
A multiplexed session created via
CreateSession(confirmed by"multiplexed": truein the response) does not appear in subsequentListSessionsresults — neither via the REST API norgcloud spanner databases sessions list.This is consistent with the protobuf docstring on the
Sessionmessage: "You can't delete or list multiplexed sessions."Implication: The only server-side way to observe multiplexed sessions is through the built-in OTel metrics with the
is_multiplexedattribute filter. There is no Admin API method to enumerate active multiplexed sessions.Library architecture
Regular session flow
Multiplexed session flow
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:CommitRequest. For mutation-only transactions (no prior query/DML), the library prependsSELECT 1to obtain a token.multiplexed_session_previous_transaction_idis passed inReadWritetransaction options for server-side ordering of transactions on the same multiplexed session.Environment variables
Setting any to
"false"falls back to pool-based regular sessions for that transaction type.DatabaseSessionsManager._use_multiplexed()reads these at runtime viaos.getenv()with a default of"true".Built-in OTel metrics
Spanner's built-in client metrics include an
is_multiplexedattribute (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 withSPANNER_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 toadvanced-session-pool-topics.rst.Environment
google-cloud-spannerv3.63.0