Skip to content

Commit 137e6de

Browse files
d-csclaude
andcommitted
perf(run-store,webapp): prime run to org off the hot path, drop the dual-write DB read
Make the hot snapshot read path do no per-request run to org DB read during pure dual-write. resolve(runId) is now a pure cache get: the fire-and-forget replica populate is gone. The decorator primes the run to org cache for free on every mirrored write and every Redis read hit, so a resident run's mapping is known with zero DB. resolveAuthoritative stays as the fail-closed backstop for the redis-only guard, and the fallback order is unchanged. Also corrects the stale writesRedisForTransition doc comment, which still claimed the predicate was org-blind after it became per-org presence-as-latch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d45c466 commit 137e6de

5 files changed

Lines changed: 273 additions & 74 deletions

File tree

apps/webapp/app/v3/snapshotRunOrg.server.ts

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { LRUCache } from "lru-cache";
2-
import { $replica, prisma } from "~/db.server";
2+
import { prisma } from "~/db.server";
33
import { env } from "~/env.server";
4-
import { logger } from "~/services/logger.server";
54
import { singleton } from "~/utils/singleton";
65

76
/**
@@ -29,31 +28,38 @@ type RunOrgClient = {
2928

3029
export type SnapshotRunOrgSource = {
3130
/**
32-
* Cache hit or undefined. On a miss, kicks off a replica populate off-path and returns at once.
33-
* Never blocks, never throws.
31+
* A PURE cache get: the cached org id, or undefined on a miss. Never queries, never blocks, never
32+
* throws. The hot read path calls this, so it does no work — a resident run's mapping is put here
33+
* for free by `prime`, from a mirrored write or a Redis read hit, and a non-resident run never
34+
* needs one. There is deliberately no off-path populate: during pure dual-write the read path must
35+
* issue zero run→org DB reads fleet-wide.
3436
*/
3537
resolve(runId: string): string | undefined;
38+
/**
39+
* Records a run→org mapping the caller learned for free — from a mirrored write or a Redis read
40+
* hit — so a later `resolve` is a pure hit with no DB read. In-memory, immutable (run→org never
41+
* changes), no TTL, no invalidation. Fire-and-forget: never queries, never throws.
42+
*/
43+
prime(runId: string, organizationId: string): void;
3644
/**
3745
* Awaits a bounded primary read, caches, and returns the org id. Throws on timeout, client
38-
* failure, or a run with no organisation, so a caller can fail closed.
46+
* failure, or a run with no organisation, so a caller can fail closed. This is the redis-only
47+
* fallback gate's last leg, reached only when the sync cache is cold AND some org is redis-only.
3948
*/
4049
resolveAuthoritative(runId: string): Promise<string>;
4150
};
4251

