Add sharding support to HACS data generation pipeline#5419
Conversation
There was a problem hiding this comment.
Pull request overview
Adds deterministic sharding to the HACS data generation pipeline so large categories can be generated in parallel and then merged into a single validated output, with CI updated to orchestrate shard runs + merging.
Changes:
- Added stable shard assignment (
shard_for) and shard slicing to generate per-shard partial outputs underoutputdata/_shards/. - Extracted finalization (summary/validation/diff writing) into
finalize_category_output()and introduced a newmerge_category_datascript to finalize merged shard outputs. - Updated GitHub Actions workflow to generate shards via a matrix, download shard artifacts, merge them, then summarize/publish.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/data/generate_category_data.py |
Adds shard hashing/slicing, shard arg parsing, shard output writing, and a shared finalization function. |
scripts/data/merge_category_data.py |
New script that loads shard partials and runs the shared finalization step on the merged dataset. |
.github/workflows/generate-hacs-data.yml |
Refactors the workflow to run per-shard generation, merge shard artifacts, then summarize/publish. |
tests/scripts/data/test_generate_category_data.py |
Adds unit + integration-style tests for shard assignment, parsing, slicing, and sharded-vs-unsharded equivalence. |
tests/conftest.py |
Extends HTTP session proxying to cover the new merge script. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The generate-hacs-data workflow processed each category in a single job. The integration category is by far the largest, so that job dominated runtime and was the most likely to hit GitHub rate limits or time out. Split generation into shards (integration ×3, plugin ×2, everything else ×1) and merge each category's shards back into a single result before the existing summarize/publish jobs run. - generate_category_data: add a stable, salt-free `shard_for` helper and thread a shard `index/total` through generation, slicing both the published data and the default-file repo list by the same lowercased full_name so shards are disjoint and their union equals the unsharded set. Refactor the summary/diff/validation/writing tail into a reusable `finalize_category_output`; a `--shard x/y` CLI selects a shard and writes a partial artifact. With `--shard 1/1` (the default) behaviour is unchanged. - merge_category_data: new script that concatenates the shard partials and runs finalize once on the full merged dataset. - workflow: emit a shard matrix, run generation per shard, add a merge job per category that rebuilds the per-category artifact in the same shape as before, and repoint summarize/publish accordingly. - tests: cover shard_for/_parse_shard/_slice_by_shard and assert a sharded generate + merge matches the unsharded output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NL8ieP9vEVJLTZ51esR6VD
- generate_category_data: distinguish a sharded invocation from an unsharded one by whether a shard was passed, not by shards > 1. With --shard omitted the full category output is produced (unchanged); with any shard given -- including 1/1 -- a partial artifact is written, so the single-shard categories go through the merge step uniformly like the workflow expects (previously they took the finalize path and the shard artifact upload had nothing to upload). - finalize_category_output: sort repositories.json explicitly (sort_keys is a no-op on a list) and sort the keys of data.json/stored.json, so the published output is deterministic regardless of shard-assembly order. - tests: replace the network-driven sharded/unsharded equivalence test (which required API-usage snapshots) with a network-free roundtrip test of write_shard_output + _load_shard_files across 1/2/3 shards; drop the now-unused merge ClientSession patch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NL8ieP9vEVJLTZ51esR6VD
b61c4b3 to
ba5fbb6
Compare
| strategy: | ||
| fail-fast: false | ||
| max-parallel: 1 | ||
| matrix: | ||
| category: ${{ fromJSON( needs.generate-matrix.outputs.categories )}} | ||
| include: ${{ fromJSON( needs.generate-matrix.outputs.shards )}} |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Closing in favor of splitting this into smaller, independently-reviewable PRs (the diff here got large). The work will land as a sequence:
Each merges safely on its own; only the last changes the running workflow. Opening the next PR after each merges. Branch left intact for reference. Generated by Claude Code |
Summary
Adds sharding to the HACS data generation pipeline. Large categories are split into independent shards that are generated separately and merged back into a single per-category result before summary/validation/publish. Motivated by the
integrationcategory, whose single job had grown too large; splitting it keeps each job within per-job time/rate limits and makes runs independently retriable.The generation matrix runs serially (
max-parallel: 1, retained from #5416) to stay within GitHub API rate limits on the shared token — so sharding here provides smaller, resilient, independently-mergeable jobs rather than wall-clock speedup.Key changes
shard_for()(stable, lowercased MD5 hashing) assigns repositories to shards;_slice_by_shard()partitions by that assignment. Disjoint, and the union equals the unsharded set, so merged output matches an unsharded run.argparsewith--shard x/y(1-based). Omitting--shardproduces full, unsharded output (unchanged; used by tests and the single-repositoryvalidate.ymlpath); passing any shard — including1/1— writes a partial, so every sharded invocation flows through the merge step uniformly.finalize_category_output(), run once on the merged dataset. Sharded runs write partialdata.json+stored.jsonunderoutputdata/_shards/, recombined by the merge step.scripts/data/merge_category_data.py.repositories.jsonsorted explicitly (sort_keysis a no-op on a list);data.json/stored.jsonkeys sorted — published files are stable regardless of shard-assembly order.generate-matrixcomputes shard counts (integration=3, plugin=2, others=1);category-dataruns per shard, serially; newmergejob recombines a category's shards and uploads the per-category artifact in the original shape;summarizedepends onmergeand skips directories without asummary.json.Tests
--shard x/yparsing, slice disjointness/completeness.write_shard_output+ the merge loader reassemble the full set across 1/2/3 shards.https://claude.ai/code/session_01NL8ieP9vEVJLTZ51esR6VD