Aller au contenu principal
fellwin
Commerce · Reconditionnement2024 → en cours · Plus de 3 ans en production · Concepteur · développeur · exploitant

Cykero

120 000 articles série, tarifés en moins d'une seconde, en multi-tenant.

  • 3+ ans

    en production continue

  • 3,5 M

    variants en catalogue

  • <100 ms

    p99 sur la recherche

Le contexte

Cykero opère sur le marché européen de l'électronique reconditionnée, à travers plusieurs canaux : marketplaces (Amazon, Refurbed), boutique en propre, B2B grands comptes, opérations entrepôt. Chaque canal a son propre rythme, ses propres exigences de formatage, et son propre contrat opérationnel.

Au démarrage, l'enjeu n'était pas la complexité technique d'un seul système. C'était la cohérence entre plusieurs systèmes qui doivent partager une même vérité sur le stock et le prix, en temps réel, à l'échelle du catalogue (~3,5 millions de variants) et des opérations (~120 000 articles série identifiés unitairement).

Nous avons construit la plateforme comme un ensemble de services spécialisés (catalogue, opérations entrepôt, adaptateurs canaux, backend B2B) qui communiquent via un bus d'événements asynchrone. Aucun service ne partage de base de données avec un autre. La règle est simple : chaque service est seul à pouvoir modifier sa source de vérité, et publie ses changements sous forme d'événements typés que les autres consomment.

Les décisions d'architecture

Catalogue indexant tous les variants, y compris ceux sans stock, pas Réduire le catalogue aux seuls SKU effectivement stockés. Un variant sans stock reste une offre dormante : la configuration marchand existe, seul l'approvisionnement manque. Les marketplaces affichent l'iPhone « disponible en rouge » même quand le rouge est épuisé. Les équipes Operations doivent savoir quels variants ré-approvisionner. Réduire le catalogue aurait masqué ces deux signaux et créé une décorrélation permanente entre ce que le marché voit et ce que nous gérons.

Trade-off assumé : ~3,5 millions de variants à maintenir au lieu d'environ 1 million de SKU effectivement stockés. Amplification mécanique des coûts de synchronisation vers les canaux externes. Mitigation : un cache SKU par adaptateur de canal filtre vers ~5 à 8 000 SKU réellement transmis, avec une discipline de réconciliation périodique pour détecter les dérives.

Indice de recherche matérialisé, reconstruit quotidiennement, pas Agrégation live sur la base catalogue à chaque requête. L'agrégation live sur 3,5 millions de variants coûte 5 à 10 secondes par requête. Inacceptable sur mobile où l'utilisateur scrolle. La vue matérialisée dénormalisée descend la requête sous les 100 ms p99. Elle n'indexe que les variants avec stock disponible, ce qui garde l'index pertinent sans masquer le modèle complet utilisé pour la planification.

Trade-off assumé : Les données de recherche peuvent avoir jusqu'à 24 heures de retard sur les mises à jour de stock. Acceptable parce que la requête de stock définitive se fait au checkout via une API séparée. La recherche est exploratoire, pas transactionnelle. Le coût opérationnel est de pouvoir reconstruire l'index complet en moins d'une heure si besoin de rattrapage.

Prix en deux étapes : coût au repos stocké, ajustements calculés à la volée, pas Pré-calculer et stocker tous les prix finaux par combinaison de règles. Le prix final dépend de plusieurs axes : garantie 12 ou 24 mois, accessoires inclus ou non, marge négociée par client B2B, règles propres au canal de sortie. Pré-calculer toutes les combinaisons crée une explosion combinatoire ingérable à 120 000 articles. La fonction d'ajustement est pure (CPU seul, sans I/O) ; elle coûte environ 0,001 ms par item, soit ~500 ms pour un batch de 120 000. Le bénéfice principal est qu'un changement de règle (par exemple la garantie 24 mois qui passe de 2,5 % à 3 %) ne nécessite aucune invalidation de cache. C'est un déploiement de code, et non une mutation de données.

Trade-off assumé : Chaque requête de prix paie le coût de calcul. Compensé par le batching côté lecture, et par la garantie que le calcul est CPU-only (il ne provoque jamais d'attente d'I/O). Le code de l'ajustement est versionné comme une dépendance critique : un changement de logique passe par revue et test, jamais par mutation directe en base.

