Products Manager APP

Technical

Performance

Performance

Products Manager v5.7 est conçu pour absorber des volumes de données industriels — des imports de 100k lignes aux catalogues multi-fournisseurs de plusieurs millions de produits — tout en maintenant des temps de réponse sous les 200ms au p99 sur les routes critiques.


Objectifs de Performance

SLOs de Production

p99 < 200ms sur les routes produit | 50k lignes importées en ~45s | Recherche full-text < 50ms | 0% pool exhaustion depuis l'architecture multi-DB

OpérationCibleP95P99
Lecture produit (cache L1)<10ms<20ms<50ms
Lecture produit (cache L2 Redis)<20ms<40ms<80ms
Lecture produit (base de données)<50ms<100ms<200ms
Écriture produit<100ms<200ms<500ms
Recherche full-text (Meilisearch)<50ms<80ms<150ms
Recherche sémantique (Qdrant)<100ms<150ms<300ms
Import CSV 50k lignes~45s~60s~90s
Import CSV 100k lignes~90s~120s~180s
Enrichissement IA batch 100 produits~8min~10min~15min
Export catalogue complet<30s<60s<120s

Base de Données

Architecture 8 Instances PostgreSQL

La séparation du trafic sur 8 bases PostgreSQL spécialisées est le levier principal de performance. Avant cette migration, les imports batch bloquaient les lectures catalogue par contention de verrous. Désormais chaque domaine fonctionnel dispose de sa propre instance avec un pool dimensionné à sa charge réelle.

BasePool BaseMax OverflowTemps MoyenCharge Dominante
db_core510<10msAuth, JWT, RBAC (haute lecture)
db_catalog2030<50msProduits, prix, stocks (trafic maximal)
db_imports1525<40msJobs d'import, logs (haute écriture burst)
db_exports1020<20msExport jobs (batch séquentiel)
db_media1020<30msMétadonnées fichiers, thumbnails
db_code2asin1020<30msMapping EAN/ASIN, cache Amazon
db_analytics1020<60msMétriques, rapports BI
db_suppliers1020<30msDonnées fournisseurs, configurations

Connection Pooling

Chaque base utilise asyncpg avec un pool dimensionné indépendamment :

# Paramètres globaux de pool
DATABASE_POOL_TIMEOUT = 30       # 30s d'attente max avant erreur
DATABASE_POOL_RECYCLE = 3600     # Recyclage des connexions après 1h
DATABASE_POOL_PRE_PING = True    # Vérification avant réutilisation
DATABASE_MAX_OVERFLOW = 30       # Connexions supplémentaires autorisées

# Dimensionnement par base (pool_size=20 pour db_catalog, max total=50)
CATALOG_POOL_SIZE = 20
IMPORTS_POOL_SIZE = 15
CORE_POOL_SIZE = 5

Le max_overflow=30 sur db_catalog permet d'absorber les pics de trafic (ex : un import massif déclenchant des lectures parallèles de validation) sans rejeter de requêtes.

Index Composites (100+)

Plus de 100 index composites couvrent les patterns d'accès fréquents. Exemples représentatifs :

-- db_catalog : filtre catalogue par catégorie et marque
CREATE INDEX idx_products_category_brand
ON products(category_id, brand_id)
WHERE deleted_at IS NULL;

-- db_catalog : lookup EAN avec filtre soft-delete
CREATE INDEX idx_products_ean_active
ON products(ean, deleted_at)
WHERE deleted_at IS NULL;

-- db_catalog : filtre fournisseur + statut (exports, rapports)
CREATE INDEX idx_products_supplier_status
ON products(supplier_id, status)
WHERE deleted_at IS NULL;

-- db_imports : monitoring des jobs actifs
CREATE INDEX idx_import_jobs_status_created
ON import_jobs(status, created_at DESC)
WHERE status IN ('pending', 'running');

-- db_analytics : agrégations métriques par période
CREATE INDEX idx_performance_metrics_date_type
ON performance_metrics(metric_date, metric_type);

Soft Delete avec Index Partiels

Le pattern soft-delete (deleted_at IS NULL) est systématiquement indexé via des index partiels, ce qui réduit la taille des index de 20 à 40% et accélère les scans sur les enregistrements actifs :

