perf: optimize was_applied fast path for known LWT statements (~1.5us, 1.1x speedup) - #797
perf: optimize was_applied fast path for known LWT statements (~1.5us, 1.1x speedup)#797mykaul wants to merge 2 commits into
Conversation
8a3b2ed to
f4ec874
Compare
f4ec874 to
c7fa63a
Compare
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
|
Rebased onto latest Consistency verdict: safe. The fast path doesn't introduce a new LWT-detection heuristic — it reuses the single canonical Tracing where
Also checked No correctness fix was needed. I rebased the two commits onto current
CI on the PR is green (13/13 checks) and there were no unresolved review threads to address. Force-pushed the rebased branch to |
There was a problem hiding this comment.
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 ResultSet.was_applied by adding a fast path for queries that already know they are LWT statements, avoiding expensive batch detection in the common single-LWT case.
Changes:
- Add
query.is_lwt()-based fast path inResultSet.was_appliedto skip batch regex detection for non-batch known-LWT statements - Update and add unit tests to cover fast-path, slow-path fallback, and batch behavior
- Add a micro-benchmark script to measure the fast-path vs slow-path overhead
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
cassandra/cluster.py |
Adds LWT fast path in ResultSet.was_applied to skip batch detection when LWT is known |
tests/unit/test_resultset.py |
Adds tests for fast/slow paths and adjusts existing test to avoid accidental fast-path routing |
benchmarks/bench_was_applied.py |
Introduces micro-benchmark comparing fast-path checks vs regex-based slow path |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add a fast path in ResultSet.was_applied that skips batch detection (isinstance checks + regex match) when the query has a known LWT status from the server PREPARE response. For BoundStatement queries where is_lwt() returns True, the batch_regex match on the query string is entirely avoided. This benefits the most common LWT use case: prepared INSERT/UPDATE IF statements executed via BoundStatement, where the driver already knows from the PREPARE response whether the statement is an LWT. The slow path (isinstance + regex) is preserved for: - BatchStatement queries (detected via isinstance) - SimpleStatement batch queries (detected via regex) - Any query where is_lwt() returns False The fast-path condition checks `isinstance(query, BatchStatement)` before looking up `is_lwt`, and uses a getattr/callable guard around the call instead of calling `query.is_lwt()` unconditionally. This protects `was_applied` from raising AttributeError for any query object that doesn't implement is_lwt() -- e.g. response_future.query left as None, which is a real, reachable value (see ResponseFuture.query's class-level default and Session.prepare()/prepare_on_all_hosts, which construct ResponseFuture(..., query=None, ...) explicitly) -- falling back to the slow path instead. Also adds explicit tests for the fast path, non-LWT fallback, BatchStatement handling, and a regression test for a query without is_lwt() in was_applied. Part of: scylladb#751
Construct a minimal ResultSet with a mocked response_future and real cassandra.query statement objects, and time actual accesses to rs.was_applied, instead of re-implementing a simplified stand-in for its fast-path/slow-path branching. This also means the slow path exercises the real ResultSet.batch_regex instead of a different, looser regex, so the reported cost reflects the real regex match. On this machine: ~0.21us/call for the fast path (known-LWT BoundStatement) vs ~0.35us/call for the slow path (SimpleStatement regex match), a ~1.7x speedup -- both call costs are far below a microsecond once measured against the real was_applied property instead of Mock-heavy stand-ins.
c7fa63a to
72aeb59
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
cassandra/cluster.py:5923
- This does not remove the work claimed by the fast path. In the previous code,
batch_regex.match()was already guarded byisinstance(query, SimpleStatement), so aBoundStatementnever ran the regex; this branch still performs theBatchStatementcheck and now addsgetattr,callable, and anis_lwt()call. The target BoundStatement path is therefore likely slower rather than faster. Please restore the existing path unless an apples-to-apples benchmark demonstrates an actual reduction, or redesign the branch to eliminate work that BoundStatements previously performed.
is_lwt = getattr(query, 'is_lwt', None)
if not isinstance(query, BatchStatement) and callable(is_lwt) and is_lwt():
benchmarks/bench_was_applied.py:74
- This benchmark cannot attribute its reported “speedup” to this PR: it compares a BoundStatement with a SimpleStatement, but the pre-PR implementation already skipped the regex for BoundStatements. It would report a difference even on the base branch. It also uses one
timeit()run rather than themin(timeit.repeat(..., repeat=7))methodology stated in the PR. Please compare the same BoundStatement workload before and after the implementation (and use repeated runs) so the claimed regression/improvement is reproducible.
t_fast = timeit.timeit(fast_path, number=n)
t_slow = timeit.timeit(slow_path, number=n)
Summary
ResultSet.was_appliedfor statements wherequery.is_lwt()is True (BoundStatement/PreparedStatement)batch_regex.match()call andisinstance(query, BatchStatement)check for the common single-LWT caseBenchmark
Measured with
min()oftimeit.repeat(repeat=7, number=200_000)on a quiet machine (load <1).Savings: 612 ns/call
Tests
test_was_applied_lwt_fast_path,test_was_applied_non_lwt_fallback,test_was_applied_batch_statementtest_was_appliedto use explicit non-LWT query to exercise the slow (regex) path