mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-19 21:18:16 +02:00
fix(api): tient les très gros fournisseurs (132k articles)
Le journal du fournisseur D005 montre 132 388 lignes persistées pour un seul
fournisseur. À cette échelle, /api/v1/grid sans limit ne tenait pas.
Deux plantages francs, indépendants de la taille de la réponse :
- getNetworkMetricsByCodeCentrale : inArray avec 132 000 valeurs, soit autant
de paramètres liés, alors que le protocole PostgreSQL en accepte 65535.
- pgGetGammesByCodeins : même problème sur son IN (…).
Les deux lectures sont désormais découpées par lots de 2000. Le bug existait
pour tout appel dépassant ~65 000 codes, indépendamment de l'API.
Volume : charger 132k lignes complètes puis les sérialiser d'un bloc représente
plusieurs centaines de Mo en mémoire. Sans `limit`, la réponse est maintenant
diffusée en flux, par lots internes de 1000, à mémoire constante. La forme
{ data, pagination, meta } est inchangée — un client existant ne voit que des
octets arrivant progressivement.
Une erreur survenant après les premiers octets ne peut plus changer le code
HTTP : le document est alors fermé avec meta.erreur et meta.complet=false, pour
qu'une réponse tronquée ne passe pas pour complète.
`fields` reste le levier décisif à cette échelle : une ligne complète porte les
séries mensuelles et les ventilations par magasin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
This commit is contained in:
3 files changed
+135
-26
No files matched your search
+22
-15
@@ -314,22 +314,29 @@ export async function pgGetGammesByCodeins(codeins: string[]): Promise<Map<strin
|
||||
const unique = [...new Set(codeins.map(c => String(c ?? "").trim()).filter(Boolean))];
|
||||
if (unique.length === 0) return new Map();
|
||||
|
||||
// DISTINCT ON (codein) → 1 gamme par article, saison la plus récente en premier.
|
||||
const result = await pgNoParallel(sql`
|
||||
SELECT DISTINCT ON (a.codein)
|
||||
TRIM(a.codein::text) AS codein,
|
||||
g.code AS gamme_code
|
||||
FROM art_gamme_saison ags
|
||||
JOIN articles a ON a.no_id = ags.artnoid
|
||||
JOIN gammes g ON g.no_id = ags.idgamme
|
||||
JOIN saisons s ON s.no_id = ags.idsaison
|
||||
WHERE TRIM(a.codein::text) IN (${sql.join(unique.map(c => sql`${c}`), sql`, `)})
|
||||
ORDER BY a.codein, s.no_id DESC
|
||||
`);
|
||||
|
||||
// Découpage obligatoire : chaque codein devient un paramètre lié, or PostgreSQL
|
||||
// en accepte 65535 au maximum. Un gros fournisseur dépasse 130 000 articles —
|
||||
// la requête échouait alors purement et simplement.
|
||||
const CHUNK = 2000;
|
||||
const map = new Map<string, string>();
|
||||
for (const row of result.rows as unknown as { codein: string; gamme_code: string }[]) {
|
||||
if (row.codein && row.gamme_code) map.set(row.codein, String(row.gamme_code).trim());
|
||||
|
||||
for (let i = 0; i < unique.length; i += CHUNK) {
|
||||
const batch = unique.slice(i, i + CHUNK);
|
||||
// DISTINCT ON (codein) → 1 gamme par article, saison la plus récente en premier.
|
||||
const result = await pgNoParallel(sql`
|
||||
SELECT DISTINCT ON (a.codein)
|
||||
TRIM(a.codein::text) AS codein,
|
||||
g.code AS gamme_code
|
||||
FROM art_gamme_saison ags
|
||||
JOIN articles a ON a.no_id = ags.artnoid
|
||||
JOIN gammes g ON g.no_id = ags.idgamme
|
||||
JOIN saisons s ON s.no_id = ags.idsaison
|
||||
WHERE TRIM(a.codein::text) IN (${sql.join(batch.map(c => sql`${c}`), sql`, `)})
|
||||
ORDER BY a.codein, s.no_id DESC
|
||||
`);
|
||||
for (const row of result.rows as unknown as { codein: string; gamme_code: string }[]) {
|
||||
if (row.codein && row.gamme_code) map.set(row.codein, String(row.gamme_code).trim());
|
||||
}
|
||||
}
|
||||
console.log(`[pg-ff] pgGetGammesByCodeins: ${map.size}/${unique.length} articles avec gamme`);
|
||||
return map;
|
||||
|
||||
@@ -42,10 +42,19 @@ export async function getNetworkMetricsByCodeCentrale(
|
||||
const unique = [...new Set(codes.filter(Boolean))];
|
||||
if (unique.length === 0) return out;
|
||||
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(qlikNetworkMetrics)
|
||||
.where(inArray(qlikNetworkMetrics.codeCentrale, unique));
|
||||
// Découpage obligatoire : `inArray` produit un paramètre lié par valeur, or le
|
||||
// protocole PostgreSQL en accepte 65535 au maximum. Un gros fournisseur peut
|
||||
// dépasser 130 000 codes — la requête échouait alors purement et simplement.
|
||||
const CHUNK = 2000;
|
||||
const rows: Array<typeof qlikNetworkMetrics.$inferSelect> = [];
|
||||
for (let i = 0; i < unique.length; i += CHUNK) {
|
||||
const batch = unique.slice(i, i + CHUNK);
|
||||
const part = await db
|
||||
.select()
|
||||
.from(qlikNetworkMetrics)
|
||||
.where(inArray(qlikNetworkMetrics.codeCentrale, batch));
|
||||
rows.push(...part);
|
||||
}
|
||||
|
||||
for (const r of rows) {
|
||||
out.set(r.codeCentrale, {
|
||||
|
||||
Reference in new issue
Block a user