Skip to content

Commit 72d711d

Browse files
Callum Styanclaude
andcommitted
feat: wire render cache into prebuilds reconciler
This commit integrates the render cache into the prebuilds reconciliation flow, enabling cache sharing across all prebuild operations. Changes: 1. StoreReconciler: - Add renderCache field (*dynamicparameters.RenderCache) - Initialize cache in NewStoreReconciler() - Pass cache to builder in provision() method 2. wsbuilder.Builder: - Add renderCache field - Add RenderCache(cache) builder method - Pass cache to dynamicparameters.Prepare() when available How it works: - Single RenderCache instance shared across all prebuilds - Each workspace build passes the cache through the builder chain - Cache keyed by (templateVersionID, ownerID, parameterHash) - All prebuilds use PrebuildsSystemUserID, maximizing cache hits Expected behavior for a preset with 5 instances: - First instance: cache miss → full render → cache stored - Instances 2-5: cache hit → instant return - Result: ~80% reduction in render operations per cycle This addresses the resource cost identified in profiling where the prebuilds path consumed 25% CPU and 50% memory allocations, primarily from repeated Terraform file parsing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 22f5008 commit 72d711d

File tree

2 files changed

+34
-1
lines changed

2 files changed

+34
-1
lines changed

coderd/wsbuilder/wsbuilder.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ type Builder struct {
8888
parameterRender dynamicparameters.Renderer
8989
workspaceTags *map[string]string
9090

91+
// renderCache caches template rendering results
92+
renderCache *dynamicparameters.RenderCache
93+
9194
prebuiltWorkspaceBuildStage sdkproto.PrebuiltWorkspaceBuildStage
9295
verifyNoLegacyParametersOnce bool
9396
}
@@ -253,6 +256,14 @@ func (b Builder) TemplateVersionPresetID(id uuid.UUID) Builder {
253256
return b
254257
}
255258

259+
// RenderCache sets the render cache to use for template rendering.
260+
// This allows multiple workspace builds to share cached render results.
261+
func (b Builder) RenderCache(cache *dynamicparameters.RenderCache) Builder {
262+
// nolint: revive
263+
b.renderCache = cache
264+
return b
265+
}
266+
256267
type BuildError struct {
257268
// Status is a suitable HTTP status code
258269
Status int
@@ -686,6 +697,22 @@ func (b *Builder) getDynamicParameterRenderer() (dynamicparameters.Renderer, err
686697
return nil, xerrors.Errorf("get template version variables: %w", err)
687698
}
688699

700+
// Pass render cache if available
701+
if b.renderCache != nil {
702+
renderer, err := dynamicparameters.Prepare(b.ctx, b.store, b.fileCache, tv.ID,
703+
dynamicparameters.WithTemplateVersion(*tv),
704+
dynamicparameters.WithProvisionerJob(*job),
705+
dynamicparameters.WithTerraformValues(*tfVals),
706+
dynamicparameters.WithTemplateVariableValues(variableValues),
707+
dynamicparameters.WithRenderCache(b.renderCache),
708+
)
709+
if err != nil {
710+
return nil, xerrors.Errorf("get template version renderer: %w", err)
711+
}
712+
b.parameterRender = renderer
713+
return renderer, nil
714+
}
715+
689716
renderer, err := dynamicparameters.Prepare(b.ctx, b.store, b.fileCache, tv.ID,
690717
dynamicparameters.WithTemplateVersion(*tv),
691718
dynamicparameters.WithProvisionerJob(*job),

enterprise/coderd/prebuilds/reconcile.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/coder/coder/v2/coderd/database/dbauthz"
2727
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
2828
"github.com/coder/coder/v2/coderd/database/pubsub"
29+
"github.com/coder/coder/v2/coderd/dynamicparameters"
2930
"github.com/coder/coder/v2/coderd/files"
3031
"github.com/coder/coder/v2/coderd/notifications"
3132
"github.com/coder/coder/v2/coderd/prebuilds"
@@ -58,6 +59,9 @@ type StoreReconciler struct {
5859
metrics *MetricsCollector
5960
// Operational metrics
6061
reconciliationDuration prometheus.Histogram
62+
63+
// renderCache caches template rendering results to avoid expensive re-parsing
64+
renderCache *dynamicparameters.RenderCache
6165
}
6266

6367
var _ prebuilds.ReconciliationOrchestrator = &StoreReconciler{}
@@ -102,6 +106,7 @@ func NewStoreReconciler(store database.Store,
102106
buildUsageChecker: buildUsageChecker,
103107
done: make(chan struct{}, 1),
104108
provisionNotifyCh: make(chan database.ProvisionerJob, 10),
109+
renderCache: dynamicparameters.NewRenderCache(),
105110
}
106111

107112
if registerer != nil {
@@ -900,7 +905,8 @@ func (c *StoreReconciler) provision(
900905
builder := wsbuilder.New(workspace, transition, *c.buildUsageChecker.Load()).
901906
Reason(database.BuildReasonInitiator).
902907
Initiator(database.PrebuildsSystemUserID).
903-
MarkPrebuild()
908+
MarkPrebuild().
909+
RenderCache(c.renderCache)
904910

905911
if transition != database.WorkspaceTransitionDelete {
906912
// We don't specify the version for a delete transition,

0 commit comments

Comments
 (0)