Skip to content

feat(export): persistent on-disk helmTemplate cache#4

Merged
julienduchesne merged 5 commits into
masterfrom
julienduchesne/helm-disk-cache
Jun 5, 2026
Merged

feat(export): persistent on-disk helmTemplate cache#4
julienduchesne merged 5 commits into
masterfrom
julienduchesne/helm-disk-cache

Conversation

@julienduchesne

Copy link
Copy Markdown
Collaborator

Summary

Exporting many environments that render the same Helm charts pays the helm-rendering cost on every run. Add an experimental, opt-in --helm-cache flag to rtk export that keeps a helm-cache/ directory inside the output dir and reuses helmTemplate results across runs and environments.

  • Cache hits skip both the helm subprocess and YAML parsing: the cached value is the manifested JSON of the rendered resource map, so a hit is a single deserialize into a Val
  • Cache key now covers the full call — release name, chart path, namespace, values, flags, nameFormat, and the chart's Chart.yaml contents (so a vendored version bump invalidates stale entries)
  • Add RTK_HELM_DISABLE_MEMOIZATION to bypass the in-memory cache entirely (helm runs on every call) for benchmarking

Context

Builds on the process-global in-memory Helm cache. That cache now stores the post-parse JSON projection instead of raw YAML, so hits no longer re-parse YAML, and it is shared across worker threads — a chart rendered for one environment is reused by every other. The on-disk cache is global and loaded/written exactly once per run: before the parallel loop every entry is preloaded into memory and the directory is cleared; after the loop only the keys touched this run are written back, which prunes stale entries. A process-global touched set is used because a single main.jsonnet can expand into many environments across worker threads (an earlier per-environment design raced on the shared directory).

Gated behind --helm-cache (off by default) so the metadata directory never appears in normal exports or golden-test output.

Stacked on #3.

Made with Cursor

@julienduchesne julienduchesne requested a review from a team as a code owner June 4, 2026 19:57
NickAnge
NickAnge previously approved these changes Jun 5, 2026
Base automatically changed from julienduchesne/rtk-memoize to master June 5, 2026 12:08
@julienduchesne julienduchesne dismissed NickAnge’s stale review June 5, 2026 12:08

The base branch was changed.

julienduchesne and others added 3 commits June 5, 2026 08:10
Add an experimental, opt-in `--helm-cache` flag to `rtk export` that
maintains a `helm-cache/` directory inside the output dir, persisting
helmTemplate results across runs and environments.

Builds on the process-global in-memory Helm cache: the in-memory value
is now the manifested JSON of the rendered resource map (not raw YAML),
so cache hits skip both the helm subprocess and YAML parsing - a hit is
a single serde_json::from_str into a Val. The cache key now also covers
nameFormat and the chart's Chart.yaml contents.

The disk cache is global and loaded/written exactly once per run: before
the parallel loop all entries are preloaded into the in-memory cache and
the directory is cleared; after the loop only the keys touched this run
are written back, pruning stale entries. A process-global touched set is
used because a single main.jsonnet can expand into many environments
across worker threads.

Also add RTK_HELM_DISABLE_MEMOIZATION as a benchmarking escape hatch that
bypasses the in-memory cache so every helmTemplate call invokes helm.

Co-authored-by: Cursor <cursoragent@cursor.com>
The delete_opts test constructs ExportOpts with all fields explicitly
(no ..Default::default()), so it needs the new helm_cache field.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a self-contained uv script and fixture that measure the three Helm
templating cache regimes via hyperfine: no-memoization
(RTK_HELM_DISABLE_MEMOIZATION=1), the default in-memory cache, and a warm
--helm-cache. The fixture renders one local chart across 60 inline
environments with identical parameters, mirroring a fleet that renders the
same chart across many clusters.

Co-authored-by: Cursor <cursoragent@cursor.com>
@julienduchesne julienduchesne force-pushed the julienduchesne/helm-disk-cache branch from a340dd5 to 8c49b46 Compare June 5, 2026 12:18
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

Benchmark Results

Benchmark Test vs tk vs base
Diff cluster_scoped 48.73x faster equal
Diff configmap_unchanged 30.87x faster equal
Diff deployment_nested_changes 28.42x faster equal
Env List Single Inline Directory (--json) 46.32x faster equal
Env List Single Inline File (--json) 46.09x faster equal
Env List All Environments (--json) 84.41x faster equal
Eval Single Static Environment 15.76x faster equal
Eval Inline Environment File 19.02x faster equal
Eval Eval Expression - Deeply Nested Path 15.86x faster equal
Export (Full) Single Static Environment 6.96x faster equal
Export (Full) Single Inline Environment 21.27x faster equal
Export (Full) All Environments 19.25x faster 1.02x slower
Export (Replace) Single Static Environment 5.69x faster equal
Export (Replace) Single Inline Environment 21.61x faster equal
Export (Replace) All Environments 27.94x faster 1.01x faster
Helm Template Full Export 1.41x faster equal
Tool Importers Count Single Lib Directory (Non-Recursive) 2.24x faster equal
Tool Importers Count Single Lib Directory (Recursive) 2.23x faster equal
Tool Importers Count Entire Lib Directory (Non-Recursive) 1.34x faster equal
Tool Importers Count Entire Lib Directory (Recursive) 1.14x faster equal
Tool Importers Global Lib File 3.29x faster equal
Tool Importers Single Env-Specific Lib File 1.58x faster equal
Tool Importers All Env-Specific Lib Files 5.2x faster equal
Tool Importers All Lib Files 5.23x faster equal
Tool Importers All Jsonnet Files 9.04x faster equal
Tool Imports Single Static Environment 3.38x faster equal
Tool Imports Single Inline Environment File 3.48x faster equal
Validate Manifests All Validations - equal
Validate Manifests Manifest Tests Only - equal
Validate Manifests Namespace Test Only - equal

Full results available in workflow artifacts.

Benchmark run on commit afc832220e78e518d527ced7ac53120a49cb0d78

julienduchesne and others added 2 commits June 5, 2026 08:52
Wire the helmTemplate caching benchmark into the CI benchmark matrix via a
new "static" mode in run-benchmark.py (pre-existing fixtures dir + explicit
export commands, rtk-vs-tk-vs-base, no mock k8s server). Replaces the
standalone helm-template-benchmark.py, which produced no table row.

Also adds unit tests for the on-disk helm cache and helm cache key.

Co-authored-by: Cursor <cursoragent@cursor.com>
Expand the benchmark chart to 15 templates (~90 resources) and concentrate
cost in CPU-bound rendering (chained sha256sum) so the cached render dominates.
Output is kept moderate since large YAML mainly adds per-environment
serialization, where Go's yaml is faster and erodes the cache's advantage.

Co-authored-by: Cursor <cursoragent@cursor.com>
@julienduchesne julienduchesne merged commit 56a774c into master Jun 5, 2026
25 checks passed
@julienduchesne julienduchesne deleted the julienduchesne/helm-disk-cache branch June 5, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants