Repository navigation
ci: build each release binary once and run the long pytest jobs as shards - #679
Merged
Merged
Conversation
LeeroyHannigan
requested review from
amrith,
c33howard,
jcshepherd,
pdf-amzn,
robinnsc and
yesyayen
as code owners
October 30, 2026 19:06
navareamol
enabled auto-merge
October 30, 2026 21:10
robinnsc
previously approved these changes
Oct 30, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Oct 30, 2026
devtools/test-shard-weights.tsv holds the seconds each pytest file and each tests/rust module took on each backend in the merge group run for c5fe781 on 30 Oct, read from its job logs. devtools/test-shards uses them to balance the shards.
Ten integration jobs each ran their own cargo build --release. Each distinct feature set now has one build job that uploads target/release/extenddb as an artifact, and the jobs that test it download that binary instead of compiling it: - build-postgres (cargo build --release): run-integration, run-rust-integration, run-rust-integration-postgres-novector - build-sqlite (--no-default-features --features sqlite): run-integration-sqlite, run-rust-integration-sqlite - build-mongodb (--features mongodb-test-hooks): mongodb-pytest, mongodb-rust - build-cassandra (--features cassandra): cassandra-pytest, cassandra-rust The cargo flags and features are unchanged. run-integration-dev-mode keeps its own build because it is the only job using that feature set. Jobs that only run pytest no longer install the Rust toolchain or restore the cargo cache. Jobs that still run cargo test download the binary after the cache restore, so a cached target/ cannot replace it. Artifacts do not keep file modes, so each download is followed by chmod +x.
devtools/test-shards has three commands: - collect runs pytest --collect-only -q on the given arguments and writes each collected test's file and id, with paths relative to the working directory so lists from different rootdirs compare. - plan assigns every collected file to one of N shards, longest first to the least loaded shard, using the seconds in devtools/test-shard-weights.tsv. Every shard computes the same assignment from the same collection, so each file is in exactly one shard. - check reads the lists all legs of a sharded job wrote and fails unless they are shards 1 to N of one N, agree on the whole suite, and together ran every test of it exactly once. The weights only balance the shards; a missing or stale weight cannot drop a test because check compares the shards with the whole suite.
run-tests --shard K/N runs only shard K of the main, comprehensive and CLI suites. Before each suite, devtools/test-shards prepare collects the whole suite with the suite's own arguments, picks the shard's files, and writes the whole and the shard's collected lists under $EXTENDDB_SHARD_DIR (default discussions/shards) for the summary job. pytest then loads devtools/pytest_shard.py, which deselects every test outside the shard's files, so filters, ignores and xdist options apply as in an unsharded run. A shard with no files of a suite runs nothing for it rather than calling pytest with no paths. The existing suite command lines are unchanged; each gains one line that calls the hook when --shard is given.
bash 4.2, which some development hosts still run, has no `local -n`, so shard_suite now takes the suite's command as arguments and leaves the command to run in SHARD_CMD.
run-integration, run-integration-sqlite and mongodb-pytest each run as a three-leg matrix, every leg calling run-tests with --shard K/3 against its own server and database, as the single job did. Each leg uploads its shard lists. The integration and mongodb-integration summary jobs keep their names and their result checks, then download the lists and run devtools/test-shards check. It fails unless the legs of each job are shards 1 to 3 of one split, agree on the whole suite, and together collected every test of it exactly once, so a bad split turns the required check red.
run-rust-integration ran the storage-level tests after the wire-level Rust suite, 7.1 of its 27.4 minutes in the merge group for #511 on 30 Oct. They drive the storage layer against the job's PostgreSQL and need no server, so run-postgres-storage-tests runs them with the same image, environment and cargo command, without waiting for the release build. The integration summary job requires it.
run-tests reads its --shard default from EXTENDDB_TEST_SHARD, and the three sharded jobs set it at job level, so their run lines are unchanged from main. Each upload of the shard lists moves to the end of its job. Open pull requests that edit those run lines or add steps before them merge without conflicts.
- devtools/test-shards collect escapes backslashes, tabs and newlines in each path and test id, so an id carrying a tab no longer splits a row and makes check report a test in two shards. - prepare captures each collection's output and prints only its summary line, or the whole output when the collection fails. Each leg's log had carried the collection tree twice, about 4,900 lines for the main suite. - Every upload-artifact step sets overwrite: true, so re-running one failed job can upload its artifact under the same name again.
build-postgres, build-sqlite, run-postgres-storage-tests, build-mongodb and build-cassandra are new in this branch, so #680 did not reach their Swatinem/rust-cache steps. They now set save-if to github.ref == 'refs/heads/main', as every other rust-cache step outside launcher.yml does, so pull request and merge group runs of these jobs restore main's caches and save nothing.
LeeroyHannigan
force-pushed
the
ci/fast-checks
branch
from
October 30, 2026 22:08
051c0d1 to
4a45a51
Compare
The three workflow files conflicted. Each one was resolved by taking main's version of the file and applying this branch's edits to main's current jobs. The edits use the action SHAs main pins. Every job, needs entry and if condition from main stays. Every step stays as well, except the toolchain, cache and build steps that a download of the shared binary replaces. integration.yml: build-postgres and build-sqlite are added. run-integration and run-integration-sqlite run as three shards, download their binary and upload their shard lists after main's last step. run-integration-sqlite-s3, which main added, downloads the SQLite binary instead of building the same one. run-rust-integration, run-rust-integration-postgres-novector and run-rust-integration-sqlite download their binary after the cache restore. The storage-level step moves to run-postgres-storage-tests with main's --tests command, and the full-history checkout that migrations_tag_blobs needs moves with it. The integration job also needs run-postgres-storage-tests and checks the shard lists. integration-mongodb.yml: build-mongodb is added, and mongodb-pytest runs as three shards on its binary. mongodb-rust downloads that binary. mongodb-backup-store and mongodb-catalog, which main added, download it too instead of building the same one. mongodb-integration checks the shard lists after main's result check. integration-cassandra.yml: build-cassandra is added, and cassandra-pytest and cassandra-rust download its binary. cassandra-catalog-checks and cassandra-authorizer, which main added, download it too instead of building the same one. cassandra-catalog-checks keeps its Rust toolchain for the v0.1.14 build. devtools/run-tests merged without conflicts.
cassandra-pytest runs as five legs, each with EXTENDDB_TEST_SHARD set to K/5 as in mongodb-pytest. Each leg has its own Cassandra service container and uploads its shard lists. After its result check, cassandra-integration downloads the lists and runs devtools/test-shards check, so it fails unless every leg succeeded and the legs together collected every test of the whole suite exactly once. devtools/test-shard-weights.tsv gains a cassandra column, which devtools/test-shards reads for --backend cassandra. It holds the seconds each file of the main and comprehensive suites took in the cassandra-pytest job of the push run for d35d860 on 6 Nov, rounded to whole seconds. A file with no Cassandra measurement has "-", and the tool weights it by its test count. With these weights the tool models each leg at 320 to 324 seconds of tests. In that job the two suites took 1,583 seconds.
cassandra-catalog-checks built the v0.1.14 tag on every run. The build took 310 to 413 seconds in the last four pushes to main. An actions/cache step, pinned to the commit SHA licenses.yml uses, now keeps the binary that build writes, keyed on the tag, the cargo features and the runner OS. On a cache hit the build step is skipped and the check runs the restored binary from the same path. The check is unchanged.
The Cassandra column counted the 5.5 s of tests/python/test_query_start_key_index.py under test_query_start_key_partition.py, which took 21.7 s by itself in the same job. Each file now has its own weight. The weights only balance the shards, so no test moves in or out of a run.
Every leg of run-integration and run-integration-sqlite runs the schema check. A schema difference fails all three legs, and each one then uploads schema-postgres or schema-sqlite. upload-artifact refuses a second artifact of one name in a run, so legs 2 and 3 reported an extra error. With overwrite: true, each leg uploads the same file without that error.
The cache key and the build command of the v0.1.14 binary now read the cargo features from V0114_FEATURES. A change to the features changes the key, so the job cannot restore a binary built with other features. The key also names the runner's architecture. The cache step runs first. On a hit, cassandra-catalog-checks skips the Rust toolchain and cache steps, because only the v0.1.14 build uses them.
LeeroyHannigan
requested review from
hitesh96db and
navareamol
as code owners
November 6, 2026 19:04
cassandra-catalog-checks used actions/cache, which saves the entry from any run that misses, pull request runs included. The job now restores the entry with actions/cache/restore in every run, and saves it with actions/cache/save only on a push to main that missed. No pull request run writes the entry, the same rule the Rust caches follow.
integration.yml conflicted in the job-level settings of run-integration and run-integration-sqlite. Main raised the timeout of both jobs from 40 to 55 minutes and added a comment with each job's measured time. This branch added needs, the shard matrix and EXTENDDB_TEST_SHARD to the same settings. Each job now has main's comment and timeout, then this branch's needs, strategy and env. The rest of main's workflow changes merged without conflicts. Main added no job to the three integration workflows. Each job that main changed already downloads its binary from a shared build job on this branch, so no build step changes. integration.yml: run-integration gets the S3 mock step, the S3 endpoint for the backup store and job suites, and the step that prints the mock's log. These steps run no cargo command, so the job stays without a Rust toolchain or cache. run-integration-sqlite-s3 and run-rust-integration-sqlite get comment changes only. integration-mongodb.yml: mongodb-pytest and mongodb-rust set EXTENDDB_TEST_BACKUP_STORE to filesystem. mongodb-rust keeps its Rust toolchain and cache, because run-mongodb-tests --rust-live runs cargo test. mongodb-pytest stays without them: run-mongodb-tests and run-tests run cargo only for their --rust options, and the job passes none of them. Main's new matrix-nightly.yml and its devtools/run-mongodb-tests changes merged without conflicts.
The Swatinem/rust-cache step of cassandra-catalog-checks had no save-if. A pull request or merge group run that missed the v0.1.14 cache therefore saved Rust cache entries of its own. The step now sets save-if to github.ref == 'refs/heads/main', as the rust-cache steps of cassandra-storage, build-cassandra, cassandra-rust and cassandra-production-build do. Pull request and merge group runs restore main's cache and save nothing.
The rust-cache step of cassandra-two-nodes had no save-if, so each pull request run saved an entry of its own. Those entries count against the repository's 10 GB cache limit. When the total passes the limit, GitHub evicts older entries, main's included. The step now saves only on pushes to main, as the other rust-cache steps do.
Main raised the timeouts of five jobs and added or extended a comment with each job's measured time. run-integration went from 55 to 70 minutes and run-integration-sqlite from 55 to 60. run-rust-integration went from 40 to 50 and mongodb-rust from 40 to 55. cassandra-pytest went from 45 to 60. This branch added needs to the same job settings of all five, and the shard matrix and EXTENDDB_TEST_SHARD to run-integration, run-integration-sqlite and cassandra-pytest. Each job now has main's comment and timeout, then this branch's keys. These were the only conflicts. The timeouts apply to each shard, which runs part of the suite. Main added no job and no build step to the three integration workflows, so no download step changes. The PartiQL test files that main added have no shard weight. devtools/test-shards weights them by their test count.
devtools/test-shard-weights.tsv had no row for the 35 PartiQL test files, which hold 3,133 of the 5,255 tests that the main suite collects. The planner priced each of their tests at the suite's mean, about 1.2 s on PostgreSQL, so it modelled them at 62.9 minutes. On main's push run for 0cfb0d9 they took 8.9 minutes of worker time on PostgreSQL. The main and comprehensive rows now hold each file's seconds from that run's run-integration, run-integration-sqlite, mongodb-pytest and cassandra-pytest jobs. A file's seconds are the gaps between its result lines, so they include its fixtures. The cli and rust-integration rows do not change. With the old weights, the slowest PostgreSQL shard had 9.7 minutes of that run's measured main-suite time, and the slowest SQLite shard 12.6. MongoDB's slowest had 9.7 and Cassandra's 7.7. With the new weights the slowest shards have 7.5, 8.7, 6.4 and 4.4 minutes. Those figures fit the weights to the run they come from, so a later run can spread less evenly. The weights only balance the shards. The completeness check does not read them.
Main (#474) added an "Install Terraform" step after the "Build release" step of mongodb-pytest and cassandra-pytest, and --terraform to their test commands. This branch replaced those build steps with a download of the shared binary and a chmod. These were the only conflicts. Each job now has the download and the chmod, then main's Terraform step. The same Terraform changes to run-integration and run-integration-sqlite merged without conflicts. So did main's changes to devtools/run-tests and devtools/check-postgres-catalog-lock.
run-integration, run-integration-sqlite, mongodb-pytest and cassandra-pytest pass --terraform to devtools/run-tests, and each job runs as shards. The apply is one run against the job's server, not a suite of test files, so run-tests ran it on every shard. With --shard K/N, run-tests now runs the apply when K is 1 and prints a skip line on the other shards. Each backend's job runs the apply once, as on main.
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.
What
This PR reduces the run time of the integration workflows. It builds each release binary once and runs the long pytest jobs as shards.
target/release/extenddb. Each job that ran the same build command downloads it instead. run-integration-dev-mode keeps its own build, because no other job uses its features.devtools/test-shardsassigns the files by the seconds indevtools/test-shard-weights.tsv, anddevtools/run-tests --shard K/Nruns one shard. The weights of the main and comprehensive suites are each file's seconds on main's push run for 0cfb0d9.--shard,devtools/run-testsruns the--terraformapply on shard 1 only. Each backend's job runs it once, as on main.This PR removes or skips no test, and the required check names stay the same.
Why
Main's push run for 2671ee1 on 9 November took 43.1 minutes for integration, 35.4 for cassandra-integration and 41.3 for mongodb-integration. Each figure is the time from the start of the workflow run to the end of its check job. On main, each pytest job builds its own release binary and runs its whole suite.
Testing done
bash -nandpy_compilepass on the devtools files.tests::panicking_refresh_does_not_leak_inflight_flagin extenddb-cache, a timing test in a crate that this PR does not change. The rerun passed. I found no other failure of that test in the failed jobs of the Test workflow's 724 runs since 26 October.whole 5298, shards 2715 + 1765 + 818 = 5298 OK. For cassandra-pytest, the main suite printedwhole 5308, shards 1915 + 703 + 835 + 1031 + 824 = 5308 OK.✓ Terraform apply passed. The other shards printed a skip line such asskipped: shard 2/3 (shard 1 runs the apply).Known issues
Checklist
cargo test --workspace)cargo fmt --check)cargo clippy -- -W clippy::pedantic)Storagetrait, auth model, on-diskformat, or public CLI surface, an RFC has been accepted or is linked
below. Otherwise, an ADR captures the decision (link below).
ADR / RFC: n/a
By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache License 2.0 and I agree to the Developer Certificate of
Origin (DCO). See CONTRIBUTING.md for details.