L'exécution

La plateforme tourne sur infrastructure auto-hébergée. Chaque service vit dans son propre conteneur, déployé via GitHub Actions sur des VPS dédiés (un par environnement : staging, production). Le bus d'événements (NATS JetStream) tourne sur sa propre instance avec persistance disque. Les bases de données (MongoDB pour le catalogue, PostgreSQL pour le B2B) ont leur propre cycle de sauvegarde, vérifié mensuellement par restauration test sur un environnement éphémère.

L'observabilité s'appuie sur trois mécanismes complémentaires. Logs structurés par service, agrégés sur un serveur central, avec un niveau de verbosité configurable par environnement. Heartbeats périodiques publiés par chaque adaptateur de canal toutes les 5 minutes : compteurs de synchronisation, taux d'échec, dérive de cache SKU. Ces heartbeats sont eux-mêmes des messages NATS, ce qui veut dire qu'on peut alerter sur leur absence aussi facilement que sur leur contenu. Dead-letter queues par stream : chaque message qui échoue trois fois après backoff exponentiel est mis de côté avec son contexte complet, lisible et rejouable par un humain.

Les déploiements sont continus. Une fusion sur main déclenche le build, les tests, le push d'image, puis l'orchestration : chaque service redémarre dans l'ordre qui préserve les invariants du bus (les consommateurs avant les producteurs en cas de changement de schéma d'événement).

Les leçons

La première leçon a porté sur la dérive silencieuse entre le catalogue et les canaux externes. Pendant les premiers mois, nous nous appuyions exclusivement sur la diffusion d'événements pour synchroniser le stock vers chaque marketplace. Cela fonctionnait, jusqu'à ce qu'un changement d'API côté canal rejette silencieusement une catégorie d'événements, sans erreur visible côté émetteur. La dérive a couru deux semaines avant d'être détectée par une réclamation client qui voyait un produit proposé alors que notre catalogue le marquait épuisé.

Nous avons ajouté un mécanisme de réconciliation périodique : un job qui compare ce que chaque canal expose effectivement avec ce que le catalogue prétend avoir transmis. Tout écart maintenu plus de 24 heures déclenche une alerte humaine. Plus largement, nous avons appris qu'un système distribué qui s'appuie sur des événements doit toujours pouvoir prouver son propre état par une voie indépendante ; sinon il fonctionne, mais il ne s'opère pas.

La seconde leçon est venue avec l'arrivée du second tenant. Notre première version traitait le stock comme une grandeur unifiée : un variant était « en stock » globalement. Lorsque la seconde entité juridique a démarré, avec ses propres règles d'imposition, ses propres délais d'expédition et son propre périmètre légal, nous avons dû repenser le modèle de visibilité : chaque commande doit voir le stock de sa juridiction d'abord, puis éventuellement le stock global pour les cas de fulfillment croisé. Cela a touché le catalogue, le pricing, l'API B2B et l'export marketplace. Un mois de refactoring qu'un meilleur modèle initial nous aurait évité.

En production aujourd'hui

La plateforme tourne en continu depuis plus de trois ans. Les volumes : ~3,5 millions de variants au catalogue, ~120 000 articles série tracés unitairement, plusieurs centaines de milliers de mises à jour de stock par jour propagées à travers le bus d'événements. Les marketplaces (Amazon, Refurbed) consomment leurs flux dédiés ; le frontoffice B2B et la boutique en propre consomment leur propre vue du catalogue. Chaque canal a son propre rythme et son propre contrat ; la plateforme s'efforce de ne jamais en perdre un seul de vue.

La roadmap court terme finalise un service Operations dédié à la phase d'inbound (réception, contrôle qualité, mise en linéaire) : service additionnel de la plateforme, et celui qui standardise le pipeline d'entrée pour rendre Cykero scalable au-delà de l'organisation actuelle.

Un système à concevoir, déployer ou reprendre en main ?

Nous étudions chaque cahier des charges sérieusement. Réponse sous 48 h ouvrées.

Réservez un diagnostic