@@ -200,11 +200,11 @@ async function measureOperation({ name, operation, iterations, skipWarmup = fals
200200/**
201201 * Benchmark: Object Create
202202 */
203- async function benchmarkObjectCreate ( ) {
203+ async function benchmarkObjectCreate ( name ) {
204204 let counter = 0 ;
205205
206206 return measureOperation ( {
207- name : 'Object Create' ,
207+ name,
208208 iterations : 1_000 ,
209209 operation : async ( ) => {
210210 const TestObject = Parse . Object . extend ( 'BenchmarkTest' ) ;
@@ -220,7 +220,7 @@ async function benchmarkObjectCreate() {
220220/**
221221 * Benchmark: Object Read (by ID)
222222 */
223- async function benchmarkObjectRead ( ) {
223+ async function benchmarkObjectRead ( name ) {
224224 // Setup: Create test objects
225225 const TestObject = Parse . Object . extend ( 'BenchmarkTest' ) ;
226226 const objects = [ ] ;
@@ -236,7 +236,7 @@ async function benchmarkObjectRead() {
236236 let counter = 0 ;
237237
238238 return measureOperation ( {
239- name : 'Object Read' ,
239+ name,
240240 iterations : 1_000 ,
241241 operation : async ( ) => {
242242 const query = new Parse . Query ( 'BenchmarkTest' ) ;
@@ -248,7 +248,7 @@ async function benchmarkObjectRead() {
248248/**
249249 * Benchmark: Object Update
250250 */
251- async function benchmarkObjectUpdate ( ) {
251+ async function benchmarkObjectUpdate ( name ) {
252252 // Setup: Create test objects
253253 const TestObject = Parse . Object . extend ( 'BenchmarkTest' ) ;
254254 const objects = [ ] ;
@@ -265,7 +265,7 @@ async function benchmarkObjectUpdate() {
265265 let counter = 0 ;
266266
267267 return measureOperation ( {
268- name : 'Object Update' ,
268+ name,
269269 iterations : 1_000 ,
270270 operation : async ( ) => {
271271 const obj = objects [ counter ++ % objects . length ] ;
@@ -279,7 +279,7 @@ async function benchmarkObjectUpdate() {
279279/**
280280 * Benchmark: Simple Query
281281 */
282- async function benchmarkSimpleQuery ( ) {
282+ async function benchmarkSimpleQuery ( name ) {
283283 // Setup: Create test data
284284 const TestObject = Parse . Object . extend ( 'BenchmarkTest' ) ;
285285 const objects = [ ] ;
@@ -296,7 +296,7 @@ async function benchmarkSimpleQuery() {
296296 let counter = 0 ;
297297
298298 return measureOperation ( {
299- name : 'Simple Query' ,
299+ name,
300300 iterations : 1_000 ,
301301 operation : async ( ) => {
302302 const query = new Parse . Query ( 'BenchmarkTest' ) ;
@@ -309,11 +309,11 @@ async function benchmarkSimpleQuery() {
309309/**
310310 * Benchmark: Batch Save (saveAll)
311311 */
312- async function benchmarkBatchSave ( ) {
312+ async function benchmarkBatchSave ( name ) {
313313 const BATCH_SIZE = 10 ;
314314
315315 return measureOperation ( {
316- name : 'Batch Save (10 objects)' ,
316+ name,
317317 iterations : 1_000 ,
318318 operation : async ( ) => {
319319 const TestObject = Parse . Object . extend ( 'BenchmarkTest' ) ;
@@ -334,11 +334,11 @@ async function benchmarkBatchSave() {
334334/**
335335 * Benchmark: User Signup
336336 */
337- async function benchmarkUserSignup ( ) {
337+ async function benchmarkUserSignup ( name ) {
338338 let counter = 0 ;
339339
340340 return measureOperation ( {
341- name : 'User Signup' ,
341+ name,
342342 iterations : 500 ,
343343 operation : async ( ) => {
344344 counter ++ ;
@@ -354,7 +354,7 @@ async function benchmarkUserSignup() {
354354/**
355355 * Benchmark: User Login
356356 */
357- async function benchmarkUserLogin ( ) {
357+ async function benchmarkUserLogin ( name ) {
358358 // Setup: Create test users
359359 const users = [ ] ;
360360
@@ -371,7 +371,7 @@ async function benchmarkUserLogin() {
371371 let counter = 0 ;
372372
373373 return measureOperation ( {
374- name : 'User Login' ,
374+ name,
375375 iterations : 500 ,
376376 operation : async ( ) => {
377377 const userCreds = users [ counter ++ % users . length ] ;
@@ -382,52 +382,146 @@ async function benchmarkUserLogin() {
382382}
383383
384384/**
385- * Benchmark: Query with Include (Parallel Include Pointers)
385+ * Benchmark: Query with Include (Parallel Pointers)
386+ * Tests the performance improvement when fetching multiple pointers at the same level.
386387 */
387- async function benchmarkQueryWithInclude ( ) {
388- // Setup: Create nested object hierarchy
388+ async function benchmarkQueryWithIncludeParallel ( name ) {
389+ const PointerAClass = Parse . Object . extend ( 'PointerA' ) ;
390+ const PointerBClass = Parse . Object . extend ( 'PointerB' ) ;
391+ const PointerCClass = Parse . Object . extend ( 'PointerC' ) ;
392+ const RootClass = Parse . Object . extend ( 'Root' ) ;
393+
394+ // Create pointer objects
395+ const pointerAObjects = [ ] ;
396+ for ( let i = 0 ; i < 10 ; i ++ ) {
397+ const obj = new PointerAClass ( ) ;
398+ obj . set ( 'name' , `pointerA-${ i } ` ) ;
399+ pointerAObjects . push ( obj ) ;
400+ }
401+ await Parse . Object . saveAll ( pointerAObjects ) ;
402+
403+ const pointerBObjects = [ ] ;
404+ for ( let i = 0 ; i < 10 ; i ++ ) {
405+ const obj = new PointerBClass ( ) ;
406+ obj . set ( 'name' , `pointerB-${ i } ` ) ;
407+ pointerBObjects . push ( obj ) ;
408+ }
409+ await Parse . Object . saveAll ( pointerBObjects ) ;
410+
411+ const pointerCObjects = [ ] ;
412+ for ( let i = 0 ; i < 10 ; i ++ ) {
413+ const obj = new PointerCClass ( ) ;
414+ obj . set ( 'name' , `pointerC-${ i } ` ) ;
415+ pointerCObjects . push ( obj ) ;
416+ }
417+ await Parse . Object . saveAll ( pointerCObjects ) ;
418+
419+ // Create Root objects with multiple pointers at the same level
420+ const rootObjects = [ ] ;
421+ for ( let i = 0 ; i < 10 ; i ++ ) {
422+ const obj = new RootClass ( ) ;
423+ obj . set ( 'name' , `root-${ i } ` ) ;
424+ obj . set ( 'pointerA' , pointerAObjects [ i % pointerAObjects . length ] ) ;
425+ obj . set ( 'pointerB' , pointerBObjects [ i % pointerBObjects . length ] ) ;
426+ obj . set ( 'pointerC' , pointerCObjects [ i % pointerCObjects . length ] ) ;
427+ rootObjects . push ( obj ) ;
428+ }
429+ await Parse . Object . saveAll ( rootObjects ) ;
430+
431+ return measureOperation ( {
432+ name,
433+ skipWarmup : true ,
434+ dbLatency : 100 ,
435+ iterations : 100 ,
436+ operation : async ( ) => {
437+ const query = new Parse . Query ( 'Root' ) ;
438+ // Include multiple pointers at the same level - should fetch in parallel
439+ query . include ( [ 'pointerA' , 'pointerB' , 'pointerC' ] ) ;
440+ await query . find ( ) ;
441+ } ,
442+ } ) ;
443+ }
444+
445+ /**
446+ * Benchmark: Query with Include (Nested Pointers with Parallel Leaf Nodes)
447+ * Tests the PR's optimization for parallel fetching at each nested level.
448+ * Pattern: p1.p2.p3, p1.p2.p4, p1.p2.p5
449+ * After fetching p2, we know the objectIds and can fetch p3, p4, p5 in parallel.
450+ */
451+ async function benchmarkQueryWithIncludeNested ( name ) {
452+ const Level3AClass = Parse . Object . extend ( 'Level3A' ) ;
453+ const Level3BClass = Parse . Object . extend ( 'Level3B' ) ;
454+ const Level3CClass = Parse . Object . extend ( 'Level3C' ) ;
389455 const Level2Class = Parse . Object . extend ( 'Level2' ) ;
390456 const Level1Class = Parse . Object . extend ( 'Level1' ) ;
391457 const RootClass = Parse . Object . extend ( 'Root' ) ;
392458
459+ // Create Level3 objects (leaf nodes)
460+ const level3AObjects = [ ] ;
461+ for ( let i = 0 ; i < 10 ; i ++ ) {
462+ const obj = new Level3AClass ( ) ;
463+ obj . set ( 'name' , `level3A-${ i } ` ) ;
464+ level3AObjects . push ( obj ) ;
465+ }
466+ await Parse . Object . saveAll ( level3AObjects ) ;
467+
468+ const level3BObjects = [ ] ;
469+ for ( let i = 0 ; i < 10 ; i ++ ) {
470+ const obj = new Level3BClass ( ) ;
471+ obj . set ( 'name' , `level3B-${ i } ` ) ;
472+ level3BObjects . push ( obj ) ;
473+ }
474+ await Parse . Object . saveAll ( level3BObjects ) ;
475+
476+ const level3CObjects = [ ] ;
477+ for ( let i = 0 ; i < 10 ; i ++ ) {
478+ const obj = new Level3CClass ( ) ;
479+ obj . set ( 'name' , `level3C-${ i } ` ) ;
480+ level3CObjects . push ( obj ) ;
481+ }
482+ await Parse . Object . saveAll ( level3CObjects ) ;
483+
484+ // Create Level2 objects pointing to multiple Level3 objects
485+ const level2Objects = [ ] ;
486+ for ( let i = 0 ; i < 10 ; i ++ ) {
487+ const obj = new Level2Class ( ) ;
488+ obj . set ( 'name' , `level2-${ i } ` ) ;
489+ obj . set ( 'level3A' , level3AObjects [ i % level3AObjects . length ] ) ;
490+ obj . set ( 'level3B' , level3BObjects [ i % level3BObjects . length ] ) ;
491+ obj . set ( 'level3C' , level3CObjects [ i % level3CObjects . length ] ) ;
492+ level2Objects . push ( obj ) ;
493+ }
494+ await Parse . Object . saveAll ( level2Objects ) ;
495+
496+ // Create Level1 objects pointing to Level2
497+ const level1Objects = [ ] ;
498+ for ( let i = 0 ; i < 10 ; i ++ ) {
499+ const obj = new Level1Class ( ) ;
500+ obj . set ( 'name' , `level1-${ i } ` ) ;
501+ obj . set ( 'level2' , level2Objects [ i % level2Objects . length ] ) ;
502+ level1Objects . push ( obj ) ;
503+ }
504+ await Parse . Object . saveAll ( level1Objects ) ;
505+
506+ // Create Root objects pointing to Level1
507+ const rootObjects = [ ] ;
508+ for ( let i = 0 ; i < 10 ; i ++ ) {
509+ const obj = new RootClass ( ) ;
510+ obj . set ( 'name' , `root-${ i } ` ) ;
511+ obj . set ( 'level1' , level1Objects [ i % level1Objects . length ] ) ;
512+ rootObjects . push ( obj ) ;
513+ }
514+ await Parse . Object . saveAll ( rootObjects ) ;
515+
393516 return measureOperation ( {
394- name : 'Query with Include (2 levels)' ,
517+ name,
395518 skipWarmup : true ,
396- dbLatency : 50 ,
519+ dbLatency : 100 ,
397520 iterations : 100 ,
398521 operation : async ( ) => {
399- // Create 10 Level2 objects
400- const level2Objects = [ ] ;
401- for ( let i = 0 ; i < 10 ; i ++ ) {
402- const obj = new Level2Class ( ) ;
403- obj . set ( 'name' , `level2-${ i } ` ) ;
404- obj . set ( 'value' , i ) ;
405- level2Objects . push ( obj ) ;
406- }
407- await Parse . Object . saveAll ( level2Objects ) ;
408-
409- // Create 10 Level1 objects, each pointing to a Level2 object
410- const level1Objects = [ ] ;
411- for ( let i = 0 ; i < 10 ; i ++ ) {
412- const obj = new Level1Class ( ) ;
413- obj . set ( 'name' , `level1-${ i } ` ) ;
414- obj . set ( 'level2' , level2Objects [ i % level2Objects . length ] ) ;
415- level1Objects . push ( obj ) ;
416- }
417- await Parse . Object . saveAll ( level1Objects ) ;
418-
419- // Create 10 Root objects, each pointing to a Level1 object
420- const rootObjects = [ ] ;
421- for ( let i = 0 ; i < 10 ; i ++ ) {
422- const obj = new RootClass ( ) ;
423- obj . set ( 'name' , `root-${ i } ` ) ;
424- obj . set ( 'level1' , level1Objects [ i % level1Objects . length ] ) ;
425- rootObjects . push ( obj ) ;
426- }
427- await Parse . Object . saveAll ( rootObjects ) ;
428-
429522 const query = new Parse . Query ( 'Root' ) ;
430- query . include ( 'level1.level2' ) ;
523+ // After fetching level1.level2, the PR should fetch level3A, level3B, level3C in parallel
524+ query . include ( [ 'level1.level2.level3A' , 'level1.level2.level3B' , 'level1.level2.level3C' ] ) ;
431525 await query . find ( ) ;
432526 } ,
433527 } ) ;
@@ -453,22 +547,23 @@ async function runBenchmarks() {
453547
454548 // Define all benchmarks to run
455549 const benchmarks = [
456- { name : 'Object Create' , fn : benchmarkObjectCreate } ,
457- { name : 'Object Read' , fn : benchmarkObjectRead } ,
458- { name : 'Object Update' , fn : benchmarkObjectUpdate } ,
459- { name : 'Simple Query' , fn : benchmarkSimpleQuery } ,
460- { name : 'Batch Save' , fn : benchmarkBatchSave } ,
461- { name : 'User Signup' , fn : benchmarkUserSignup } ,
462- { name : 'User Login' , fn : benchmarkUserLogin } ,
463- { name : 'Query with Include' , fn : benchmarkQueryWithInclude } ,
550+ { name : 'Object.save (create)' , fn : benchmarkObjectCreate } ,
551+ { name : 'Object.save (update)' , fn : benchmarkObjectUpdate } ,
552+ { name : 'Object.saveAll (batch save)' , fn : benchmarkBatchSave } ,
553+ { name : 'Query.get (by objectId)' , fn : benchmarkObjectRead } ,
554+ { name : 'Query.find (simple query)' , fn : benchmarkSimpleQuery } ,
555+ { name : 'User.signUp' , fn : benchmarkUserSignup } ,
556+ { name : 'User.login' , fn : benchmarkUserLogin } ,
557+ { name : 'Query.include (parallel pointers)' , fn : benchmarkQueryWithIncludeParallel } ,
558+ { name : 'Query.include (nested pointers)' , fn : benchmarkQueryWithIncludeNested } ,
464559 ] ;
465560
466561 // Run each benchmark with database cleanup
467562 for ( const benchmark of benchmarks ) {
468563 logInfo ( `\nRunning benchmark '${ benchmark . name } '...` ) ;
469564 resetParseServer ( ) ;
470565 await cleanupDatabase ( ) ;
471- results . push ( await benchmark . fn ( ) ) ;
566+ results . push ( await benchmark . fn ( benchmark . name ) ) ;
472567 }
473568
474569 // Output results in github-action-benchmark format (stdout)
0 commit comments