From ac46637377f3603573655c5a2a605302a743606e Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 26 May 2026 02:40:37 +0200 Subject: [PATCH] perf: skip previous-1950-2024 fetch si latest suffit (cold-fetch -90%) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Latest fichier (~50 KB compressé) couvre l'année courante et la précédente. En mai 2026 il contient janvier 2025 → présent (~17 mois > 365j requis). Previous (~4 MB compressé) n'est nécessaire que si latest a < 365j — ce qui ne se produit qu'en début d'année (jan-mar 2025 par exemple). Cold-fetch dept : - Avant : ~17s (latest + previous + parsing 70 ans de data inutiles) - Après : ~2-3s (latest seul, parse de quelques mois) + mem_limit 256m → 512m pour donner du headroom au cold-fetch concurrent (constaté : 5 workers warmup parallèles ont OOM le container). Co-Authored-By: Claude Opus 4.7 (1M context) --- docker-compose.yml | 4 ++-- src/lib/climato.ts | 48 +++++++++++++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 03a4c25..fa3e996 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,8 +17,8 @@ services: - HOST=0.0.0.0 networks: - shared-net - mem_limit: 256m - cpus: 0.5 + mem_limit: 512m + cpus: 1.0 logging: driver: json-file options: diff --git a/src/lib/climato.ts b/src/lib/climato.ts index 7ec76a8..c38dccb 100644 --- a/src/lib/climato.ts +++ b/src/lib/climato.ts @@ -83,23 +83,8 @@ async function fetchOne(url: string): Promise { return gunzipSync(buf).toString('utf-8'); } -async function fetchClimato(dept: string): Promise { - const byDate = new Map(); - // 1) latest (rapide, ~50 KB compressé) — couvre l'année courante - const latestUrl = buildUrl(dept, LATEST); - if (latestUrl) { - const text = await fetchOne(latestUrl); - if (text) parseCsvInto(text, byDate); - } - // 2) previous (~4 MB compressé) — nécessaire pour combler les jours hors année courante - // On le fetch toujours, son taille est tolérable au cold-fetch (cache 24h). - const previousUrl = buildUrl(dept, PREVIOUS); - if (previousUrl) { - const text = await fetchOne(previousUrl); - if (text) parseCsvInto(text, byDate); - } - - const days: DayObservation[] = [...byDate.entries()] +function aggregateDays(byDate: Map): DayObservation[] { + return [...byDate.entries()] .sort(([a], [b]) => (a < b ? -1 : 1)) .map(([date, agg]) => ({ date, @@ -109,8 +94,35 @@ async function fetchClimato(dept: string): Promise { rr: agg.rrN > 0 ? +(agg.rrSum / agg.rrN).toFixed(1) : null, stations: agg.stations, })); +} + +async function fetchClimato(dept: string): Promise { + const byDate = new Map(); + // 1) latest (~50 KB compressé) — couvre l'année courante. + // Le fichier latest-2025-2026 contient janvier 2025 → aujourd'hui. + // En 2026, ça couvre déjà > 365 jours, donc previous n'est pas nécessaire. + const latestUrl = buildUrl(dept, LATEST); + if (latestUrl) { + const text = await fetchOne(latestUrl); + if (text) parseCsvInto(text, byDate); + } + + let days = aggregateDays(byDate); + + // 2) previous (~4 MB compressé) — UNIQUEMENT si latest ne couvre pas MAX_DAYS. + // Cas où c'est nécessaire : début 2025 (latest n'a que ~quelques mois). + // En mai 2026, latest couvre déjà ~17 mois → previous skip → cold-fetch ~10× plus rapide. + if (days.length < MAX_DAYS) { + const previousUrl = buildUrl(dept, PREVIOUS); + if (previousUrl) { + const text = await fetchOne(previousUrl); + if (text) { + parseCsvInto(text, byDate); + days = aggregateDays(byDate); + } + } + } - // Garder MAX_DAYS derniers jours (365) return { dept, fetchedAt: new Date().toISOString(), days: days.slice(-MAX_DAYS) }; }