Skip to content

perf(spanner): add fast-path for multiplexed session acquisition - #18317

Merged
olavloite merged 1 commit into
mainfrom
spanner-fast-path-mux-session-acquisition
Sep 10, 2026
Merged

olavloite merged 1 commit into
mainfrom
spanner-fast-path-mux-session-acquisition

Conversation

@olavloite

@olavloite olavloite commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor
  • Bypass initialization and multiplexed session locks on steady-state queries.
  • Build replacement sessions outside the lock during maintenance rotation, holding the lock only for the pointer swap.
  • Replace maintenance sleep loops with event wait for immediate shutdown termination.
  • Use monotonic time for maintenance intervals and clear local manager references before waiting.

Results Summary

Metric PR #18317 (spanner-fast-path-mux-session-acquisition) 7-Day Nightly Baseline (main) Delta Absolute Difference
Sample Count 628,791 ops 300,566,193 ops — —
Mean Latency 5.327 ms 5.344 ms -0.32% -0.017 ms (-17 us)
P50 Latency 4.962 ms 4.996 ms -0.68% -0.034 ms (-34 us)
P90 Latency 6.624 ms 6.769 ms -2.15% -0.145 ms (-145 us)
P99 Latency 11.068 ms 10.700 ms +3.44% +0.368 ms

The performance gain from this optimization is minimal in an end-to-end test.

@olavloite
olavloite requested a review from a team as a code owner September 9, 2026 10:59

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors multiplexed session management in both the synchronous and asynchronous database session managers. Key changes include introducing a fast path for retrieving already-initialized multiplexed sessions, replacing time.time() with time.monotonic() for interval calculations, implementing a safer session rotation mechanism (_rotate_multiplexed_session) that swaps sessions before deletion, and using event-based waiting for cleaner termination. Extensive unit tests have been added to cover these changes. The review feedback highlights multiple violations of the Repository Style Guide (Section 4: Unit Testing and Mock Hygiene) where time.monotonic is patched globally in the tests. To ensure test isolation, these mocks should be localized to the target module's import path.

- Bypass initialization and multiplexed session locks on steady-state queries.
- Build replacement sessions outside the lock during maintenance rotation, holding the lock only for the pointer swap.
- Replace maintenance sleep loops with event wait for immediate shutdown termination.
- Use monotonic time for maintenance intervals and clear local manager references before waiting.
@olavloite
olavloite force-pushed the spanner-fast-path-mux-session-acquisition branch from 1b8b33e to 7bf0dcb Compare September 9, 2026 11:34
@olavloite

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the multiplexed session management in both the synchronous and asynchronous database session managers. Key changes include adding a fast path in _get_multiplexed_session to return an existing session without locking, improving thread safety by assigning sessions and maintenance threads to local variables before updating instance state, introducing a _rotate_multiplexed_session helper to safely swap and delete sessions, and replacing the polling sleep in _maintain_multiplexed_session with an event-wait mechanism using time.monotonic(). Additionally, comprehensive unit tests have been added to verify these new behaviors and edge cases. No review comments were provided, so there is no feedback to address.

await self._multiplexed_session.delete()
session_to_delete = self._multiplexed_session
self._multiplexed_session = None
await session_to_delete.delete()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This is strictly speaking a bug. Multiplexed sessions cannot be deleted. I will fix this in a separate follow-up pull request. I kept it for now to keep the change as small as possible.

Comment on lines -144 to +149
self._multiplexed_session_thread.start()
maintenance_thread.start()
self._multiplexed_session_thread = maintenance_thread
self._multiplexed_session = session

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assume if maintenance_thread.start() fails, now the local session gets orphaned on the Spanner backend because self._multiplexed_session isn't updated until like 149. I believe this opens a scope for session leaks.

Old code assigned it immediately after creation, ensuring close() can always clean it up if things go wrong.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed from your other comment that multiplexed sessions cannot be deleted. We should be good here then.

@olavloite
olavloite merged commit 9569b39 into main Sep 10, 2026
47 checks passed
@olavloite
olavloite deleted the spanner-fast-path-mux-session-acquisition branch September 10, 2026 14:59
parthea pushed a commit that referenced this pull request Nov 12, 2026
🤖 I have created a release *beep* *boop*
---


##
[3.72.0](google-cloud-spanner-v3.71.0...google-cloud-spanner-v3.72.0)
(2026-11-12)


### Features

* **dbapi:** wire timeout parameter through Connection to execute_sql
([#18484](#18484))
([98fc304](98fc304))
* declare Python3.15 support
([8775794](8775794))
* **spanner:** auth login support for Spanner Omni endpoints
([#18273](#18273))
([9e0548d](9e0548d))
* update API sources and regenerate
([#18797](#18797))
([be4d954](be4d954))


### Bug Fixes

* **google-cloud-spanner:** clear buffered DDL statements on rollback
and flush on autocommit
([#18636](#18636))
([15d6a2c](15d6a2c))
* **google-cloud-spanner:** do not replay DDL statements on transaction
retry
([#18625](#18625))
([78b9da2](78b9da2))
* **spanner:** correctly wrap async streaming calls in metrics
interceptor
([#18581](#18581))
([2d19b0a](2d19b0a))
* **spanner:** eliminate duplicate metrics capture and measure full
streaming latency
([#18605](#18605))
([a7678fd](a7678fd))
* **spanner:** record streaming operation completion when stream
finishes
([#18704](#18704))
([ddeae47](ddeae47))
* **spanner:** release transaction lock if inline begin fails
([#18409](#18409))
([dd24029](dd24029))
* **spanner:** stop deleting multiplexed sessions on rotation and close
([#18332](#18332))
([c38e960](c38e960))


### Performance Improvements

* **spanner:** add fast-path for multiplexed session acquisition
([#18317](#18317))
([9569b39](9569b39))
* **spanner:** avoid allocating empty RequestOptions on read and query
paths
([#18422](#18422))
([3743ff0](3743ff0))
* **spanner:** build ExecuteSqlRequest on the raw protobuf message
([#18379](#18379))
([6d91fec](6d91fec))
* **spanner:** eliminate redundant tracing span in run_in_transaction
([#18357](#18357))
([181e26a](181e26a))
* **spanner:** optimize built-in metrics hot path and harden concurrency
([#18329](#18329))
([f2a791f](f2a791f))
* **spanner:** optimize query option merging and prevent in-place
mutation
([#18358](#18358))
([8cb3c6a](8cb3c6a))
* **spanner:** optimize query parameter encoding with direct type
dispatch
([#18359](#18359))
([f06497c](f06497c))
* **spanner:** optimize query result decoding and type resolution
([#18330](#18330))
([ce72986](ce72986))
* **spanner:** optimize request ID header generation and retry closures
([#18408](#18408))
([ad03b95](ad03b95))
* **spanner:** optimize row popping in StreamedResultSet
([#18316](#18316))
([e66103d](e66103d))
* **spanner:** optimize single-chunk results and single-row lookups
([#18602](#18602))
([05b3775](05b3775))
* **spanner:** prune lock and begin event allocations on single-use
snapshots
([#18420](#18420))
([ab56473](ab56473))
* **spanner:** short-circuit trace_call when tracing is inactive
([#18356](#18356))
([58031be](58031be))
* **spanner:** support PartialResultSet.last with background stream
draining
([#18320](#18320))
([80efd63](80efd63))


### Documentation

* fix typos
([#18979](#18979))
([5c8baeb](5c8baeb))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants