@@ -24,6 +24,35 @@ const ITERATIONS = parseInt(process.env.BENCHMARK_ITERATIONS || '10000', 10);
2424// Parse Server instance
2525let parseServer ;
2626let mongoClient ;
27+ let proxyProcess ;
28+ let proxyServerCleanup ;
29+
30+ /**
31+ * Start MongoDB proxy with artificial latency
32+ */
33+ async function startProxy ( ) {
34+ const { spawn } = require ( 'child_process' ) ;
35+
36+ proxyProcess = spawn ( 'node' , [ 'benchmark/db-proxy.js' ] , {
37+ env : { ...process . env , PROXY_PORT : '27018' , TARGET_PORT : '27017' , LATENCY_MS : '10000' } ,
38+ stdio : 'inherit' ,
39+ } ) ;
40+
41+ // Wait for proxy to start
42+ await new Promise ( resolve => setTimeout ( resolve , 2000 ) ) ;
43+ console . log ( 'MongoDB proxy started on port 27018 with 10ms latency' ) ;
44+ }
45+
46+ /**
47+ * Stop MongoDB proxy
48+ */
49+ async function stopProxy ( ) {
50+ if ( proxyProcess ) {
51+ proxyProcess . kill ( ) ;
52+ await new Promise ( resolve => setTimeout ( resolve , 500 ) ) ;
53+ console . log ( 'MongoDB proxy stopped' ) ;
54+ }
55+ }
2756
2857/**
2958 * Initialize Parse Server for benchmarking
@@ -86,6 +115,66 @@ async function cleanupDatabase() {
86115 }
87116}
88117
118+ /**
119+ * Reset Parse SDK to use the default server
120+ */
121+ function resetParseServer ( ) {
122+ Parse . serverURL = SERVER_URL ;
123+ }
124+
125+ /**
126+ * Start a Parse Server instance using the DB proxy for latency simulation
127+ * Stores cleanup function globally for later use
128+ */
129+ async function useProxyServer ( ) {
130+ const express = require ( 'express' ) ;
131+ const { default : ParseServer } = require ( '../lib/index.js' ) ;
132+
133+ // Create a new Parse Server instance using the proxy
134+ const app = express ( ) ;
135+ const proxyParseServer = new ParseServer ( {
136+ databaseURI : 'mongodb://localhost:27018/parse_benchmark_test' ,
137+ appId : APP_ID ,
138+ masterKey : MASTER_KEY ,
139+ serverURL : 'http://localhost:1338/parse' ,
140+ silent : true ,
141+ allowClientClassCreation : true ,
142+ logLevel : 'error' ,
143+ verbose : false ,
144+ } ) ;
145+
146+ app . use ( '/parse' , proxyParseServer . app ) ;
147+
148+ const server = await new Promise ( ( resolve , reject ) => {
149+ const s = app . listen ( 1338 , ( err ) => {
150+ if ( err ) {
151+ reject ( err ) ;
152+ } else {
153+ resolve ( s ) ;
154+ }
155+ } ) ;
156+ } ) ;
157+
158+ // Configure Parse SDK to use the proxy server
159+ Parse . serverURL = 'http://localhost:1338/parse' ;
160+
161+ // Store cleanup function globally
162+ proxyServerCleanup = async ( ) => {
163+ server . close ( ) ;
164+ await new Promise ( resolve => setTimeout ( resolve , 500 ) ) ;
165+ proxyServerCleanup = null ;
166+ } ;
167+ }
168+
169+ /**
170+ * Clean up proxy server if it's running
171+ */
172+ async function cleanupProxyServer ( ) {
173+ if ( proxyServerCleanup ) {
174+ await proxyServerCleanup ( ) ;
175+ }
176+ }
177+
89178/**
90179 * Measure average time for an async operation over multiple iterations
91180 * Uses warmup iterations, median metric, and outlier filtering for robustness
@@ -295,8 +384,12 @@ async function benchmarkUserLogin() {
295384
296385/**
297386 * Benchmark: Query with Include (Parallel Include Pointers)
387+ * This test uses the TCP proxy (port 27018) to simulate 10ms database latency for more realistic measurements
298388 */
299389async function benchmarkQueryWithInclude ( ) {
390+ // Start proxy server
391+ await useProxyServer ( ) ;
392+
300393 // Setup: Create nested object hierarchy
301394 const Level2Class = Parse . Object . extend ( 'Level2' ) ;
302395 const Level1Class = Parse . Object . extend ( 'Level1' ) ;
@@ -332,11 +425,13 @@ async function benchmarkQueryWithInclude() {
332425 }
333426 await Parse . Object . saveAll ( rootObjects ) ;
334427
335- return measureOperation ( 'Query with Include (2 levels)' , async ( ) => {
428+ const result = await measureOperation ( 'Query with Include (2 levels)' , async ( ) => {
336429 const query = new Parse . Query ( 'Root' ) ;
337430 query . include ( 'level1.level2' ) ;
338431 await query . find ( ) ;
339- } , Math . floor ( ITERATIONS / 10 ) ) ; // Fewer iterations for complex queries
432+ } ) ;
433+
434+ return result ;
340435}
341436
342437/**
@@ -349,6 +444,9 @@ async function runBenchmarks() {
349444 let server ;
350445
351446 try {
447+ // Start MongoDB proxy
448+ await startProxy ( ) ;
449+
352450 // Initialize Parse Server
353451 console . log ( 'Initializing Parse Server...' ) ;
354452 server = await initializeParseServer ( ) ;
@@ -358,38 +456,26 @@ async function runBenchmarks() {
358456
359457 const results = [ ] ;
360458
361- // Run each benchmark with database cleanup
362- console . log ( 'Running Object Create benchmark...' ) ;
363- await cleanupDatabase ( ) ;
364- results . push ( await benchmarkObjectCreate ( ) ) ;
365-
366- console . log ( 'Running Object Read benchmark...' ) ;
367- await cleanupDatabase ( ) ;
368- results . push ( await benchmarkObjectRead ( ) ) ;
459+ // Define all benchmarks to run
460+ const benchmarks = [
461+ { name : 'Object Create' , fn : benchmarkObjectCreate } ,
462+ { name : 'Object Read' , fn : benchmarkObjectRead } ,
463+ { name : 'Object Update' , fn : benchmarkObjectUpdate } ,
464+ { name : 'Simple Query' , fn : benchmarkSimpleQuery } ,
465+ { name : 'Batch Save' , fn : benchmarkBatchSave } ,
466+ { name : 'User Signup' , fn : benchmarkUserSignup } ,
467+ { name : 'User Login' , fn : benchmarkUserLogin } ,
468+ { name : 'Query with Include' , fn : benchmarkQueryWithInclude } ,
469+ ] ;
369470
370- console . log ( 'Running Object Update benchmark...' ) ;
371- await cleanupDatabase ( ) ;
372- results . push ( await benchmarkObjectUpdate ( ) ) ;
373-
374- console . log ( 'Running Simple Query benchmark...' ) ;
375- await cleanupDatabase ( ) ;
376- results . push ( await benchmarkSimpleQuery ( ) ) ;
377-
378- console . log ( 'Running Batch Save benchmark...' ) ;
379- await cleanupDatabase ( ) ;
380- results . push ( await benchmarkBatchSave ( ) ) ;
381-
382- console . log ( 'Running User Signup benchmark...' ) ;
383- await cleanupDatabase ( ) ;
384- results . push ( await benchmarkUserSignup ( ) ) ;
385-
386- console . log ( 'Running User Login benchmark...' ) ;
387- await cleanupDatabase ( ) ;
388- results . push ( await benchmarkUserLogin ( ) ) ;
389-
390- console . log ( 'Running Query with Include benchmark...' ) ;
391- await cleanupDatabase ( ) ;
392- results . push ( await benchmarkQueryWithInclude ( ) ) ;
471+ // Run each benchmark with database cleanup
472+ for ( const benchmark of benchmarks ) {
473+ console . log ( `Running ${ benchmark . name } benchmark...` ) ;
474+ resetParseServer ( ) ;
475+ await cleanupDatabase ( ) ;
476+ results . push ( await benchmark . fn ( ) ) ;
477+ await cleanupProxyServer ( ) ;
478+ }
393479
394480 // Output results in github-action-benchmark format (stdout)
395481 console . log ( JSON . stringify ( results , null , 2 ) ) ;
@@ -412,8 +498,9 @@ async function runBenchmarks() {
412498 if ( server ) {
413499 server . close ( ) ;
414500 }
501+ await stopProxy ( ) ;
415502 // Give some time for cleanup
416- setTimeout ( ( ) => process . exit ( 0 ) , 10000 ) ;
503+ setTimeout ( ( ) => process . exit ( 0 ) , 1000 ) ;
417504 }
418505}
419506
0 commit comments