Repository navigation
perf: complete LUW replies at the DRDA chain end instead of timed drains - #34
Merged
Merged
Conversation
After every reply the driver waited frame_drain_timeout (25 ms) for follow-up frames, and 250 ms before fetching any result with LOB columns, even though nothing more ever arrives. DRDA clears the chain bit on the last DSS of a reply chain, so off z/OS read_reply_frames now reads through that DSS and the speculative drains return immediately. A DSS capture of the full Rust and Node suites (358 connections) showed every LUW reply chain ends this way, with no pipelined request chains and no unsolicited replies. Same-correlation segments merged by DssReader now carry the last segment's chain bit, so a merged frame cannot hide the end of a chain. On Db2 12.1, VALUES 1 drops from ~30 ms to ~0.5 ms and a LOB SELECT from ~285 ms to ~1 ms. z/OS keeps its timed drains, and frame_drain_timeout is documented as z/OS-only.
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.
Problem
Nearly all of the driver's request latency came from waiting, not from Db2:
read_*_reply_frameswaitedframeDrainTimeout(default 25 ms) for follow-up frames before returning.query_frame_drain_timeoutwaited 250 ms, the z/OS LOB drain applied regardless of server.Neither wait ever received anything on LUW, so every operation paid for them:
VALUES 1How replies actually end
DRDA marks every reply DSS except the last one in a chain with the chain bit (
0x40). A logging TCP proxy between the driver and Db2 12.1 confirmed this for every flow: connect, prepare, describe input, OPNQRY/CNTQRY with and without LOBs, EXTDTA, execute plus commit, batches, errors, CALL, transactions and TLS.Running the full Rust and Node suites through the proxy (358 connections) found no case where the driver pipelines independent request chains, and no reply that arrived without an outstanding request. Once the unchained DSS arrives, the reply is complete.
Change
read_reply_framesreads through the DSS that clears the chain bit. A reply split across TCP reads, such as a large QRYDTA or EXTDTA chain or a commit flushed separately, is no longer cut off by a timer.DssReader: a frame merged from same-correlation segments now reports the last segment's chain bit, so a merged frame can't hide the end of a chain.frameDrainTimeoutis now documented as z/OS-only. Before the server is identified, behavior is unchanged.Results
examples/performancesuites plus a LOB-parameter benchmark on Db2 12.1 (Docker, Apple M4 Pro), mergedmainvs. this branch, single runs:VALUES 1The Node test suite drops from 46.7 s to 4.0 s, and the Rust integration suite from several minutes to 7 s. The only benchmark that doesn't move is "10,000 prepared executions", which is an unindexed
COUNT(*)over 50,000 rows and is bound by Db2.Tests
lob_wire_regressions.rs): an LUW server whose OPNQRY reply chain spans two DSSes and two writes 50 ms apart, then an execute plus commit chain, withframe_drain_timeout = 5s. Both operations must finish in under 1 s, which they cannot if any drain wait remains. Fails onmain.DssReaderunit test: a merged same-correlation continuation reports the last segment's cleared chain bit. Fails without the change.cargo test --workspace --test '*'passes.npm testhas 137 passed, 0 failed, 7 skipped, with the TLS suite (8/8) on port 50001.cargo test --workspace --test '*'passes.main(missing seed fixtures).rowCount, and later queries on the connection stay in sync.cargo fmt --check,cargo clippy --workspace --all-targets -D warnings, workspace unit tests and thedb2-napiJS tests all pass.Notes
queryTimeout(or the existing 30 s fetch deadline), just like a server that never replies. Before, the driver gave up after 25 ms and returned a partial reply, leaving the rest to be misread by the next request.executeBatchunder autocommit sends no RDBCMM, so its rows stay uncommitted until the connection's next commit.🤖 Generated with Claude Code