Skip to content

Commit 28b2358

Browse files
bchapuisclaude
andcommitted
Denormalize workflowName into execution store to avoid extra DB queries
Store workflow name directly in execution records and analytics blobs, eliminating the need to join or fetch workflow names separately when listing or displaying executions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0d59a10 commit 28b2358

8 files changed

Lines changed: 22 additions & 37 deletions

File tree

apps/api/src/mocks/execution-store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export class MockExecutionStore implements ExecutionStore {
2828
const row: ExecutionRow = {
2929
id: record.id,
3030
workflowId: record.workflowId,
31+
workflowName: record.workflowName,
3132
organizationId: record.organizationId,
3233
status: record.status,
3334
error: record.error ?? null,

apps/api/src/routes/admin/executions.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Hono } from "hono";
55
import { z } from "zod";
66

77
import { ApiContext } from "../../context";
8-
import { createDatabase, organizations, workflows } from "../../db";
8+
import { createDatabase, organizations } from "../../db";
99
import { CloudflareExecutionStore } from "../../runtime/cloudflare-execution-store";
1010

1111
const adminExecutionsRoutes = new Hono<ApiContext>();
@@ -127,10 +127,9 @@ adminExecutionsRoutes.get(
127127
const result = (await response.json()) as { data?: any[] };
128128
const rows = result.data || [];
129129

130-
// Get organization and workflow names for the results
130+
// Get organization names for the results
131131
const db = createDatabase(c.env.DB);
132132
const orgIds = [...new Set(rows.map((r) => r.index1))];
133-
const workflowIds = [...new Set(rows.map((r) => r.blob2))];
134133

135134
// Fetch organization names
136135
const orgsMap = new Map<string, string>();
@@ -141,15 +140,6 @@ adminExecutionsRoutes.get(
141140
orgs.forEach((o) => orgsMap.set(o.id, o.name));
142141
}
143142

144-
// Fetch workflow names
145-
const workflowsMap = new Map<string, string>();
146-
if (workflowIds.length > 0) {
147-
const wfs = await db
148-
.select({ id: workflows.id, name: workflows.name })
149-
.from(workflows);
150-
wfs.forEach((w) => workflowsMap.set(w.id, w.name));
151-
}
152-
153143
const executions = rows.map((row) => {
154144
const timestamp = new Date(row.timestamp);
155145
const startedAt = row.double2 ? new Date(row.double2) : timestamp;
@@ -158,7 +148,7 @@ adminExecutionsRoutes.get(
158148
return {
159149
id: row.blob1,
160150
workflowId: row.blob2,
161-
workflowName: workflowsMap.get(row.blob2) || "Unknown Workflow",
151+
workflowName: row.blob6 || "Unknown Workflow",
162152
organizationId: row.index1,
163153
organizationName: orgsMap.get(row.index1) || "Unknown Organization",
164154
status: row.blob4 as ExecutionStatusType,
@@ -213,14 +203,9 @@ adminExecutionsRoutes.get(
213203
return c.json({ error: "Execution not found" }, 404);
214204
}
215205

216-
// Get workflow and org names
206+
// Get org name
217207
const db = createDatabase(c.env.DB);
218208

219-
const [workflow] = await db
220-
.select({ name: workflows.name })
221-
.from(workflows)
222-
.where(eq(workflows.id, execution.workflowId));
223-
224209
const [org] = await db
225210
.select({ name: organizations.name })
226211
.from(organizations)
@@ -230,7 +215,7 @@ adminExecutionsRoutes.get(
230215
execution: {
231216
id: execution.id,
232217
workflowId: execution.workflowId,
233-
workflowName: workflow?.name || "Unknown Workflow",
218+
workflowName: execution.workflowName,
234219
organizationId,
235220
organizationName: org?.name || "Unknown Organization",
236221
status: execution.status,

apps/api/src/routes/dashboard.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ dashboard.get("/", async (c) => {
6464
// Recent executions (last 10)
6565
const recentExecutions = executions.slice(0, 10).map((e: ExecutionRow) => ({
6666
id: e.id,
67-
workflowName: workflows.find((w) => w.id === e.workflowId)?.name || "",
67+
workflowName: e.workflowName,
6868
status: e.status,
6969
startedAt: e.startedAt ? Number(e.startedAt) : Date.now(),
7070
endedAt: e.endedAt ? Number(e.endedAt) : undefined,

apps/api/src/routes/executions.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { ApiContext } from "../context";
1313
import { createDatabase } from "../db";
1414
import { feedback } from "../db/schema";
1515
import { CloudflareExecutionStore } from "../runtime/cloudflare-execution-store";
16-
import { WorkflowStore } from "../stores/workflow-store";
1716
import { isUuid, parseUuid } from "../utils/validation";
1817

1918
const executionRoutes = new Hono<ApiContext>();
@@ -36,17 +35,10 @@ executionRoutes.get("/:id", apiKeyOrJwtMiddleware, async (c) => {
3635
return c.json({ error: "Execution not found" }, 404);
3736
}
3837

39-
// Get workflow name
40-
const workflowStore = new WorkflowStore(c.env);
41-
const workflowName = await workflowStore.getName(
42-
execution.workflowId,
43-
organizationId
44-
);
45-
4638
const workflowExecution: WorkflowExecution = {
4739
id: execution.id,
4840
workflowId: execution.workflowId,
49-
workflowName: workflowName || "Unknown Workflow",
41+
workflowName: execution.workflowName,
5042
status: execution.status as WorkflowExecutionStatus,
5143
nodeExecutions: execution.data.nodeExecutions || [],
5244
error: execution.error || undefined,
@@ -88,7 +80,6 @@ executionRoutes.get("/:id", apiKeyOrJwtMiddleware, async (c) => {
8880

8981
executionRoutes.get("/", jwtMiddleware, async (c) => {
9082
const executionStore = new CloudflareExecutionStore(c.env);
91-
const workflowStore = new WorkflowStore(c.env);
9283
const { workflowId, limit, offset } = c.req.query();
9384

9485
const organizationId = c.get("organizationId")!;
@@ -108,16 +99,11 @@ executionRoutes.get("/", jwtMiddleware, async (c) => {
10899

109100
const executions = await executionStore.list(organizationId, queryParams);
110101

111-
// Get workflow names for all executions
112-
const workflowIds = [...new Set(executions.map((e) => e.workflowId))];
113-
const workflowNames = await workflowStore.getNames(workflowIds);
114-
const workflowMap = new Map(workflowNames.map((w) => [w.id, w.name]));
115-
116102
const results = executions.map((execution) => {
117103
return {
118104
id: execution.id,
119105
workflowId: execution.workflowId,
120-
workflowName: workflowMap.get(execution.workflowId) || "Unknown Workflow",
106+
workflowName: execution.workflowName,
121107
status: execution.status as WorkflowExecutionStatus,
122108
error: execution.error || undefined,
123109
startedAt: execution.startedAt || undefined,

apps/api/src/routes/workflows.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ workflowRoutes.post(
570570
const updatedExecution = await executionStore.save({
571571
id: executionId,
572572
workflowId: execution.workflowId,
573+
workflowName: execution.workflowName,
573574
userId: "cancelled", // Required by SaveExecutionRecord but not stored in DB
574575
organizationId: execution.organizationId,
575576
status: ExecutionStatus.CANCELLED,
@@ -594,6 +595,7 @@ workflowRoutes.post(
594595
await executionStore.save({
595596
id: executionId,
596597
workflowId: execution.workflowId,
598+
workflowName: execution.workflowName,
597599
userId: "cancelled", // Required by SaveExecutionRecord but not stored in DB
598600
organizationId: execution.organizationId,
599601
status: ExecutionStatus.CANCELLED,

apps/api/src/runtime/cloudflare-execution-store.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ interface AnalyticsRow {
3030
blob3: string;
3131
blob4: string;
3232
blob5: string;
33+
blob6: string;
3334
double1: number;
3435
double2: number;
3536
double3: number;
@@ -61,6 +62,7 @@ export class CloudflareExecutionStore implements ExecutionStore {
6162
const executionData: WorkflowExecution = {
6263
id: record.id,
6364
workflowId: record.workflowId,
65+
workflowName: record.workflowName,
6466
status: record.status,
6567
nodeExecutions,
6668
error: record.error,
@@ -166,6 +168,7 @@ export class CloudflareExecutionStore implements ExecutionStore {
166168
record.definitionHash ?? "",
167169
record.status,
168170
(record.error || "").substring(0, 2000), // truncate to fit in blob
171+
record.workflowName,
169172
],
170173
doubles: [durationMs, startedAtMs, endedAtMs, usage],
171174
};
@@ -273,6 +276,10 @@ export class CloudflareExecutionStore implements ExecutionStore {
273276
return {
274277
id: executionData.id,
275278
workflowId: executionData.workflowId,
279+
workflowName:
280+
executionData.workflowName ??
281+
executionData.workflowDefinition?.name ??
282+
"Unknown Workflow",
276283
organizationId,
277284
status: executionData.status,
278285
error: executionData.error ?? null,
@@ -326,6 +333,7 @@ export class CloudflareExecutionStore implements ExecutionStore {
326333
return {
327334
id: row.blob1,
328335
workflowId: row.blob2,
336+
workflowName: row.blob6 || "Unknown Workflow",
329337
organizationId: row.index1,
330338
definitionHash: row.blob3 || undefined,
331339
status: row.blob4 as WorkflowExecutionStatus,

packages/runtime/src/base-runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ export abstract class Runtime<Env = unknown> {
472472
return this.executionStore.save({
473473
id: instanceId,
474474
workflowId: ctx.workflowId,
475+
workflowName: ctx.workflow.name,
475476
userId,
476477
organizationId,
477478
status: finalStatus,

packages/runtime/src/execution-store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
export interface ExecutionRow {
1212
id: string;
1313
workflowId: string;
14+
workflowName: string;
1415
organizationId: string;
1516
status: WorkflowExecutionStatus;
1617
error: string | null;
@@ -28,6 +29,7 @@ export interface ExecutionRow {
2829
export interface SaveExecutionRecord {
2930
id: string;
3031
workflowId: string;
32+
workflowName: string;
3133
userId: string;
3234
organizationId: string;
3335
status: WorkflowExecutionStatus;

0 commit comments

Comments
 (0)