Skip to content

docs(spanner): document behavioral differences between regular and multiplexed sessions #16619

@waiho-gumloop

Description

@waiho-gumloop

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions