Skip to content

perf: buffer accumulation in _write_query_params() reduces f.write() calls (~100ns improvement, 1.1-1.3x speedup) - #790

Open
mykaul wants to merge 1 commit into
scylladb:masterfrom
mykaul:perf/buffer-accum-write-params
Open

perf: buffer accumulation in _write_query_params() reduces f.write() calls (~100ns improvement, 1.1-1.3x speedup)#790
mykaul wants to merge 1 commit into
scylladb:masterfrom
mykaul:perf/buffer-accum-write-params

Conversation

@mykaul

@mykaul mykaul commented Apr 4, 2026

Copy link
Copy Markdown

Summary

Replace per-parameter write_value(f, param) loops with buffer accumulation (list.append + b"".join + single f.write()), reducing f.write() calls from (2*N + 1) to 1 for N query parameters in the execute/query path. Accumulation is flushed in bounded 64KB chunks to avoid an unbounded transient allocation for very large parameter sets (e.g. large vectors), addressing a peak-memory concern raised in review.

What changed

cassandra/protocol.py

  1. _QueryMessage._write_query_params() -- Buffer accumulation for the parameter loop, flushed via f.write() once the accumulated size reaches _WRITE_QUERY_PARAMS_FLUSH_THRESHOLD (64KB) to bound peak memory.
  2. ExecuteMessage._write_query_params() -- Removed unnecessary super() pass-through override (now inherited directly from _QueryMessage).

tests/unit/test_protocol.py

Added 14 new test methods in WriteQueryParamsBufferAccumulationTest, asserting exact serialized bytes (assertEqual/endswith) rather than substring containment.

Benchmark

Re-measured on this machine with the included benchmarks/bench_execute_write_params.py (min() of timeit.repeat(repeat=5, number=500_000)). No compiled Cython .so was available in this environment, so these are pure-Python numbers -- not directly comparable to the previous table, which assumed a Cython build. module (py) is the actual shipped _write_query_params() method call (includes real method-dispatch overhead); buf_accum is the standalone reference function used only for isolating the accumulation logic itself.

Scenario params Baseline (ns/call) buf_accum standalone (ns/call) module (py), actual (ns/call) module vs baseline
128D float32 vector 2 960.4 872.0 (1.10x) 1055.2 0.91x
768D float32 vector 2 1021.6 906.2 (1.13x) 1032.1 0.99x
1536D float32 vector 2 914.3 867.3 (1.05x) 1043.6 0.88x
10 text columns 10 1786.7 1418.5 (1.26x) 1762.9 1.01x