4352
export function createSnapshotRunOrgSource(clients?: {
4453
primary: RunOrgClient;
45-
replica: RunOrgClient;
4654
}): SnapshotRunOrgSource {
4755
const primaryClient = (clients?.primary ?? prisma) as RunOrgClient;
48-
const replicaClient = (clients?.replica ?? $replica) as RunOrgClient;
4956
// No ttl: run→org is immutable, so a cached mapping never goes stale.
5057
const cache = new LRUCache<string, string>({
5158
max: env.RUN_ENGINE_SNAPSHOT_STORE_RUN_ORG_CACHE_MAX ?? DEFAULT_CACHE_MAX,
5259
});
53-
const inFlight = new Set<string>();
5460

55-
async function read(runId: string, client: RunOrgClient): Promise<string> {
56-
return client.taskRun
61+
async function read(runId: string): Promise<string> {
62+
return primaryClient.taskRun
5763
.findFirst({
5864
where: { id: runId },
5965
select: { runtimeEnvironment: { select: { organizationId: true } } },
@@ -70,21 +76,12 @@ export function createSnapshotRunOrgSource(clients?: {
7076

7177
return {
7278
resolve(runId) {
73-
const cached = cache.get(runId);
74-
if (cached !== undefined) {
75-
return cached;
76-
}
77-
if (!inFlight.has(runId)) {
78-
inFlight.add(runId);
79-
void read(runId, replicaClient)
80-
.catch((error) => {
81-
logger.warn("snapshotRunOrg: run→org populate failed", { runId, error });
82-
})
83-
.finally(() => {
84-
inFlight.delete(runId);
85-
});
86-
}
87-
return undefined;
79+
return cache.get(runId);
80+
},
81+
prime(runId, organizationId) {
82+
// run→org is immutable, so this can only ever confirm what is already there; setting keeps the
83+
// active run hot in the LRU, which is exactly the run whose mapping the read path will want.
84+
cache.set(runId, organizationId);
8885
},
8986
async resolveAuthoritative(runId) {
9087
const cached = cache.get(runId);
@@ -104,7 +101,7 @@ export function createSnapshotRunOrgSource(clients?: {
104101
});
105102

106103
try {
107-
return await Promise.race([read(runId, primaryClient), deadline]);
104+
return await Promise.race([read(runId), deadline]);
108105
} finally {
109106
if (timer) clearTimeout(timer);
110107
}

apps/webapp/app/v3/snapshotStoreMode.server.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ type ResolverOrgSource = {
103103
/** Resolves a run to its organisation. Cache-only and synchronous, undefined on a miss. */
104104
type ResolverRunOrgSource = {
105105
resolve(runId: string): string | undefined;
106+
/** Records an immutable run→org mapping learned off a mirrored write or a Redis read hit. */
107+
prime?(runId: string, organizationId: string): void;
106108
/** Bounded authoritative read, throws on failure/timeout, for the redis-only fallback gate. */
107109
resolveAuthoritative?(runId: string): Promise<string>;
108110
};
@@ -194,6 +196,11 @@ export function buildSnapshotStoreModeResolver(deps: {
194196
},
195197
anyOrgReadEnabled: (): boolean => deps.census?.anyOrgReadEnabled() ?? false,
196198
anyOrgRedisOnly: (): boolean => deps.census?.anyOrgRedisOnly() ?? false,
199+
// The decorator hands back a run→org mapping it learned on a mirrored write or a Redis read hit.
200+
// Recorded in-memory so a later readModeFor is a pure hit; absent hook is a no-op.
201+
prime: (runId: string, organizationId: string): void => {
202+
deps.runOrg?.prime?.(runId, organizationId);
203+
},
197204
};
198205
}
199206

@@ -237,6 +244,7 @@ export const snapshotStoreModeResolver: SnapshotStoreModeResolver = buildSnapsho
237244
},
238245
runOrg: {
239246
resolve: (runId) => snapshotRunOrgSource().resolve(runId),
247+
prime: (runId, organizationId) => snapshotRunOrgSource().prime(runId, organizationId),
240248
resolveAuthoritative: (runId) => snapshotRunOrgSource().resolveAuthoritative(runId),
241249
},
242250
census: {

apps/webapp/test/snapshotRunOrg.server.test.ts

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,77 +33,77 @@ function fakeClient(opts: {
3333
const tick = () => new Promise((resolve) => setTimeout(resolve, 10));
3434

3535
describe("snapshot run→org source", () => {
36-
it("returns undefined on a cold miss, then serves the org id once the populate settles", async () => {
37-
const replica = fakeClient({ mapping: { run_a: "org_a" } });
38-
const source = createSnapshotRunOrgSource({ primary: replica, replica });
36+
it("resolve is a pure cache get: a cold miss is undefined and never queries", async () => {
37+
const primary = fakeClient({ mapping: { run_a: "org_a" } });
38+
const source = createSnapshotRunOrgSource({ primary });
3939

4040
expect(source.resolve("run_a")).toBeUndefined();
4141

4242
await tick();
4343

44-
expect(source.resolve("run_a")).toBe("org_a");
44+
// No off-path populate exists anymore, so a miss stays a miss and the DB is never touched.
45+
expect(source.resolve("run_a")).toBeUndefined();
46+
expect(primary.calls).toBe(0);
4547
});
4648

47-
it("does not start a second populate while one is in flight", async () => {
48-
const replica = fakeClient({ mapping: { run_a: "org_a" }, delayMs: 20 });
49-
const source = createSnapshotRunOrgSource({ primary: replica, replica });
49+
it("prime makes a later resolve a pure hit, with no query", async () => {
50+
const primary = fakeClient({ mapping: { run_a: "org_a" } });
51+
const source = createSnapshotRunOrgSource({ primary });
52+
53+
source.prime("run_a", "org_a");
54+
55+
expect(source.resolve("run_a")).toBe("org_a");
56+
await tick();
57+
expect(primary.calls).toBe(0);
58+
});
5059

51-
source.resolve("run_a");
52-
source.resolve("run_a");
53-
source.resolve("run_a");
60+
it("prime is idempotent and never queries, however many times it is called", async () => {
61+
const primary = fakeClient({ mapping: { run_a: "org_a" } });
62+
const source = createSnapshotRunOrgSource({ primary });
5463

55-
await new Promise((resolve) => setTimeout(resolve, 40));
64+
source.prime("run_a", "org_a");
65+
source.prime("run_a", "org_a");
66+
source.prime("run_a", "org_a");
5667

57-
expect(replica.calls).toBe(1);
5868
expect(source.resolve("run_a")).toBe("org_a");
69+
expect(primary.calls).toBe(0);
5970
});
6071

6172
it("resolveAuthoritative returns the org id on success and caches it", async () => {
6273
const primary = fakeClient({ mapping: { run_a: "org_a" } });
63-
const source = createSnapshotRunOrgSource({ primary, replica: primary });
74+
const source = createSnapshotRunOrgSource({ primary });
6475

6576
await expect(source.resolveAuthoritative("run_a")).resolves.toBe("org_a");
6677
expect(source.resolve("run_a")).toBe("org_a");
6778
});
6879

80+
it("resolveAuthoritative serves a primed mapping without querying", async () => {
81+
const primary = fakeClient({ mapping: { run_a: "org_a" } });
82+
const source = createSnapshotRunOrgSource({ primary });
83+
84+
source.prime("run_a", "org_a");
85+
86+
await expect(source.resolveAuthoritative("run_a")).resolves.toBe("org_a");
87+
expect(primary.calls).toBe(0);
88+
});
89+
6990
it("resolveAuthoritative throws when the run has no organization", async () => {
7091
const primary = fakeClient({ mapping: {} });
71-
const source = createSnapshotRunOrgSource({ primary, replica: primary });
92+
const source = createSnapshotRunOrgSource({ primary });
7293

7394
await expect(source.resolveAuthoritative("run_missing")).rejects.toThrow();
7495
});
7596

7697
it("resolveAuthoritative throws when the client rejects", async () => {
7798
const primary = fakeClient({ reject: true });
78-
const source = createSnapshotRunOrgSource({ primary, replica: primary });
99+
const source = createSnapshotRunOrgSource({ primary });
79100

80101
await expect(source.resolveAuthoritative("run_a")).rejects.toThrow();
81102
});
82103

83-
it("resolve stays silent and releases in-flight when findFirst throws synchronously", async () => {
84-
let calls = 0;
85-
const throwing = {
86-
taskRun: {
87-
findFirst() {
88-
calls++;
89-
throw new Error("sync boom");
90-
},
91-
},
92-
} as unknown as NonNullable<Parameters<typeof createSnapshotRunOrgSource>[0]>["replica"];
93-
const source = createSnapshotRunOrgSource({ primary: throwing, replica: throwing });
94-
95-
expect(() => source.resolve("run_a")).not.toThrow();
96-
97-
await tick();
98-
99-
// In-flight was released, so a fresh miss starts a new populate rather than wedging forever.
100-
expect(() => source.resolve("run_a")).not.toThrow();
101-
expect(calls).toBe(2);
102-
});
103-
104104
it("resolveAuthoritative throws when the read exceeds the deadline", async () => {
105105
const primary = fakeClient({ mapping: { run_a: "org_a" }, delayMs: 2000 });
106-
const source = createSnapshotRunOrgSource({ primary, replica: primary });
106+
const source = createSnapshotRunOrgSource({ primary });
107107

108108
await expect(source.resolveAuthoritative("run_a")).rejects.toThrow(/deadline|exceed/i);
109109
});
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// The read path must do NO run→org DB read during pure dual-write, and the mappings it needs must
2+
// arrive for free: the decorator primes the run→org cache on every mirrored write and every Redis
3+
// read hit. A counting fake stands in for the run→org source — `readModeFor` is a pure in-memory
4+
// lookup (the off-path populate is gone), and only `readModeForAuthoritative` would ever touch the
5+
// DB, so counting its calls counts run→org DB reads.
6+
import { describe, expect, it } from "vitest";
7+
import {
8+
TaskRunExecutionSnapshotStore,
9+
type SnapshotStoreMode,
10+
type SnapshotStoreModeResolver,
11+
} from "./taskRunExecutionSnapshotStore.js";
12+
import type { RedisSnapshotStore, SnapshotRead } from "./redisSnapshotStore.js";
13+
import type { RunStore } from "./types.js";
14+
15+
const SCOPE = {
16+
environmentId: "env_1",
17+
environmentType: "PRODUCTION",
18+
projectId: "proj_1",
19+
organizationId: "org_a",
20+
} as const;
21+
22+
function harness(opts: {
23+
globalMode: SnapshotStoreMode;
24+
anyOrgReadEnabled?: boolean;
25+
anyOrgRedisOnly?: boolean;
26+
/** What Redis returns from getLatest, when a read routes to it. */
27+
latest?: SnapshotRead | null;
28+
}) {
29+
// The run→org cache the decorator primes into, plus a DB-read counter. `readModeFor` reads it in
30+
// memory; only the authoritative leg counts as a DB read, and nothing here calls it unless asked.
31+
const runOrg = new Map<string, string>();
32+
let dbReads = 0;
33+
34+
const redis = new Proxy({} as RedisSnapshotStore, {
35+
get: (_t, prop) => {
36+
if (prop === "getLatest") return () => Promise.resolve(opts.latest ?? null);
37+
return () => Promise.resolve({ outcome: "written", seq: 1 });
38+
},
39+
});
40+
41+
const delegateTouched: string[] = [];
42+
const delegate = new Proxy({} as Record<string, unknown>, {
43+
get: (_t, prop) => {
44+
return (...__: unknown[]) => {
45+
delegateTouched.push(String(prop));
46+
return Promise.resolve({ id: "pg_row" });
47+
};
48+
},
49+
}) as unknown as RunStore;
50+
51+
const modeResolver: SnapshotStoreModeResolver = {
52+
resolve: () => opts.globalMode,
53+
prime: (runId: string, organizationId: string) => {
54+
runOrg.set(runId, organizationId);
55+
},
56+
readModeFor: (runId: string) => {
57+
const org = runOrg.get(runId);
58+
return org ? opts.globalMode : undefined;
59+
},
60+
readModeForAuthoritative: async (runId: string) => {
61+
dbReads++;
62+
const org = runOrg.get(runId);
63+
return org ? opts.globalMode : undefined;
64+
},
65+
anyOrgReadEnabled: () => opts.anyOrgReadEnabled ?? false,
66+
anyOrgRedisOnly: () => opts.anyOrgRedisOnly ?? false,
67+
};
68+
69+
const decorated = new TaskRunExecutionSnapshotStore(delegate, {
70+
store: redis,
71+
mode: opts.globalMode,
72+
modeResolver,
73+
readPercent: 100,
74+
});
75+
76+
return { decorated, runOrg, delegateTouched, dbReads: () => dbReads };
77+
}
78+
79+
function completionForOrg(organizationId: string) {
80+
return {
81+
completedAt: new Date(),
82+
outputType: "application/json",
83+
usageDurationMs: 1,
84+
costInCents: 0,
85+
snapshot: {
86+
id: "snap_1",
87+
executionStatus: "FINISHED" as const,
88+
description: "done",
89+
runStatus: "COMPLETED_SUCCESSFULLY" as const,
90+
attemptNumber: 1,
91+
...SCOPE,
92+
organizationId,
93+
},
94+
};
95+
}
96+
97+
function redisRead(organizationId: string): SnapshotRead {
98+
return {
99+
id: "snap_1",
100+
seq: 1,
101+
isValid: true,
102+
raw: "{}",
103+
entry: {
104+
id: "snap_1",
105+
runId: "run_1",
106+
organizationId,
107+
executionStatus: "EXECUTING",
108+
createdAt: new Date().toISOString(),
109+
},
110+
};
111+
}
112+
113+
describe("priming the run→org cache off the hot path", () => {
114+
it("a mirrored transition primes the run→org mapping, with no run→org DB read", async () => {
115+
const h = harness({ globalMode: "dual-write" });
116+
117+
await h.decorated.completeAttemptSuccess("run_1", completionForOrg("org_a"), {
118+
select: { id: true },
119+
});
120+
121+
expect(h.runOrg.get("run_1")).toBe("org_a");
122+
expect(h.dbReads()).toBe(0);
123+
// After the write, the run's mode resolves from the primed cache: a pure hit, still no DB read.
124+
expect(h.decorated.modeForTest("org_a")).toBe("dual-write");
125+
expect(h.dbReads()).toBe(0);
126+
});
127+
128+
it("a Redis read hit primes the run→org mapping", async () => {
129+
const h = harness({
130+
globalMode: "redis-read",
131+
anyOrgReadEnabled: true,
132+
latest: redisRead("org_a"),
133+
});
134+
135+
const latest = await h.decorated.findLatestExecutionSnapshot("run_1");
136+
137+
expect(latest).not.toBeNull();
138+
expect(h.runOrg.get("run_1")).toBe("org_a");
139+
});
140+
141+
it("a pure dual-write read issues ZERO run→org DB calls", async () => {
142+
const h = harness({
143+
globalMode: "dual-write",
144+
anyOrgReadEnabled: false,
145+
anyOrgRedisOnly: false,
146+
});
147+
148+
const latest = await h.decorated.findLatestExecutionSnapshot("run_cold", undefined, "env_1");
149+
150+
// Fell back to Postgres, and the authoritative (DB) leg was never consulted.
151+
expect(latest).toEqual({ id: "pg_row" });
152+
expect(h.delegateTouched).toContain("findLatestExecutionSnapshot");
153+
expect(h.dbReads()).toBe(0);
154+
});
155+
});

0 commit comments

Comments
 (0)