Add Python benchmark engine (async valkey-glide, redis-py, valkey-py) - #24
Conversation
1c02c21 to
b83ef00
Compare
|
Note that an earlier attempt (https://github.com/jduo/resp-bench/tree/python-engine) also added Python sync if we want to see how that was done and consider here. |
Thanks — I went through that branch before opening this PR. Borrowed from it: the Skipped its sync mode deliberately: #11 calls for the async clients, and that path is |
That's fine, just wanted to make sure we referenced the other implementation. We can add sync in the future as needed. |
|
Overall looks good. My main concerns that need to be resolves are that:
|
4096bac
Thanks — this was a genuinely useful review; the measured reproductions made every item easy to confirm. I reproduced all of them locally before changing anything, and all 12 are addressed in On sharing the GLIDE client: I have kept one client per connection for now, because sharing a multiplexing client across workers is what ikolomi#11 explicitly declined — the reasoning there was that one-client-per-transport-connection is the cross-engine comparison baseline, and that transport sharing should be reached via pipelining instead. I do not think I should reverse that inside a language-engine PR. That said your point stands on its own merits: N separate On peer drivers: agreed, and your own findings are the argument for it — the two peers were not configured equivalently. Dropped The four correctness bugs (worker cancellation, loop starvation, warmup, config validation) have tests; the suite is now 59 tests. Two of your findings led somewhere broader than the original report: the starvation hole also existed on the success path (any non-suspending driver let one connection monopolise a duration phase — Given the severity of the starvation and validation issues I would rather you re-reviewed than merged on the earlier approval. |
|
Looks like we are following the conclusion of ikolomi#11 (i.e. client==connection, and use pipelining for shared transport). My only remaining concern I have is that Java/Ruby/C# all support pipelining and can set pipeline_depth>1, so I think we need the same pipeline support in the Python client. |
|
@jamesx-improving +1 on the pipeline_depth support |
|
I suggest to support both sync and async benchmark. |
Signed-off-by: James Xin <james.xin@improving.com>
Signed-off-by: James Xin <james.xin@improving.com>
…arity Signed-off-by: James Xin <james.xin@improving.com>
Implement pipeline_depth > 1 (jeremyprime): N clients, N*D worker coroutines, so a connection holds up to D requests in flight. Each connection owns one key generator and command selector shared by its slots, so depth changes concurrency without changing which keys a connection touches -- matching the C# engine. Drivers differ in what depth costs physically, so it is recorded rather than inferred: GLIDE multiplexes (1 socket), redis-py serves concurrent commands from its pool (bounded at depth via set_max_in_flight). New prime() hook fills a pooling driver's sockets before the measured window, independent of warmup_requests. Phase rows carry pipeline_depth, sockets_per_client and total_sockets. Also from review: - warmup stays warmup_requests per client, not depth * that - except BaseException so Ctrl-C still emits a row; INTERRUPTED status, and any non-COMPLETED status sets the non-zero exit code - generate_graphs skips non-COMPLETED rows, which averaged in as 0 RPS - commands[].command missing is reported with field and phase name - python-clean no longer deletes python/.venv - document the new fields and the C-compiler prerequisite Signed-off-by: James Xin <james.xin@improving.com>
6b3c8ab
4096bac to
6b3c8ab
Compare
Implemented in For One caveat worth knowing, because it isn't symmetric across drivers:
redis-py has no single-socket path to real depth — Two bugs this shook out, both relevant beyond Python:
Also flagging that the premise isn't quite right, since it affects what "the same support" means: only C# actually pipelines today.
I matched C#-GLIDE, not Java. Happy to file those separately — an engine that can't pipeline should reject |
Thanks @Aryex — landed, details in the comment above. |
make python-build failed with Error 127 on an AL2023 EC2 host, aborting the sweep at its first cell before any benchmark code ran. The target invoked pip by bare name. AL2023's python3.11-pip installs only pip3.11, and provision.sh symlinks python/python3 but never pip, so there was no bare pip on PATH. Use `python -m pip` instead: it always resolves through the same interpreter `python-run` uses, so the two targets cannot disagree about which environment they installed into. Provisioned hosts also install against a system interpreter whose site-packages is not writable, so they need --user -- but pip rejects --user inside a virtualenv, which is how this runs locally. Hence PIP_FLAGS, empty by default, with provision.sh exporting --user into .resp-bench-env so the sweep's own make picks it up rather than just the provisioning shell. Finally, warm the Python engine alongside Java/Ruby/C#/Node. Its absence is why this stayed hidden: warm-up is best-effort, so nothing failed loudly until the sweep hit a cell. Signed-off-by: James Xin <james.xin@improving.com>
2fc61ac
Resolve conflict from the merged Python engine (#24): keep both PHP and Python entries in the benchmark workflow's engines choice and generate-graphs needs.
Implements the Python engine (#11), at parity with the Java (reference), Ruby, and C# engines.
What
python/(pip install -e .,python -m resp_bench), driving three async drivers, one client per connection on a single asyncio event loop:valkey-glide-python— GLIDE async (import glide)redis-py—redis.asynciovalkey-py—valkey.asyncio(Valkey fork of redis-py)recording— in-memory driver for server-free testsunit:"us", uppercased command keys, HDR compressed base64).Concurrency model
connections = N→ N clients (one per connection — theclient == connectioninvariant) driven by N worker coroutines viaasyncio.gather, each awaiting one command at a time. This is the faithful async analogue of the Java/Ruby "one in-flight request per connection" model, keeping results comparable across engines.pipeline_depth > 1is not yet implemented; a>1request logs a warning and runs at depth 1.Cross-engine parity
JavaRandomLCG port (incl. int32-overflow rejection) — verified byte-identical to Java'sjava.util.Random(seed-0 anchor) and to the Ruby engine (seed 12345).sequential_intuses a per-phase shared counter (matches Java'sforkForThread);uniform_randseeded per workerseed+idx.%0Ndhonoringkey_size_bytes; leaky-bucket rate limiter; HdrHistogram(1, 600_000_000, 3).Harness
python-build/test/run/clean/infotargets.driver_ids inscripts/run_benchmark_matrix.py(DRIVER_ENGINE_MAP) andscripts/generate_graphs.py(DRIVER_LANGUAGE_MAP).configs/drivers/{default,high-throughput}/+example-*-standalone.json.benchmark-pythonjob (matrix over the three drivers) added togenerate-graphsneeds;drivers.jsonpython list.docs/ADDING_LANGUAGE.mdfrom3_600_000_000(1 hour) to600_000_000(600s) — every engine uses 600s.Tests
34 unit + integration tests (
cd python && python -m pytest): parity anchors (JavaRandom, key generator), rate limiter, config loader, HDR encode/decode round-trip, NDJSON schema, and end-to-end runs via the recording driver (no server needed).Notes
valkey-glidedriver_idisvalkey-glide-python(not the barevalkey-glide, which is Java's) — matches thevalkey-glide-ruby/valkey-glide-csharpconvention.