@@ -358,21 +358,50 @@ async fn listing_ignores_build_history(
358358 ) ;
359359}
360360
361- /// Three thousand built nodes, half in a named database, are read in batches and keep their own builds .
361+ /// Three thousand built nodes, half in a named database, keep their own builds, and listing them grows linearly .
362362async fn listing_reads_wide_targets_in_batches (
363363 store : & ControlStore ,
364364 credentials : ( & AuditContext , Uuid ) ,
365365 ( location, mut input) : ( Uuid , SnapshotInput ) ,
366366) {
367367 let names: Vec < String > = ( 0 ..3_000 ) . map ( |index| format ! ( "wide_{index:04}" ) ) . collect ( ) ;
368+ let mut medians: Vec < std:: time:: Duration > = Vec :: new ( ) ;
369+ for ( first, size) in [ ( 0 , 1_000 ) , ( 1_000 , names. len ( ) ) ] {
370+ medians. push (
371+ list_wide_target (
372+ store,
373+ credentials,
374+ ( location, & mut input) ,
375+ ( & names, first, size) ,
376+ )
377+ . await ,
378+ ) ;
379+ }
380+ let ratio: f64 = medians[ 1 ] . as_secs_f64 ( ) / medians[ 0 ] . as_secs_f64 ( ) ;
381+ assert ! (
382+ ratio < 6.0 ,
383+ "listing {} nodes took {:?}, {ratio:.1} times the {:?} for 1,000; listing must stay linear" ,
384+ names. len( ) ,
385+ medians[ 1 ] ,
386+ medians[ 0 ]
387+ ) ;
388+ }
389+
390+ /// Add `names[first..size]` with one build each and return the median time to list the first `size`.
391+ async fn list_wide_target (
392+ store : & ControlStore ,
393+ credentials : ( & AuditContext , Uuid ) ,
394+ ( location, input) : ( Uuid , & mut SnapshotInput ) ,
395+ ( names, first, size) : ( & [ String ] , usize , usize ) ,
396+ ) -> std:: time:: Duration {
368397 let database = |index : usize | ( index % 2 == 1 ) . then ( || "analytics_db" . to_owned ( ) ) ;
369398 let built_at = |index : usize | {
370399 chrono:: DateTime :: parse_from_rfc3339 ( "2026-02-01T00:00:00Z" )
371400 . expect ( "build start" )
372401 . to_utc ( )
373402 + chrono:: Duration :: seconds ( i64:: try_from ( index) . expect ( "seconds" ) )
374403 } ;
375- for ( index, name) in names. iter ( ) . enumerate ( ) {
404+ for ( index, name) in names. iter ( ) . enumerate ( ) . take ( size ) . skip ( first ) {
376405 input. nodes . insert ( format ! ( "model:{name}" ) , SnapshotNode {
377406 kind : "model" . into ( ) ,
378407 definition : json ! ( { "name" : name, "destination" : { "database" : database( index) , "schema" : "main" , "name" : name} } ) ,
@@ -382,9 +411,7 @@ async fn listing_reads_wide_targets_in_batches(
382411 } ) ;
383412 }
384413 input. generation += 1 ;
385- publish_snapshot ( store, & input)
386- . await
387- . expect ( "wide snapshot" ) ;
414+ publish_snapshot ( store, input) . await . expect ( "wide snapshot" ) ;
388415 let transaction = begin ( store, TransactionMode :: Write ) . await . expect ( "begin" ) ;
389416 let ( mut transaction, rows) = query (
390417 transaction,
@@ -396,11 +423,11 @@ async fn listing_reads_wide_targets_in_batches(
396423 . await
397424 . expect ( "template build" ) ;
398425 let template: Uuid = serde_json:: from_value ( rows[ 0 ] [ "event_id" ] . clone ( ) ) . expect ( "event id" ) ;
399- for ( chunk_index, chunk) in names. chunks ( 300 ) . enumerate ( ) {
426+ for ( chunk_index, chunk) in names[ first..size ] . chunks ( 300 ) . enumerate ( ) {
400427 let mut parameters = vec ! [ Parameter :: Id ( template) ] ;
401428 let mut values: Vec < String > = Vec :: new ( ) ;
402429 for ( offset, name) in chunk. iter ( ) . enumerate ( ) {
403- let index = chunk_index * 300 + offset;
430+ let index = first + chunk_index * 300 + offset;
404431 values. push ( format ! (
405432 "(${},${},${},${},${})" ,
406433 parameters. len( ) + 1 ,
@@ -448,7 +475,7 @@ async fn listing_reads_wide_targets_in_batches(
448475 . iter ( )
449476 . map ( |node| ( node. node_key . as_str ( ) , node. last_build . as_ref ( ) ) )
450477 . collect ( ) ;
451- let built = names
478+ let built = names[ ..size ]
452479 . iter ( )
453480 . enumerate ( )
454481 . filter ( |( index, name) | {
@@ -462,13 +489,8 @@ async fn listing_reads_wide_targets_in_batches(
462489 } )
463490 } )
464491 . count ( ) ;
465- assert_eq ! ( built, names . len ( ) , "every wide node keeps its own build" ) ;
492+ assert_eq ! ( built, size , "every wide node keeps its own build" ) ;
466493 }
467494 timings. sort ( ) ;
468- assert ! (
469- timings[ 1 ] < std:: time:: Duration :: from_millis( 450 ) ,
470- "listing {} nodes took {:?}" ,
471- names. len( ) ,
472- timings[ 1 ]
473- ) ;
495+ timings[ 1 ]
474496}
0 commit comments