Skip to content

Commit 0e117c2

Browse files
committed
fix(webapp): move transaction-resilience config out of db.server so wholesale db.server mocks don't break
1 parent dd41264 commit 0e117c2

3 files changed

Lines changed: 87 additions & 74 deletions

File tree

apps/webapp/app/db.server.ts

Lines changed: 6 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ import {
33
PrismaClient,
44
boundedIn,
55
$transaction as transac,
6-
TokenBucketRetryBudget,
76
type PrismaClientOrTransaction,
87
type PrismaReplicaClient,
98
type PrismaTransactionClient,
109
type PrismaTransactionOptions,
11-
type TransactionStartRetryConfig,
1210
} from "@trigger.dev/database";
1311
import { RunOpsPrismaClient } from "@internal/run-ops-database";
1412
import { markReadReplicaClient } from "@internal/run-store";
@@ -34,6 +32,12 @@ import {
3432
import { computeRunOpsSplitReadEnabled } from "./v3/runOpsMigration/runOpsSplitReadGate";
3533
import { assertControlPlaneCoresidencyAdvisory } from "./v3/runOpsMigration/controlPlaneCoresidencySentinel.server";
3634
import { DATASOURCE_CONTEXT_KEY, startActiveSpan } from "./v3/tracer.server";
35+
import {
36+
controlPlaneTransactionResilience,
37+
runOpsLegacyTransactionResilience,
38+
runOpsTransactionResilience,
39+
type TransactionResilienceConfig,
40+
} from "./v3/transactionResilience.server";
3741
import type { Span } from "@opentelemetry/api";
3842
import { context, trace } from "@opentelemetry/api";
3943
import { queryPerformanceMonitor } from "./utils/queryPerformanceMonitor.server";
@@ -61,74 +65,6 @@ function logTransactionPrismaError(error: Prisma.PrismaClientKnownRequestError)
6165
});
6266
}
6367

64-
/**
65-
* Resolved transaction-resilience config for one writer pool. Each pool gets its own
66-
* {@link TransactionStartRetryConfig} (with its OWN token bucket, so a storm on one pool cannot
67-
* drain another's retry budget) plus the `maxWait` applied when that pool opens a transaction.
68-
* Env is read here at the app boundary (IoC); the library never reads env.
69-
*/
70-
export type TransactionResilienceConfig = {
71-
maxWait: number;
72-
startRetry: TransactionStartRetryConfig;
73-
};
74-
75-
function resolveTransactionResilience(
76-
pool: "control-plane" | "run-ops" | "run-ops-legacy",
77-
overrides: {
78-
maxWaitMs?: number;
79-
enabled?: boolean;
80-
maxAttempts?: number;
81-
backoffMinMs?: number;
82-
backoffMaxMs?: number;
83-
budgetPerSec?: number;
84-
budgetBurst?: number;
85-
}
86-
): TransactionResilienceConfig {
87-
const budgetPerSec =
88-
overrides.budgetPerSec ?? env.DATABASE_TRANSACTION_START_RETRY_BUDGET_PER_SEC;
89-
const budgetBurst = overrides.budgetBurst ?? env.DATABASE_TRANSACTION_START_RETRY_BUDGET_BURST;
90-
return {
91-
maxWait: Math.max(0, overrides.maxWaitMs ?? env.DATABASE_TRANSACTION_MAX_WAIT_MS),
92-
startRetry: {
93-
options: {
94-
enabled: overrides.enabled ?? env.DATABASE_TRANSACTION_START_RETRY_ENABLED,
95-
maxAttempts: overrides.maxAttempts ?? env.DATABASE_TRANSACTION_START_RETRY_MAX_ATTEMPTS,
96-
backoffMinMs: overrides.backoffMinMs ?? env.DATABASE_TRANSACTION_START_RETRY_BACKOFF_MIN_MS,
97-
backoffMaxMs: overrides.backoffMaxMs ?? env.DATABASE_TRANSACTION_START_RETRY_BACKOFF_MAX_MS,
98-
},
99-
budget: new TokenBucketRetryBudget({ ratePerSec: budgetPerSec, burst: budgetBurst }),
100-
onRetry: ({ attempt, delayMs }) =>
101-
logger.warn("retrying transaction start after acquisition failure", {
102-
pool,
103-
attempt,
104-
delayMs,
105-
}),
106-
},
107-
};
108-
}
109-
110-
export const controlPlaneTransactionResilience = resolveTransactionResilience("control-plane", {});
111-
112-
export const runOpsTransactionResilience = resolveTransactionResilience("run-ops", {
113-
maxWaitMs: env.RUN_OPS_DATABASE_TRANSACTION_MAX_WAIT_MS,
114-
enabled: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_ENABLED,
115-
maxAttempts: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_MAX_ATTEMPTS,
116-
backoffMinMs: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_BACKOFF_MIN_MS,
117-
backoffMaxMs: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_BACKOFF_MAX_MS,
118-
budgetPerSec: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_BUDGET_PER_SEC,
119-
budgetBurst: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_BUDGET_BURST,
120-
});
121-
122-
export const runOpsLegacyTransactionResilience = resolveTransactionResilience("run-ops-legacy", {
123-
maxWaitMs: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_MAX_WAIT_MS,
124-
enabled: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_ENABLED,
125-
maxAttempts: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_MAX_ATTEMPTS,
126-
backoffMinMs: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_BACKOFF_MIN_MS,
127-
backoffMaxMs: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_BACKOFF_MAX_MS,
128-
budgetPerSec: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_BUDGET_PER_SEC,
129-
budgetBurst: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_BUDGET_BURST,
130-
});
131-
13268
const transactionResilienceByClient = new WeakMap<object, TransactionResilienceConfig>();
13369

