Commit e879659
authored
feat: orchestrate and report incremental hashing (#12)
## Why
The first two PRs add the data needed for incremental hashing and a
conservative way to identify affected targets. This PR makes the feature
available in `hash-persister`.
Existing invocations remain full computations and continue to write the
existing comparison-file format. Incremental mode is explicitly opt-in
and is only an optimization: whenever `hash-persister` cannot prove
reuse is safe, it runs the existing full computation and still produces
a correct, complete output.
## User-facing workflow
The first seed is created by requesting the seedable artifact and
Bazel's `query` backend, which exposes the loading-phase dependency
graph without Bazel build configurations:
```sh
hash-persister \
--query-backend=query \
--seedable-output \
--output "hashes-${BASE_SHA}.json" \
"${BASE_SHA}"
```
The next revision can then reuse it:
```sh
hash-persister \
--query-backend=query \
--seed-file "hashes-${BASE_SHA}.json" \
--seed-sha "${BASE_SHA}" \
--output "hashes-${NEW_SHA}.json" \
"${NEW_SHA}"
```
`--seed-file` supplies the earlier complete artifact; `--seed-sha`
states which revision it must represent. Supplying a seed automatically
makes the new output seedable, including when incremental execution
falls back to full hashing. This ensures a successful run can always
seed the next revision.
`--seedable-output` without a seed performs full hashing but writes the
larger v9 artifact containing dependency edges and compatibility
metadata. Without either flag, full mode keeps writing the legacy
v8-shaped artifact. Seedable output requires `--query-backend=query`;
ordinary `cquery` usage is unaffected.
## Incremental execution
Before reusing anything, `hash-persister` checks that the seed:
- uses the current seed-capable format and contains dependency edges;
- represents the revision passed through `--seed-sha`;
- has a compatibility fingerprint matching the current Bazel release,
target expression, query backend, Bazel options, filtering inputs,
rule-class fingerprints, and hashing version;
- contains valid SHA-256 target hashes.
It then computes the changed paths between the seed and destination
revisions. Rename detection is disabled so a move is conservatively
represented as a deletion plus an addition. The planner from PR #11
either requests a full fallback or returns dirty packages and their
affected reverse dependencies.
Only that reduced set is queried and rehashed. Hashes for unaffected
targets satisfy dependency lookups from the seeded in-memory cache. The
old graph is used only to choose the conservative initial dirty set; the
scoped query observes the dirty packages and their dependency closure at
the destination revision. Those fresh results then replace the dirty
portion of the seed: deleted targets disappear, newly added targets are
discovered, added or removed dependency edges replace their old
versions, and unaffected entries remain. The output is therefore
complete rather than merely a patch over the seed. If no seeded target
is affected, the artifact can advance to the new revision without a
Bazel query.
Generated query expressions may contain many explicit labels.
Expressions up to 64 KiB remain ordinary command-line arguments; larger
expressions are passed through Bazel's `--query_file` rather than
risking the operating system's command-line length limit. Logs show a
bounded preview instead of printing an arbitrarily large expression.
## Fallback versus failure
An incompatible seed, a repository-wide or package-boundary change, an
unsupported target expression, a Git-diff problem, or a scoped-query
problem prevents safe reuse but does not prevent a correct full result.
These conditions log a stable fallback code and run full hashing.
Errors that also prevent full hashing remain failures. This distinction
lets CI treat fallback as a performance outcome rather than a
correctness or availability failure. Codes such as
`seed_compatibility_mismatch`, `package_boundary_change`, and
`scoped_query_error` are bounded for metrics; path-specific details
remain diagnostic text.
## Rollout verification and metrics
`--verify-seed` runs the incremental and full computations for the same
destination and compares their target hashes. It succeeds only if
incremental mode was actually used and both results agree. On
disagreement, it reports a bounded sample of differences and preserves
the full result as the output. This is intended for sampled rollout
validation because it deliberately performs both computations.
The primary purpose of `--execution-report <path>` is to let the CI
wrapper that invokes `hash-persister` emit metrics. `hash-persister`
writes a small, versioned JSON handoff rather than depending on a
particular metrics client or backend; the wrapper reads it after the
process exits and maps its fields into the surrounding CI system's
metrics.
The report is written for successful incremental runs, full-mode
fallbacks, and execution failures. It records whether incremental mode
was requested and actually used, a stable fallback code, and bounded
workload counts such as changed files, dirty targets, recomputed
targets, reused targets, and total targets. These support metrics such
as incremental-use and fallback rates, fallback reasons, and the
proportion of hashes reused. Human-readable fallback and error details
remain available for diagnosis but are not intended to become metric
dimensions.
The README added here documents the mode, both CLI workflows, fallback
behavior, verification, and execution reporting.
## Review guidance
The most important boundaries are seed validation, the transition from
incremental work to a full fallback, and merging fresh results back into
a complete seed. In particular, a fallback must remain seedable, clean
entries must be retained, and every dirty entry must be replaced or
removed.
## Stack
1. [Seedable persistence and compatible cache
seeding](#10)
2. [Conservative dirty-set planning and scoped
queries](#11)
3. **This PR:** CLI orchestration, verification, fallback handling,
documentation, and execution reporting6 files changed
Lines changed: 909 additions & 44 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
65 | 119 | | |
66 | 120 | | |
67 | 121 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
15 | 22 | | |
16 | 23 | | |
17 | 24 | | |
| |||
0 commit comments