-- Index partiel : n'inclut que les enregistrements non supprimés
CREATE INDEX idx_products_active_supplier
ON products(supplier_id, updated_at DESC)
WHERE deleted_at IS NULL;

-- Requête optimisée par cet index
SELECT * FROM products
WHERE supplier_id = $1
  AND deleted_at IS NULL
ORDER BY updated_at DESC
LIMIT 50;

Partitionnement par Mois

Les tables à forte croissance sont partitionnées par mois avec une rétention de 9 mois. Les partitions expirées sont droppées automatiquement via un job Celery planifié.

Tables partitionnées :

  • import_errors (db_imports) — erreurs de traitement ligne par ligne, volume proportionnel aux imports
  • performance_metrics (db_analytics) — métriques système collectées toutes les minutes
-- Partition actuelle (exemple mars 2026)
CREATE TABLE import_errors_2026_03
PARTITION OF import_errors
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

-- Job de nettoyage automatique (Celery Beat)
-- Retention : 9 mois glissants
-- Planification : 1er de chaque mois à 02:00

Monitoring APM (Sentry)

Les requêtes lentes sont capturées via l'intégration Sentry SQLAlchemy. Le seuil est configurable par environnement :

# Sentry slow query threshold
SENTRY_TRACES_SAMPLE_RATE = 0.1       # 10% des transactions
SLOW_QUERY_THRESHOLD_MS = 500          # Alerte si >500ms
DB_QUERY_APM_ENABLED = True

Les traces incluent le plan d'exécution, les paramètres (masqués en production) et le contexte de la requête HTTP parente.


Celery Workers

14 Queues Dédiées

L'isolation par queue garantit qu'une saturation d'un type de traitement (ex : imports massifs) ne bloque pas les autres opérations (ex : enrichissement IA, synchronisation Odoo).

QueueWorkersPrioritéUsage
imports4HighTraitement fichiers CSV/Excel/XML
icecat2NormalEnrichissement catalogue Icecat
enrichment2NormalEnrichissement IA (OpenAI)
odoo_sync1NormalSynchronisation Odoo bidirectionnelle
media2LowGénération thumbnails, traitement images
exports2NormalGénération exports plateformes
code2asin3NormalConversion EAN/ASIN Amazon
catalog1LowTâches maintenance catalogue
analytics1LowAgrégation métriques
notifications1HighEnvoi notifications temps réel
compliance1NormalVérifications conformité réglementaire
pricing1NormalCalculs prix, surveillance concurrents
webhooks1HighEnvoi webhooks outbound
beatScheduler Celery Beat

Total : 12 workers prefork (processus séparés, pas de GIL).

Configuration Prefork et Rate Limiting

# celery_config.py
CELERY_WORKER_CONCURRENCY = 4          # 4 processus par worker
CELERY_WORKER_PREFETCH_MULTIPLIER = 1  # Évite la surcharge mémoire
CELERY_TASK_ACKS_LATE = True           # ACK après succès seulement

# Rate limiting par queue (évite la saturation des APIs externes)
CELERY_TASK_ANNOTATIONS = {
    "tasks.icecat.*": {"rate_limit": "100/m"},
    "tasks.code2asin.*": {"rate_limit": "10/s"},
    "tasks.enrichment.*": {"rate_limit": "50/m"},
    "tasks.odoo_sync.*": {"rate_limit": "30/m"},
}

Caching

L'architecture de cache à 2 niveaux réduit la charge base de données de ~70% sur les lectures catalogue.

  • L1 (FastAPI in-memory) : données statiques (modules actifs, permissions RBAC, settings), TTL 60-300s, invalidé au restart worker
  • L2 (Redis 7, 512MB) : données produits, listes paginées, stats dashboard, stratégie allkeys-lru

Un cache warming au démarrage de FastAPI précalcule les données les plus demandées pour éliminer les cold starts.

Pour les détails complets (clés Redis, TTL par ressource, invalidation, monitoring hit/miss) : Cache → /docs/technical/caching


Full-Text Search (Meilisearch)