13470
/**

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ import type { PrismaClient, PrismaReplicaClient } from "@trigger.dev/database";
44
import type { RunOpsPrismaClient } from "@internal/run-ops-database";
55
import {
66
$replica,
7-
controlPlaneTransactionResilience,
87
prisma,
98
runOpsLegacyPrisma,
109
runOpsLegacyReplica,
11-
runOpsLegacyTransactionResilience,
1210
runOpsNewPrismaClient,
1311
runOpsNewReplicaClient,
14-
runOpsTransactionResilience,
15-
type TransactionResilienceConfig,
1612
} from "~/db.server";
1713
import { env } from "~/env.server";
1814
import { singleton } from "~/utils/singleton";
15+
import {
16+
controlPlaneTransactionResilience,
17+
runOpsLegacyTransactionResilience,
18+
runOpsTransactionResilience,
19+
type TransactionResilienceConfig,
20+
} from "./transactionResilience.server";
1921

2022
type BuildRunStoreDeps = {
2123
/** Boot constant: true only when both run-ops DBs are configured and the split flag is on. */
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { TokenBucketRetryBudget, type TransactionStartRetryConfig } from "@trigger.dev/database";
2+
import { env } from "~/env.server";
3+
import { logger } from "~/services/logger.server";
4+
5+
/**
6+
* Resolved transaction-resilience config for one writer pool. Each pool gets its own
7+
* {@link TransactionStartRetryConfig} (with its OWN token bucket, so a storm on one pool cannot
8+
* drain another's retry budget) plus the `maxWait` applied when that pool opens a transaction.
9+
* Env is read here at the app boundary (IoC); the library never reads env.
10+
*
11+
* Kept out of `db.server` on purpose: `db.server` is mocked wholesale by ~150 tests, and a new
12+
* export there breaks every mock that does not list it. Both `db.server` and `runStore.server`
13+
* import these from here instead.
14+
*/
15+
export type TransactionResilienceConfig = {
16+
maxWait: number;
17+
startRetry: TransactionStartRetryConfig;
18+
};
19+
20+
function resolveTransactionResilience(
21+
pool: "control-plane" | "run-ops" | "run-ops-legacy",
22+
overrides: {
23+
maxWaitMs?: number;
24+
enabled?: boolean;
25+
maxAttempts?: number;
26+
backoffMinMs?: number;
27+
backoffMaxMs?: number;
28+
budgetPerSec?: number;
29+
budgetBurst?: number;
30+
}
31+
): TransactionResilienceConfig {
32+
const budgetPerSec =
33+
overrides.budgetPerSec ?? env.DATABASE_TRANSACTION_START_RETRY_BUDGET_PER_SEC;
34+
const budgetBurst = overrides.budgetBurst ?? env.DATABASE_TRANSACTION_START_RETRY_BUDGET_BURST;
35+
return {
36+
maxWait: Math.max(0, overrides.maxWaitMs ?? env.DATABASE_TRANSACTION_MAX_WAIT_MS),
37+
startRetry: {
38+
options: {
39+
enabled: overrides.enabled ?? env.DATABASE_TRANSACTION_START_RETRY_ENABLED,
40+
maxAttempts: overrides.maxAttempts ?? env.DATABASE_TRANSACTION_START_RETRY_MAX_ATTEMPTS,
41+
backoffMinMs: overrides.backoffMinMs ?? env.DATABASE_TRANSACTION_START_RETRY_BACKOFF_MIN_MS,
42+
backoffMaxMs: overrides.backoffMaxMs ?? env.DATABASE_TRANSACTION_START_RETRY_BACKOFF_MAX_MS,
43+
},
44+
budget: new TokenBucketRetryBudget({ ratePerSec: budgetPerSec, burst: budgetBurst }),
45+
onRetry: ({ attempt, delayMs }) =>
46+
logger.warn("retrying transaction start after acquisition failure", {
47+
pool,
48+
attempt,
49+
delayMs,
50+
}),
51+
},
52+
};
53+
}
54+
55+
export const controlPlaneTransactionResilience = resolveTransactionResilience("control-plane", {});
56+
57+
export const runOpsTransactionResilience = resolveTransactionResilience("run-ops", {
58+
maxWaitMs: env.RUN_OPS_DATABASE_TRANSACTION_MAX_WAIT_MS,
59+
enabled: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_ENABLED,
60+
maxAttempts: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_MAX_ATTEMPTS,
61+
backoffMinMs: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_BACKOFF_MIN_MS,
62+
backoffMaxMs: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_BACKOFF_MAX_MS,
63+
budgetPerSec: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_BUDGET_PER_SEC,
64+
budgetBurst: env.RUN_OPS_DATABASE_TRANSACTION_START_RETRY_BUDGET_BURST,
65+
});
66+
67+
export const runOpsLegacyTransactionResilience = resolveTransactionResilience("run-ops-legacy", {
68+
maxWaitMs: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_MAX_WAIT_MS,
69+
enabled: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_ENABLED,
70+
maxAttempts: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_MAX_ATTEMPTS,
71+
backoffMinMs: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_BACKOFF_MIN_MS,
72+
backoffMaxMs: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_BACKOFF_MAX_MS,
73+
budgetPerSec: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_BUDGET_PER_SEC,
74+
budgetBurst: env.RUN_OPS_LEGACY_DATABASE_TRANSACTION_START_RETRY_BUDGET_BURST,
75+
});

0 commit comments

Comments
 (0)