1818 * - DELAY_BETWEEN_BATCHES: Delay between batches in seconds (default: 10)
1919 * - MAX_RETRIES: Maximum retries for failed batches (default: 3)
2020 * - REQUEST_TIMEOUT: Request timeout in seconds (default: 45)
21+ * - ENABLE_WARM_UP: Enable API route warm-up to prevent cold start issues (default: true)
2122 */
2223
2324interface BatchProgress {
@@ -111,6 +112,7 @@ interface BatchConfig {
111112 delayBetweenBatches: number ;
112113 maxRetries: number ;
113114 requestTimeout: number ; // in seconds
115+ enableWarmUp: boolean ; // whether to warm up API route before processing
114116}
115117
116118interface ApiResponse < T > {
@@ -167,6 +169,9 @@ class BatchSnapshotOrchestrator {
167169 const delayBetweenBatches = this . parseAndValidateNumber ( process . env . DELAY_BETWEEN_BATCHES || '10' , 'DELAY_BETWEEN_BATCHES' , 1 , 300 ) ;
168170 const maxRetries = this . parseAndValidateNumber ( process . env . MAX_RETRIES || '3' , 'MAX_RETRIES' , 1 , 10 ) ;
169171 const requestTimeout = this . parseAndValidateNumber ( process . env . REQUEST_TIMEOUT || '45' , 'REQUEST_TIMEOUT' , 10 , 300 ) ;
172+
173+ // Parse boolean environment variable for warm-up feature
174+ const enableWarmUp = process . env . ENABLE_WARM_UP !== 'false' ; // Default to true unless explicitly disabled
170175
171176 return {
172177 apiBaseUrl,
@@ -175,6 +180,7 @@ class BatchSnapshotOrchestrator {
175180 delayBetweenBatches,
176181 maxRetries,
177182 requestTimeout,
183+ enableWarmUp,
178184 } ;
179185 }
180186
@@ -211,7 +217,16 @@ class BatchSnapshotOrchestrator {
211217 clearTimeout ( timeoutId ) ;
212218
213219 if ( ! response . ok ) {
214- throw new Error ( `HTTP ${ response . status } : ${ response . statusText } ` ) ;
220+ // Provide more specific error messages for common cold start issues
221+ if ( response . status === 405 ) {
222+ throw new Error ( `HTTP 405: Method Not Allowed - Possible cold start issue` ) ;
223+ } else if ( response . status === 503 ) {
224+ throw new Error ( `HTTP 503: Service Unavailable - Server may be starting up` ) ;
225+ } else if ( response . status === 502 ) {
226+ throw new Error ( `HTTP 502: Bad Gateway - Upstream server may be cold` ) ;
227+ } else {
228+ throw new Error ( `HTTP ${ response . status } : ${ response . statusText } ` ) ;
229+ }
215230 }
216231
217232 const data = await response . json ( ) as T ;
@@ -228,6 +243,35 @@ class BatchSnapshotOrchestrator {
228243 return new Promise ( resolve => setTimeout ( resolve , seconds * 1000 ) ) ;
229244 }
230245
246+ private async warmUpApiRoute ( ) : Promise < boolean > {
247+ console . log ( '🔥 Warming up API route to prevent cold start issues...' ) ;
248+
249+ try {
250+ // Make a simple OPTIONS request to warm up the route
251+ const url = new URL ( `${ this . config . apiBaseUrl } /api/v1/stats/run-snapshots-batch` ) ;
252+
253+ const response = await fetch ( url . toString ( ) , {
254+ method : 'OPTIONS' ,
255+ headers : {
256+ 'Authorization' : `Bearer ${ this . config . authToken } ` ,
257+ 'Content-Type' : 'application/json' ,
258+ } ,
259+ } ) ;
260+
261+ if ( response . ok || response . status === 200 ) {
262+ console . log ( '✅ API route warmed up successfully' ) ;
263+ return true ;
264+ } else {
265+ console . log ( `⚠️ API route warm-up returned status ${ response . status } , but continuing...` ) ;
266+ return true ; // Still continue as the route might be ready
267+ }
268+ } catch ( error ) {
269+ const errorMessage = error instanceof Error ? error . message : 'Unknown error' ;
270+ console . log ( `⚠️ API route warm-up failed: ${ errorMessage } , but continuing...` ) ;
271+ return true ; // Still continue as warm-up is optional
272+ }
273+ }
274+
231275 private getFriendlyErrorName ( errorType : string ) : string {
232276 const errorMap : Record < string , string > = {
233277 'wallet_build_failed' : 'Wallet Build Failed' ,
@@ -308,9 +352,21 @@ class BatchSnapshotOrchestrator {
308352 return null ;
309353 }
310354
311- // For 405 errors (Method Not Allowed), wait longer as it might be a server-side issue
312- const waitTime = errorMessage . includes ( '405' ) ? this . config . delayBetweenBatches * 2 : this . config . delayBetweenBatches ;
313- console . log ( ` ⏳ Waiting ${ waitTime } s before retry...` ) ;
355+ // Calculate wait time with exponential backoff for cold start issues
356+ let waitTime = this . config . delayBetweenBatches ;
357+
358+ if ( errorMessage . includes ( '405' ) || errorMessage . includes ( 'cold start' ) || errorMessage . includes ( '503' ) || errorMessage . includes ( '502' ) ) {
359+ // Cold start issue - use exponential backoff
360+ waitTime = Math . min ( this . config . delayBetweenBatches * Math . pow ( 2 , attempt - 1 ) , 60 ) ;
361+ console . log ( ` 🥶 Cold start detected, using exponential backoff: ${ waitTime } s` ) ;
362+ } else if ( errorMessage . includes ( 'timeout' ) ) {
363+ // Timeout issue - wait longer
364+ waitTime = this . config . delayBetweenBatches * 2 ;
365+ console . log ( ` ⏰ Timeout detected, waiting longer: ${ waitTime } s` ) ;
366+ } else {
367+ console . log ( ` ⏳ Standard retry delay: ${ waitTime } s` ) ;
368+ }
369+
314370 await this . delay ( waitTime ) ;
315371 }
316372 }
@@ -326,6 +382,15 @@ class BatchSnapshotOrchestrator {
326382 console . log ( '🔄 Starting batch snapshot orchestration...' ) ;
327383 console . log ( `📊 Configuration: batch_size=${ this . config . batchSize } , delay=${ this . config . delayBetweenBatches } s` ) ;
328384
385+ // Warm up the API route to prevent cold start issues (if enabled)
386+ if ( this . config . enableWarmUp ) {
387+ await this . warmUpApiRoute ( ) ;
388+ // Small delay after warm-up to ensure route is fully ready
389+ await this . delay ( 2 ) ;
390+ } else {
391+ console . log ( '🔥 Warm-up disabled via ENABLE_WARM_UP=false' ) ;
392+ }
393+
329394 // First, get the total number of batches by processing batch 1
330395 console . log ( '📋 Determining total batches...' ) ;
331396 const firstBatch = await this . processBatch ( 1 , batchId ) ;
0 commit comments