perf(backend): parallelize independent startup initializers (#816)

The boot path awaited SelfUpdateService.initialize, DockerEventManager.start,
and TrivyService.initialize one at a time even though none of them depend on
each other. Group them into a single Promise.all so total cold-start time is
the slowest one rather than the sum.

Also convert the inner `docker compose version` probe in
SelfUpdateService.initialize from execFileSync to execFileAsync. Without that,
the synchronous spawn would block the event loop for up to 5 seconds and
silently serialize the other two members of the parallel block, defeating
the parallelization win for in-container deployments.

The synchronous service starts (Monitor, AutoHeal, ImageUpdate, Scheduler,
Mfa) are grouped together up front. They schedule timers whose first ticks
fire 5+ seconds out, so they safely run alongside the awaited block.
This commit is contained in:
Anso
2026-04-28 00:33:17 -04:00
committed by GitHub
parent 61a7e43d82
commit 18cf2e65e8
2 changed files with 19 additions and 14 deletions
+13 -11
View File
@@ -29,27 +29,29 @@ export async function startServer(server: Server): Promise<void> {
console.error('Migration failed:', error);
}
// Synchronous starts: schedule background timers and continue. None of
// these fire their first tick for at least a few seconds, so they
// safely run alongside the async initializers below.
LicenseService.getInstance().initialize();
await SelfUpdateService.getInstance().initialize();
MonitorService.getInstance().start();
AutoHealService.getInstance().start();
await DockerEventManager.getInstance().start();
await TrivyService.getInstance().initialize();
ImageUpdateService.getInstance().start();
SchedulerService.getInstance().start();
MfaService.getInstance().start();
// Async initializers are independent of each other; run in parallel
// so total boot time is the slowest one rather than the sum.
await Promise.all([
SelfUpdateService.getInstance().initialize(),
DockerEventManager.getInstance().start(),
TrivyService.getInstance().initialize(),
]);
// Fire-and-forget housekeeping; logged but never awaited.
sweepStaleGitTempDirs().catch((err) => {
console.warn('[GitSource] Temp dir sweep failed:', (err as Error).message);
});
MfaService.getInstance().start();
const isPilotAgent = process.env.SENCHO_MODE === 'pilot';
const listenHost = isPilotAgent ? '127.0.0.1' : undefined;
+6 -3
View File
@@ -1,4 +1,4 @@
import { execFileSync, execFile } from 'child_process';
import { execFile } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import DockerController from './DockerController';
@@ -63,9 +63,12 @@ class SelfUpdateService {
return;
}
// Verify docker compose CLI is available inside the container
// Verify docker compose CLI is available inside the container.
// execFileAsync (not Sync) so the parallel boot-task block in
// bootstrap/startup.ts can actually run TrivyService and DockerEventManager
// concurrently instead of waiting on a 5s blocking spawn here.
try {
execFileSync('docker', ['compose', 'version'], { stdio: 'pipe', timeout: 5000 });
await execFileAsync('docker', ['compose', 'version'], { timeout: 5000 });
} catch {
console.log('[SelfUpdate] docker compose CLI not available in container');
disableCapability('self-update');