Perf: Paralleler Norm-Cache-Sync mit Worker-Pool

Sequentieller Sync ersetzt durch parallele Worker:
- Standard: 4 gleichzeitige ffmpeg-Prozesse (NORM_CONCURRENCY=4)
- Konfigurierbar per NORM_CONCURRENCY env var
- Sync-Zeit bei vielen Sounds 4x schneller (linear mit Parallelität)
- Logging gibt Parallelität und Dauer aus

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bot 2026-03-01 21:29:53 +01:00
parent 68414ac257
commit 7a7056d72b

View file

@ -193,9 +193,14 @@ function normalizeToCache(filePath: string): Promise<string> {
}); });
} }
// Wie viele ffmpeg-Prozesse parallel beim Cache-Sync laufen dürfen.
// Standard: 4 (gut für 4+ Kern CPUs ohne Discord-Wiedergabe zu stören).
// Über NORM_CONCURRENCY=8 o.ä. erhöhbar.
const NORM_CONCURRENCY = Math.max(1, Number(process.env.NORM_CONCURRENCY ?? 4));
/** /**
* Vollständige Cache-Synchronisation: * Vollständige Cache-Synchronisation:
* 1. Alle Sound-Dateien normalisieren, die noch nicht im Cache sind * 1. Alle Sound-Dateien normalisieren, die noch nicht im Cache sind (parallel)
* 2. Verwaiste Cache-Dateien löschen (Sound wurde gelöscht/umbenannt) * 2. Verwaiste Cache-Dateien löschen (Sound wurde gelöscht/umbenannt)
* Läuft im Hintergrund, blockiert nicht den Server. * Läuft im Hintergrund, blockiert nicht den Server.
*/ */
@ -206,24 +211,37 @@ async function syncNormCache(): Promise<void> {
// Set aller erwarteten Cache-Keys // Set aller erwarteten Cache-Keys
const expectedKeys = new Set<string>(); const expectedKeys = new Set<string>();
let created = 0; const toProcess: string[] = [];
let skipped = 0;
let failed = 0;
for (const s of allSounds) { for (const s of allSounds) {
const fp = path.join(SOUNDS_DIR, s.relativePath); const fp = path.join(SOUNDS_DIR, s.relativePath);
const key = normCacheKey(fp); const key = normCacheKey(fp);
expectedKeys.add(key); expectedKeys.add(key);
if (!fs.existsSync(fp)) continue; if (!fs.existsSync(fp)) continue;
if (getNormCachePath(fp)) { skipped++; continue; } // bereits gecacht & gültig if (getNormCachePath(fp)) continue; // bereits gecacht & gültig
try { toProcess.push(fp);
await normalizeToCache(fp); }
created++;
} catch (e) { let created = 0;
failed++; let failed = 0;
console.warn(`Norm-cache failed: ${s.relativePath}`, e); const skipped = allSounds.length - toProcess.length;
// Parallele Worker-Pool: NORM_CONCURRENCY ffmpeg-Prozesse gleichzeitig
const queue = [...toProcess];
async function worker(): Promise<void> {
while (queue.length > 0) {
const fp = queue.shift()!;
try {
await normalizeToCache(fp);
created++;
} catch (e) {
failed++;
console.warn(`Norm-cache failed: ${path.relative(SOUNDS_DIR, fp)}`, e);
}
} }
} }
const workers = Array.from({ length: Math.min(NORM_CONCURRENCY, toProcess.length || 1) }, worker);
await Promise.all(workers);
// Verwaiste Cache-Dateien aufräumen // Verwaiste Cache-Dateien aufräumen
let cleaned = 0; let cleaned = 0;
@ -237,7 +255,7 @@ async function syncNormCache(): Promise<void> {
const dt = ((Date.now() - t0) / 1000).toFixed(1); const dt = ((Date.now() - t0) / 1000).toFixed(1);
console.log( console.log(
`Norm-Cache sync (${dt}s): ${created} neu, ${skipped} vorhanden, ${failed} fehlgeschlagen, ${cleaned} verwaist entfernt (${allSounds.length} Sounds gesamt)` `Norm-Cache sync (${dt}s, ${NORM_CONCURRENCY} parallel): ${created} neu, ${skipped} vorhanden, ${failed} fehlgeschlagen, ${cleaned} verwaist entfernt (${allSounds.length} Sounds gesamt)`
); );
} }