Skip to content

Commit 33e6023

Browse files
committed
feat(batch-snapshot): add warm-up feature to prevent cold start issues
- Introduced ENABLE_WARM_UP environment variable to enable or disable API route warm-up. - Implemented warm-up logic in BatchSnapshotOrchestrator to make an OPTIONS request before processing batches. - Enhanced error handling for common cold start issues with specific messages for HTTP 405, 503, and 502 errors. - Updated documentation to reflect the new warm-up configuration option.
1 parent 73a2728 commit 33e6023

File tree

2 files changed

+70
-6
lines changed

2 files changed

+70
-6
lines changed

.github/workflows/daily-balance-snapshots.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ jobs:
2525
node-version: '18'
2626
cache: 'npm'
2727

28-
- name: Install dependencies
29-
run: npm ci
3028

3129
- name: Install script dependencies
3230
run: |
@@ -44,6 +42,7 @@ jobs:
4442
DELAY_BETWEEN_BATCHES: 10
4543
MAX_RETRIES: 3
4644
REQUEST_TIMEOUT: 45
45+
ENABLE_WARM_UP: true
4746

4847
- name: Notify on failure
4948
if: failure()

scripts/batch-snapshot-orchestrator.ts

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
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

2324
interface 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

116118
interface 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

Comments
 (0)