Repository navigation
perf(spanner): optimize query result decoding and type resolution - #18330
Merged
Merged
Conversation
Improve query result decoding performance in StreamedResultSet and simplify
type decoder resolution in _helpers.
* Optimize row construction in StreamedResultSet._merge_values:
- Process complete rows in batch using direct column indexing rather than
iterating cell-by-cell with repeated append() calls and boundary checks.
- For lazy decoding, slice raw values directly into rows.
- Add an explicit guard for width == 0 to prevent unbounded buffer growth
on empty schemas.
- Separate partial row boundary handling from complete row decoding by
extracting _append_to_current_row, _decode_eager_rows, and
_decode_lazy_rows, removing duplicated cell-appending logic.
* Optimize type resolution in _helpers:
- Replace the if/elif cascade in _get_type_decoder with an integer-keyed
_SCALAR_DECODERS lookup table.
- Pre-allocate static decoder callables at module scope to avoid lambda
recreation when resolving column metadata.
Contributor
There was a problem hiding this comment.
Code Review
This pull request refactors and optimizes row decoding and value merging in both synchronous and asynchronous streamed result sets, introducing batch-decoding helpers and mapping scalar type codes to a pre-defined dictionary of decoders. Reviewers suggested further performance optimizations, including removing redundant integer conversions for IntEnum keys in _get_type_decoder, and using zip with sliced lists in _append_to_current_row and _decode_eager_rows to avoid the overhead of index lookups and enumeration.
sinhasubham
reviewed
Sep 28, 2026
sinhasubham
approved these changes
Sep 28, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Improve query result decoding performance in StreamedResultSet and simplify type decoder resolution in _helpers.
Benchmark Results: Lazy Decode Performance (Head-to-Head vs
main)We benchmarked this PR branch (
spanner-decode-and-type-resolution) concurrently against the currentmainbranch under identical conditions on Google Compute Engine (n2-standard-2, 2 vCPUs, single worker thread, closed-loop ineurope-north1-a) usinglazy_decode=True.This workload measures pure result set streaming and raw protobuf cell iteration without eager deserialization into rich Python types.
1. Wide Result Set (
read-large-result-set, 100,000 rows/query,lazy_decode=True)main2. Narrow Result Set (
read-narrow-result-set, 200,000 rows/query,lazy_decode=True)mainObservations
lazy_decode=True: Across repeated concurrent head-to-head runs, replacing cell-by-cell Python iteration in_merge_valueswith native C-level list slicing (_decode_lazy_rows) delivers consistent speedups across both wide and narrow workloads:lazy_decode=False), execution is bounded by network streaming and Python object instantiation (datetime,Decimal,json.loads), tracking within ±1% ofmain. When applications uselazy_decode=True, this optimization delivers significant real-world CPU and latency savings.