Meilisearch sert d'index de recherche dédié, découplé de PostgreSQL.

  • Latence : < 50ms au p99 sur les recherches produits
  • Indexation : asynchrone via Celery après chaque mutation produit (queue catalog)
  • Typo-tolérance : activée, distance de Levenshtein configurable par champ
  • Champs indexés : title, ean, sku, brand_name, category_name, description (poids différenciés)
  • Filtres : supplier_id, category_id, status, is_active
  • Tri : par pertinence, prix, date de création
# Réindexation après import
@celery_app.task(queue="catalog")
async def reindex_products_meilisearch(product_ids: list[str]):
    documents = await build_search_documents(product_ids)
    await meili_client.index("products").add_documents_in_batches(
        documents, batch_size=1000
    )

Recherche Sémantique (Qdrant)

Qdrant stocke les embeddings vectoriels des produits pour la recherche par similarité sémantique.

  • Modèle : OpenAI text-embedding-3-small (1536 dimensions)
  • Latence : < 100ms au p95 pour une recherche ANN (Approximate Nearest Neighbors)
  • Champs embarqués : titre + description + attributs clés (concaténés, tronqués à 8192 tokens)
  • Indexation : asynchrone après enrichissement IA (queue enrichment)
  • Use cases : "Produits similaires", recherche par description naturelle, détection de doublons sémantiques

Frontend Next.js 15

  • Server Components : réduction du JavaScript envoyé au client, rendu HTML côté serveur pour les pages catalogue
  • Lazy loading : composants lourds (éditeurs, charts) chargés à la demande
  • Image optimization : composant <Image> Next.js avec WebP automatique et srcset responsive
  • Code splitting : chunking automatique par route, dynamic() pour les modules optionnels
  • Bundle size : First Load JS < 500 kB (hors chunks lazy)

Monitoring

Prometheus + Grafana

Les métriques applicatives sont exposées sur /metrics au format Prometheus :

# Latence des requêtes HTTP (histogramme)
http_request_duration_seconds{method, endpoint, status}

# Taille des pools de connexions DB
db_pool_connections_active{database}
db_pool_connections_idle{database}

# Queues Celery
celery_queue_length{queue_name}
celery_task_duration_seconds{task_name, queue}

# Cache Redis
redis_cache_hits_total{resource}
redis_cache_misses_total{resource}
redis_memory_used_bytes

Les dashboards Grafana couvrent : latence par endpoint, throughput imports, occupation des pools DB, longueur des queues Celery, hit ratio cache.

Sentry APM

  • Tracing distribué sur 10% des transactions (configurable)
  • Alertes automatiques sur les requêtes SQL > 500ms
  • Profiling Python activable en production pour investigation ponctuelle

Benchmarks Observés (Production v5.7)

ScénarioRésultatConditions
Import CSV 50k lignes~45sFichier fournisseur standard, mapping simple
Import CSV 100k lignes~90sAvec validation EAN et déduplication
Enrichissement IA batch 100 produits~8minOpenAI GPT-4o, 5 champs enrichis
Recherche full-text<50msIndex Meilisearch, catalogue 500k produits
Recherche sémantique<100msQdrant ANN, 500k vecteurs
Lecture produit (cache chaud)<10msRedis L2, TTL actif
Dashboard analytics<400msp95, agrégations pré-calculées
Export CSV 10k produits<15sSans images, export direct S3

Variables d'Environnement Liées à la Performance

VariableDéfautDescription
WORKER_COUNT8Nombre de workers Uvicorn
DB_POOL_SIZE20Pool de connexions db_catalog
DB_MAX_OVERFLOW30Overflow pool db_catalog
DB_POOL_TIMEOUT30Timeout attente connexion (s)
DB_POOL_RECYCLE3600Recyclage connexion (s)
CELERY_WORKER_CONCURRENCY4Processus par worker Celery
CELERY_PREFETCH_MULTIPLIER1Préchargement tâches
SLOW_QUERY_THRESHOLD_MS500Seuil alerte requête lente (ms)
SENTRY_TRACES_SAMPLE_RATE0.1Taux échantillonnage APM
MEILISEARCH_BATCH_SIZE1000Taille batch indexation
IMPORT_BATCH_SIZE500Lignes par batch d'import

Ressources

Évolution Continue

Les benchmarks sont mesurés sur l'infrastructure de production (8 workers Uvicorn, 8 instances PostgreSQL, Redis 512MB). Ils varient selon la complexité des mappings et la qualité des données sources.