Repository navigation
Only parse files that can declare a backend - #444
benlangfeld wants to merge 1 commit into
Conversation
terraform-backend loads the whole root module in order to read one backend block. The cost scales with the size of the module rather than with the thing being looked up, and on a large root module that is most of a minute before terraform has been asked to do anything. A block cannot appear in a file that does not mention its name, so the files worth parsing can be picked out by a substring check first. Files that mention a word only in a comment or a string are still parsed, so the result is unchanged; the scan only decides what to skip, never what the answer is. A file that cannot be read is treated as a candidate so that the usual parsing and error handling still applies to it. On a generated 261 file module with one backend block, reading the backend type goes from 34.07s to 0.20s for the same answer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Running this in production now, as part of a set of four. Being straight about the evidence: this run does not isolate this PR. We are also running #445, which lets the workflow state the backend type, and that skips the detection pass altogether — so the file-walk this PR narrows does not happen at all on our plans any more. The headline number from run 38810060157 (plan job 6m37s → 3m38s) belongs to the set, not to this change. What our measurements do establish is the size of the problem this PR addresses. On a root module of ~4,200 resources, before any of these changes:
Nearly a minute, three times a run, to discover a backend declaration that can only appear in a #445 is an opt-in: it helps people who know their backend and say so. This PR helps everyone else, and it is the smaller and safer of the two. If only one of the pair is interesting, I would suggest this one. Happy to rebase or adjust. |
One of the two approaches offered in #443. The other is #445, which adds a
backend_typeinput; they are independent and this one needs nothing from the caller.The change
terraform-backendcallsload_module(), which parses every.tffile in the root module, so thatget_backend_type()can read a singlebackendblock. The cost scales with the size of the module rather than with the thing being looked up.load_module()gains an optionaldeclaringargument. When given, a file is only parsed if its text mentions one of those words:A block cannot appear in a file that does not mention its name, so this cannot change the answer — a file mentioning
backendonly in a comment or a string is still parsed in full, and the HCL parser still decides what it means. The scan only chooses what to skip. A file that cannot be read is treated as a candidate, so the existing parse-and-ignore-failures behaviour still applies to it.Nothing else calls
load_module()with the new argument, so every other caller is unaffected.Effect
Measured on the real root module this came from — 245
.tffiles, of which 12 mentionbackendorcloud:load_module(path)s3load_module(path, declaring=['backend', 'cloud'])s319x, for the same answer. That is consistent with the 46s measured in #443 on the same module inside the action.
How selective the scan is naturally depends on the module. Here only
main.tfmentionsbackendat all; the other 11 matches arecloudflareandpowerapp.cloudappearing in unrelated files. A module that says "backend" in many descriptions or resource names would save less — but never less than today, since the scan only ever skips work.I originally quoted 167x from a synthetic module. That was too flattering: the generated files were smaller and simpler than real ones. The 19x above is the number to go on.
Tests
Six added to
tests/test_module.py, covering: words found anywhere including comments, unreadable files treated as candidates, the backend still found with the filter, skipped files genuinely not parsed,localreported when nothing declares a backend, andcloudblocks found.pytest tests --ignore=tests/github_pr_commentgives 153 passed against 147 onmain, with the same 2 failures and 7 errors before and after — those need S3 and some environment variables I do not have.tests/github_pr_commentfails to collect identically before and after, on a missingTERRAFORM_ACTIONS_*variable.A larger related saving
The same root module is parsed twice per step.
terraform-versioncallsload_module()on it in its own process, immediately beforeterraform-backenddoes.That the first one is a parse rather than the archive download is now measured rather than inferred: the reporter cached the archive with
actions/cacheand the time did not move at all, with gpg verification landing at the identical offset on a cold and a warm run. See #443 (comment).Together the two parses are around 80 seconds of a ~5m30s plan.
#446 removes the first one for workflows that pin an exact version, by taking the existing argument fast path in
main()whenTERRAFORM_VERSIONnames a version rather than a constraint — which also skips the release index fetch. With this PR and #446 together, a large root module with a pinned version parses nothing at all before terraform runs.🤖 Generated with Claude Code