Skip to content

Commit f08eefc

Browse files
committed
feat(references): add stress-tasks reference project for trigger fan-out repro
1 parent 2e2fff2 commit f08eefc

7 files changed

Lines changed: 480 additions & 1 deletion

File tree

apps/webapp/seed.mts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,35 @@ async function seed() {
6767
name: "realtime-streams",
6868
externalRef: "proj_klxlzjnzxmbgiwuuwhvb",
6969
},
70+
{
71+
name: "stress-tasks",
72+
externalRef: "proj_stresstaskslocaldevx",
73+
// Stress-tasks fan-outs need a much higher concurrency ceiling than the
74+
// default 300 — at 1000+ children per parent, runs would otherwise queue
75+
// and the local repro wouldn't track the production fan-out signature.
76+
environmentConcurrencyLimit: 25000,
77+
},
7078
];
7179

7280
// Create or find each project
7381
for (const projectConfig of referenceProjects) {
74-
await findOrCreateProject(projectConfig.name, organization, user.id, projectConfig.externalRef);
82+
const result = await findOrCreateProject(
83+
projectConfig.name,
84+
organization,
85+
user.id,
86+
projectConfig.externalRef,
87+
);
88+
89+
if (projectConfig.environmentConcurrencyLimit) {
90+
const updated = await prisma.runtimeEnvironment.updateMany({
91+
where: { projectId: result.project.id },
92+
data: { maximumConcurrencyLimit: projectConfig.environmentConcurrencyLimit },
93+
});
94+
console.log(
95+
` Updated ${updated.count} environment(s) on ${projectConfig.name} ` +
96+
`to maximumConcurrencyLimit=${projectConfig.environmentConcurrencyLimit}`,
97+
);
98+
}
7599
}
76100

77101
await createBatchLimitOrgs(user);

pnpm-lock.yaml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Stress-tasks — example payloads
2+
3+
Copy any of these into the dashboard test UI (Tasks → pick the task → Test).
4+
The trigger.dev test UI defaults to the most recent run's payload, so once
5+
you've fired a particular shape once, it'll be remembered.
6+
7+
## `stress-fan-out-trigger` — N individual `.trigger()` calls in a single trace
8+
9+
Mirrors the production failure mode (events 1–10 in
10+
`prisma-connection-investigation-results.md`) where one trace fans out N HTTP
11+
triggers and exhausts the api-prod Prisma connection pool.
12+
13+
### Smoke test (use this first to confirm wiring)
14+
15+
```json
16+
{ "count": 10 }
17+
```
18+
19+
### Reproduce the prod fan-out — 1,000 all at once
20+
21+
```json
22+
{ "count": 1000 }
23+
```
24+
25+
### Bounded producer — only 100 in-flight at a time
26+
27+
```json
28+
{ "count": 1000, "concurrency": 100 }
29+
```
30+
31+
### Exercise the `runTags ||` row-lock contention path (events 3, 4, 5, 7)
32+
33+
```json
34+
{ "count": 1000, "tags": ["stress-test", "burst-2026-05-08"] }
35+
```
36+
37+
### Children doing real work — 500 triggers, 2 s child sleep, 200 in flight
38+
39+
```json
40+
{ "count": 500, "concurrency": 200, "childSleepMs": 2000 }
41+
```
42+
43+
### Large payloads — 200 triggers, 50 KB pad each
44+
45+
```json
46+
{ "count": 200, "childPayloadBytes": 50000 }
47+
```
48+
49+
### Combined contention — fan-out + tags + child work
50+
51+
```json
52+
{ "count": 1000, "concurrency": 250, "childSleepMs": 500, "tags": ["combined"] }
53+
```
54+
55+
---
56+
57+
## `stress-fan-out-batch` — N triggers via chunked `batchTrigger`
58+
59+
Different server-side code path: one HTTP request per chunk, server-side
60+
bulk insert. Useful contrast for understanding whether pool pressure is
61+
specific to the N-trigger path or surfaces here too.
62+
63+
### Smoke test
64+
65+
```json
66+
{ "count": 10, "batchSize": 10 }
67+
```
68+
69+
### Default — 1,000 across two sequential 500-payload batches
70+
71+
```json
72+
{ "count": 1000 }
73+
```
74+
75+
### Parallel batches — same volume, two batchTrigger calls in flight
76+
77+
```json
78+
{ "count": 1000, "chunkConcurrency": 2 }
79+
```
80+
81+
### Many small batches — 100 chunks of 10, sequential
82+
83+
```json
84+
{ "count": 1000, "batchSize": 10 }
85+
```
86+
87+
### Many small batches in parallel — 100 chunks of 10, 8 in flight
88+
89+
```json
90+
{ "count": 1000, "batchSize": 10, "chunkConcurrency": 8 }
91+
```
92+
93+
### With tags — exercise `runTags ||` contention via the batch path
94+
95+
```json
96+
{ "count": 1000, "tags": ["stress-batch"] }
97+
```
98+
99+
### Children doing real work
100+
101+
```json
102+
{ "count": 500, "batchSize": 100, "chunkConcurrency": 5, "childSleepMs": 2000 }
103+
```
104+
105+
---
106+
107+
## What to watch while these run
108+
109+
- **Axiom** (`['trigger-cloud-prod']` equivalent locally — wherever your local
110+
OTel goes): `prisma:engine:connection` span durations on `trigger-api-prod`
111+
/ engine. Baseline is sub-millisecond; > 100 ms is the early signal.
112+
- **Webapp logs**: P2024 ("Timed out fetching a new connection from the
113+
connection pool") and P1001 ("Can't reach database server") surfaces during
114+
the burst.
115+
- **Postgres** (`docker exec database psql -U postgres -d postgres`):
116+
`SELECT count(*) FROM pg_stat_activity;` — connection count under load.
117+
- **Run dashboard**: how many runs queued vs. executing vs. failed; the spread
118+
is what tells you whether the producer-side bottleneck (trigger plumbing)
119+
or the consumer-side bottleneck (worker concurrency) was hit first.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "references-stress-tasks",
3+
"private": true,
4+
"type": "module",
5+
"devDependencies": {
6+
"trigger.dev": "workspace:*"
7+
},
8+
"dependencies": {
9+
"@trigger.dev/build": "workspace:*",
10+
"@trigger.dev/sdk": "workspace:*",
11+
"zod": "3.25.76"
12+
},
13+
"scripts": {
14+
"dev": "trigger dev",
15+
"deploy": "trigger deploy"
16+
}
17+
}

0 commit comments

Comments
 (0)