88 * `COUNT(*) ... WHERE status='PENDING'`, over the same population. Like for like.
99 * 2. Read amplification — the store's `readBlockState` against a full-payload `SELECT`
1010 * of the same waitpoints. Like for like.
11- * 3. Store-only write paths — block+complete+deliver and K-watcher fan-out. Absolute
12- * numbers with NO Postgres counterpart: no single statement on the previous path
13- * corresponds to a Redis round trip that both blocks a run and delivers to watchers.
11+ * 3. Store-only write paths — block+complete+drain, then foreground completion versus
12+ * worker drain at several fan-out widths. Absolute numbers with NO Postgres
13+ * counterpart: no single statement on the previous path corresponds to a Redis round
14+ * trip that both blocks a run and delivers to watchers. The width sweep is the
15+ * evidence for the flat-foreground claim — completion should not track K, the drain
16+ * should.
1417 * 4. Register cost versus edge count — `registerBlocks` registers each edge with its own
1518 * round trip before the single absorb. This measures whether that serial loop is a
1619 * real cost at a wide fan-in, or a non-issue, at several fan-in widths.
1922 * empty table measures nothing.
2023 *
2124 * Knobs: BENCH_WP_ITERATIONS, BENCH_WP_FANIN, BENCH_WP_WATCHERS, BENCH_WP_REGISTER_WIDTHS,
22- * BENCH_WP_REGISTER_SAMPLES.
25+ * BENCH_WP_REGISTER_SAMPLES, BENCH_WP_FANOUT_WIDTHS .
2326 */
2427import { containerTest } from "@internal/testcontainers" ;
2528import type { PrismaClient } from "@trigger.dev/database" ;
29+ import { WaitpointFanoutWorker } from "../waitpointCoordinator/fanoutWorker.js" ;
2630import {
2731 WaitpointStoreCoordinator ,
2832 type BlockEdge ,
@@ -40,6 +44,13 @@ const REGISTER_WIDTHS = (process.env.BENCH_WP_REGISTER_WIDTHS ?? "1,10,100,1001"
4044 . map ( ( raw ) => Number ( raw . trim ( ) ) )
4145 . filter ( ( width ) => Number . isFinite ( width ) && width > 0 ) ;
4246const REGISTER_SAMPLES = Number ( process . env . BENCH_WP_REGISTER_SAMPLES ?? 20 ) ;
47+ const FANOUT_WIDTHS = ( process . env . BENCH_WP_FANOUT_WIDTHS ?? `1,10,100,${ WATCHERS } ` )
48+ . split ( "," )
49+ . map ( ( raw ) => Number ( raw . trim ( ) ) )
50+ . filter (
51+ ( width , index , all ) => Number . isFinite ( width ) && width > 0 && all . indexOf ( width ) === index
52+ )
53+ . sort ( ( a , b ) => a - b ) ;
4354const NOW = new Date ( ) . toISOString ( ) ;
4455
4556type Sample = { label : string ; count : number ; p50 : number ; p99 : number ; totalMs : number } ;
@@ -121,6 +132,7 @@ containerTest(
121132 async ( { prisma, redisOptions } ) => {
122133 const env = await setupAuthenticatedEnvironment ( prisma , "PRODUCTION" ) ;
123134 const store = new WaitpointStoreCoordinator ( { redisOptions } ) ;
135+ const worker = new WaitpointFanoutWorker ( { coordinator : store , enabled : true } ) ;
124136 const samples : Sample [ ] = [ ] ;
125137 const registerCost : Array < {
126138 width : number ;
@@ -143,13 +155,18 @@ containerTest(
143155 }
144156 await store . registerBlocks ( {
145157 runId : "bench_run_fanin" ,
158+ blockId : "bench_blk_fanin" ,
146159 edges : ids . map ( ( id , index ) => edge ( id , index ) ) ,
147160 } ) ;
148161
149162 // --- group 1: the pending-count gate, like for like ---
150163 samples . push (
151164 await measure ( "store.pendingCount" , ITERATIONS , async ( ) => {
152- await store . absorbBlockers ( { runId : "bench_run_fanin" , edges : [ ] } ) ;
165+ await store . absorbBlockers ( {
166+ runId : "bench_run_fanin" ,
167+ blockId : "bench_blk_fanin" ,
168+ edges : [ ] ,
169+ } ) ;
153170 } )
154171 ) ;
155172 samples . push (
@@ -179,40 +196,50 @@ containerTest(
179196 record : record ( id , env . id , env . project . id ) ,
180197 status : "PENDING" ,
181198 } ) ;
182- await store . registerBlocks ( { runId : `bench_run_${ i } ` , edges : [ edge ( id ) ] } ) ;
183- const done = await store . complete ( { waitpointId : id , completion } ) ;
184- for ( const watcher of done . watchers ) {
185- await store . deliverCompletion ( {
186- runId : watcher . runId ,
187- waitpointId : id ,
188- completion : done . completion ! ,
189- } ) ;
190- }
199+ await store . registerBlocks ( {
200+ runId : `bench_run_${ i } ` ,
201+ blockId : `bench_blk_${ i } ` ,
202+ edges : [ edge ( id ) ] ,
203+ } ) ;
204+ await store . complete ( { waitpointId : id , completion } ) ;
205+ await worker . visit ( id ) ;
191206 } )
192207 ) ;
193208
194- const fanOutId = "bench_fanout_w" ;
195- await store . createIfAbsent ( {
196- record : record ( fanOutId , env . id , env . project . id ) ,
197- status : "PENDING" ,
198- } ) ;
199- for ( let i = 0 ; i < WATCHERS ; i ++ ) {
200- await store . registerBlocks ( { runId : `bench_watcher_${ i } ` , edges : [ edge ( fanOutId ) ] } ) ;
209+ // Foreground completion versus watcher count. The claim under test is that the
210+ // first number is FLAT in K while the second grows with it — that is what "bounded
211+ // foreground completion" means in practice, and a single width could not show it.
212+ for ( const width of FANOUT_WIDTHS ) {
213+ const fanOutId = `bench_fanout_w_${ width } ` ;
214+ await store . createIfAbsent ( {
215+ record : record ( fanOutId , env . id , env . project . id ) ,
216+ status : "PENDING" ,
217+ } ) ;
218+ for ( let i = 0 ; i < width ; i ++ ) {
219+ await store . registerBlocks ( {
220+ runId : `bench_watcher_${ width } _${ i } ` ,
221+ blockId : `bench_blk_watcher_${ width } _${ i } ` ,
222+ edges : [ edge ( fanOutId ) ] ,
223+ } ) ;
224+ }
225+
226+ samples . push (
227+ await measure ( `store.complete(foreground, watchers=${ width } )` , 1 , async ( ) => {
228+ await store . complete ( { waitpointId : fanOutId , completion } ) ;
229+ } )
230+ ) ;
231+ samples . push (
232+ await measure ( `worker.drain(watchers=${ width } )` , 1 , async ( ) => {
233+ let visits = 0 ;
234+ // The visit page budget is bounded, so a wide fan-out takes several visits.
235+ for ( ; ; ) {
236+ const summary = await worker . visit ( fanOutId ) ;
237+ visits ++ ;
238+ if ( summary . outcome !== "more" || visits > 1_000 ) break ;
239+ }
240+ } )
241+ ) ;
201242 }
202- samples . push (
203- await measure ( `store.complete+deliver(watchers=${ WATCHERS } )` , 1 , async ( ) => {
204- const done = await store . complete ( { waitpointId : fanOutId , completion } ) ;
205- // Serial on purpose: this is the worst case, and it is the number that says
206- // whether delivery needs to pipeline.
207- for ( const watcher of done . watchers ) {
208- await store . deliverCompletion ( {
209- runId : watcher . runId ,
210- waitpointId : fanOutId ,
211- completion : done . completion ! ,
212- } ) ;
213- }
214- } )
215- ) ;
216243
217244 // --- group 4: register cost versus edge count ---
218245 // registerBlocks registers each edge with its own round trip, serially, before the
@@ -236,7 +263,12 @@ containerTest(
236263 `store.registerBlocks(edges=${ width } )` ,
237264 REGISTER_SAMPLES ,
238265 async ( ) => {
239- await store . registerBlocks ( { runId : `bench_register_${ width } _${ call ++ } ` , edges } ) ;
266+ const n = call ++ ;
267+ await store . registerBlocks ( {
268+ runId : `bench_register_${ width } _${ n } ` ,
269+ blockId : `bench_blk_register_${ width } _${ n } ` ,
270+ edges,
271+ } ) ;
240272 }
241273 ) ;
242274 samples . push ( sample ) ;
0 commit comments