The accumulation logic itself (buf_accum) is consistently faster than the baseline loop. The actual method call (module (py)) is roughly flat-to-slightly-slower than baseline for small param counts on this pure-Python build, and about even for 10 params; the previously reported >1x speedups for the real method call were measured against a Cython-compiled build and are not reproducible here. As before, the clearer payoff is for larger N and in BatchMessage.send_body() (PR #791).

Tests

  • 14 new tests covering normal, NULL, UNSET, mixed, empty bytes, large vector, many params, cross-protocol -- assertions tightened to check exact serialized bytes
  • tests/unit/test_protocol.py passes in full (39 passed) in this environment

@mykaul
mykaul marked this pull request as draft April 4, 2026 14:23
@mykaul
mykaul force-pushed the perf/buffer-accum-write-params branch 2 times, most recently from bc1545f to 9b21d5b Compare April 4, 2026 14:36
@mykaul

mykaul commented Apr 4, 2026

Copy link
Copy Markdown
Author

Just spitting this here:
Is there any value in ~0.1us improvement?

Honest answer: on its own, ~100ns per call is tiny. But context matters:
Where it adds up:

  • High-throughput vector INSERT pipelines doing 100K+ ops/sec — that's ~10ms/sec of CPU saved per core
  • The write path is single-threaded per connection, so it's on the critical path
  • Multiply by batch sizes (BatchMessage calls write_value per param per query)
    Where it doesn't matter:
  • Network round-trip to Scylla is typically 100-500us minimum
  • Serialization of the vector bytes themselves dwarfs the write_value overhead
  • Real workloads are rarely bottlenecked on parameter serialization
    The real argument for merging is code quality, not the 100ns:
  • Removing the redundant super() pass-through is a clear cleanup
  • The buffer accumulation is a well-known pattern (fewer writes = better) and the code isn't harder to read
  • It establishes a pattern for future BatchMessage optimization where the savings would be larger (N queries × M params)
    But if the question is "would a user ever notice this in production?" — no, almost certainly not. It's a micro-optimization. Whether it's worth the diff churn and review cost is a judgment call for the maintainers.

@mykaul
mykaul force-pushed the perf/buffer-accum-write-params branch 2 times, most recently from f2be2a8 to ac64459 Compare April 4, 2026 15:02
@mykaul mykaul changed the title perf: buffer accumulation in _write_query_params() reduces f.write() calls perf: buffer accumulation in _write_query_params() reduces f.write() calls (~100ns improvement, 1.1-1.3x speedup) Apr 7, 2026
@mykaul
mykaul force-pushed the perf/buffer-accum-write-params branch from ac64459 to 1e1e709 Compare April 7, 2026 08:29
@mykaul
mykaul force-pushed the perf/buffer-accum-write-params branch from 1e1e709 to 9a41e1f Compare July 29, 2026 20:31
Copilot AI review requested due to automatic review settings July 29, 2026 20:31
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 21 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Advanced

Run ID: 83f93b1b-d13b-48d3-b4cf-5c722b53aca8

📥 Commits

Reviewing files that changed from the base of the PR and between b666e5c and c6460a3.

📒 Files selected for processing (3)
  • benchmarks/bench_execute_write_params.py
  • cassandra/protocol.py
  • tests/unit/test_protocol.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Optimizes query-parameter serialization in protocol messages by buffering encoded parameter bytes and writing them in a single f.write(), reducing per-parameter write calls in hot paths.

Changes:

  • Buffer-accumulate query parameter bytes in _QueryMessage._write_query_params() and write once.
  • Remove redundant ExecuteMessage._write_query_params() pass-through override.
  • Add unit tests validating serialization parity and add a local benchmarking script.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
cassandra/protocol.py Implements buffered parameter encoding and removes redundant override to reduce overhead in execute/query paths.
tests/unit/test_protocol.py Adds unit tests to validate the new buffered encoding matches prior write_value() behavior.
benchmarks/bench_execute_write_params.py Adds a standalone benchmark script to compare encoding variants and measure performance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cassandra/protocol.py Outdated
Comment thread tests/unit/test_protocol.py Outdated
Comment thread tests/unit/test_protocol.py Outdated
Comment thread cassandra/protocol.py Outdated
@mykaul
mykaul marked this pull request as ready for review August 15, 2026 06:56
@mykaul
mykaul force-pushed the perf/buffer-accum-write-params branch from 9a41e1f to bba81b6 Compare September 10, 2026 19:04
Replace the per-parameter write_value(f, param) loop in
_QueryMessage._write_query_params() with a buffer accumulation approach:
list.append + b"".join + f.write(), flushed in bounded 64KB chunks to
avoid an unbounded transient allocation for large parameter sets (e.g.
large vectors), addressing a peak-memory concern raised in review.

This reduces the number of f.write() calls for typical param counts,
which is significant for vector workloads with large parameters.

Also removes the redundant ExecuteMessage._write_query_params()
pass-through override to avoid extra MRO lookup per call.

Includes 14 unit tests covering normal, NULL, UNSET, empty, large vector,
and mixed parameter scenarios for both ExecuteMessage and QueryMessage.
Test assertions tightened to check exact serialized bytes instead of
substring containment.

Includes a benchmark script (benchmarks/bench_execute_write_params.py),
fixed for the current encode_message() signature and re-run to reflect
actual measured numbers on this machine.

Signed-off-by: Yaniv Michael Kaul <yaniv.kaul@scylladb.com>
@mykaul
mykaul force-pushed the perf/buffer-accum-write-params branch from bba81b6 to c6460a3 Compare September 11, 2026 11:00
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