Skip to content

Commit fbe564f

Browse files
committed
feat: add template_default_builds table for templates sort
1 parent d0ea5b4 commit fbe564f

11 files changed

Lines changed: 1925 additions & 666 deletions

File tree

‎packages/api/internal/handlers/template_tags.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ func (a *APIStore) PostTemplatesTags(c *gin.Context) {
142142
}
143143
}
144144

145+
err = client.RefreshTemplateDefaultBuild(ctx, template.ID)
146+
if err != nil {
147+
telemetry.ReportCriticalError(ctx, "error when refreshing template default build", err)
148+
a.sendAPIStoreError(c, http.StatusInternalServerError, "Error creating tag assignment")
149+
150+
return
151+
}
152+
145153
err = tx.Commit(ctx)
146154
if err != nil {
147155
telemetry.ReportCriticalError(ctx, "error when committing transaction", err)

‎packages/api/internal/handlers/template_update.go‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,15 @@ func (a *APIStore) createBackwardCompatibleAlias(
186186
}
187187
}
188188

189+
// The new alias can change the template's name sort key.
190+
if err := a.sqlcDB.RefreshTemplateDefaultBuild(ctx, templateID); err != nil {
191+
return &api.APIError{
192+
Code: http.StatusInternalServerError,
193+
ClientMsg: "Error creating backward compatible alias",
194+
Err: err,
195+
}
196+
}
197+
189198
a.templateCache.InvalidateAlias(context.WithoutCancel(ctx), nil, alias)
190199
logger.L().Info(ctx, "Created or verified backward compatible non-namespaced alias",
191200
logger.WithTemplateID(templateID),

‎packages/api/internal/template-manager/template_status.go‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,12 @@ func (tm *TemplateManager) SetStatus(ctx context.Context, buildID uuid.UUID, sta
293293
Reason: buildReason,
294294
BuildID: buildID,
295295
})
296+
if err == nil {
297+
// Defensive: a previously ready default build can be failed (e.g.
298+
// admin build cancellation), in which case the projection must
299+
// fall back to the prior ready build.
300+
err = tm.sqlcDB.RefreshTemplateDefaultBuildByBuildID(ctx, buildID)
301+
}
296302
} else {
297303
err = tm.sqlcDB.UpdateEnvBuildStatus(ctx, queries.UpdateEnvBuildStatusParams{
298304
Status: buildStatus(statusGroup),
@@ -310,14 +316,28 @@ func (tm *TemplateManager) SetStatus(ctx context.Context, buildID uuid.UUID, sta
310316
func (tm *TemplateManager) SetFinished(ctx context.Context, buildID uuid.UUID, rootfsSize int64, envdVersion, kernelVersion, firecrackerVersion string) error {
311317
// first do database update to prevent race condition while calling status
312318
// TODO(ENG-3469): Switch to types.BuildStatusReady once all consumers are migrated.
313-
err := tm.sqlcDB.FinishTemplateBuild(ctx, queries.FinishTemplateBuildParams{
319+
client, tx, err := tm.sqlcDB.WithTx(ctx)
320+
if err != nil {
321+
return err
322+
}
323+
defer tx.Rollback(ctx)
324+
325+
err = client.FinishTemplateBuild(ctx, queries.FinishTemplateBuildParams{
314326
TotalDiskSizeMb: &rootfsSize,
315327
Status: types.BuildStatusUploaded,
316328
EnvdVersion: &envdVersion,
317329
KernelVersion: kernelVersion,
318330
FirecrackerVersion: firecrackerVersion,
319331
BuildID: buildID,
320332
})
333+
if err == nil {
334+
// Same transaction as the status flip so the projection can never
335+
// observe a ready build without reflecting it.
336+
err = client.RefreshTemplateDefaultBuildByBuildID(ctx, buildID)
337+
}
338+
if err == nil {
339+
err = tx.Commit(ctx)
340+
}
321341

322342
tm.buildCache.Invalidate(ctx, buildID)
323343

‎packages/api/internal/template/register_build.go‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,19 @@ func RegisterBuild(
359359
}
360360
}
361361

362+
// Placed after alias and assignment writes so the projection picks up the
363+
// final alias state; for new templates this creates the row with no build.
364+
err = client.RefreshTemplateDefaultBuild(ctx, data.TemplateID)
365+
if err != nil {
366+
telemetry.ReportCriticalError(ctx, "error when refreshing template default build", err)
367+
368+
return nil, &api.APIError{
369+
Err: err,
370+
ClientMsg: fmt.Sprintf("Error when updating template: %s", err),
371+
Code: http.StatusInternalServerError,
372+
}
373+
}
374+
362375
// Commit the transaction
363376
err = tx.Commit(ctx)
364377
if err != nil {
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
-- +goose Up
2+
-- +goose NO TRANSACTION
3+
4+
5+
-- This table is a rebuildable cache, not a source of truth: every write is an
6+
-- idempotent recompute from the canonical query.
7+
-- +goose StatementBegin
8+
CREATE TABLE IF NOT EXISTS "public"."template_default_builds" (
9+
template_id TEXT PRIMARY KEY REFERENCES "public"."envs"(id) ON DELETE CASCADE,
10+
team_id UUID NOT NULL,
11+
build_id UUID,
12+
vcpu BIGINT NOT NULL DEFAULT 0,
13+
ram_mb BIGINT NOT NULL DEFAULT 0,
14+
total_disk_size_mb BIGINT,
15+
build_created_at TIMESTAMPTZ,
16+
name_sort_key TEXT NOT NULL DEFAULT '',
17+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
18+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
19+
);
20+
-- +goose StatementEnd
21+
22+
-- Backfill existing templates in batches to avoid long-held locks
23+
-- Idempotent (ON CONFLICT DO NOTHING), so the migration can be re-run safely.
24+
-- +goose StatementBegin
25+
CREATE OR REPLACE PROCEDURE backfill_template_default_builds() AS $$
26+
DECLARE
27+
last_id TEXT := '';
28+
batch_max TEXT;
29+
BEGIN
30+
LOOP
31+
SELECT max(id) INTO batch_max
32+
FROM (
33+
SELECT e.id
34+
FROM "public"."envs" e
35+
WHERE e.source = 'template' AND e.id > last_id
36+
ORDER BY e.id
37+
LIMIT 10000
38+
) batch;
39+
40+
EXIT WHEN batch_max IS NULL;
41+
42+
INSERT INTO "public"."template_default_builds"
43+
(template_id, team_id, build_id, vcpu, ram_mb, total_disk_size_mb, build_created_at, name_sort_key)
44+
SELECT
45+
e.id,
46+
e.team_id,
47+
b.build_id,
48+
COALESCE(b.vcpu, 0),
49+
COALESCE(b.ram_mb, 0),
50+
b.total_disk_size_mb,
51+
b.build_created_at,
52+
COALESCE(a.name_sort_key, '')
53+
FROM "public"."envs" e
54+
LEFT JOIN LATERAL (
55+
SELECT
56+
eb.id AS build_id,
57+
eb.vcpu,
58+
eb.ram_mb,
59+
eb.total_disk_size_mb,
60+
eb.created_at AS build_created_at
61+
FROM "public"."env_build_assignments" ba
62+
JOIN "public"."env_builds" eb ON eb.id = ba.build_id
63+
WHERE ba.env_id = e.id AND ba.tag = 'default' AND eb.status_group = 'ready'
64+
ORDER BY ba.created_at DESC
65+
LIMIT 1
66+
) b ON TRUE
67+
LEFT JOIN LATERAL (
68+
SELECT MIN(CASE WHEN namespace IS NOT NULL THEN namespace || '/' || alias ELSE alias END) AS name_sort_key
69+
FROM "public"."env_aliases"
70+
WHERE env_id = e.id
71+
) a ON TRUE
72+
WHERE e.source = 'template' AND e.id > last_id AND e.id <= batch_max
73+
ON CONFLICT (template_id) DO NOTHING;
74+
75+
last_id := batch_max;
76+
COMMIT;
77+
RAISE NOTICE 'backfill_template_default_builds: processed up to id %, sleeping 2s before next batch...', batch_max;
78+
PERFORM pg_sleep(2);
79+
END LOOP;
80+
END;
81+
$$ LANGUAGE plpgsql;
82+
-- +goose StatementEnd
83+
84+
CALL backfill_template_default_builds();
85+
DROP PROCEDURE backfill_template_default_builds();
86+
87+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_template_default_builds_team_name
88+
ON "public"."template_default_builds" (team_id, name_sort_key, template_id);
89+
90+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_template_default_builds_team_vcpu
91+
ON "public"."template_default_builds" (team_id, vcpu, template_id);
92+
93+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_template_default_builds_team_ram
94+
ON "public"."template_default_builds" (team_id, ram_mb, template_id);
95+
96+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_template_default_builds_build_id
97+
ON "public"."template_default_builds" (build_id);
98+
99+
-- +goose Down
100+
-- +goose NO TRANSACTION
101+
102+
-- +goose StatementBegin
103+
DROP TABLE IF EXISTS "public"."template_default_builds";
104+
-- +goose StatementEnd

0 commit comments

Comments
 (0)