Module 2 — Anatomie d'un magasin de variables
Le module précédent a défini un magasin comme le contrat qui interdit l'écart entraînement-service. Ce module ouvre le capot : il nomme les six pièces qui composent un magasin et montre comment elles s'articulent, toujours sur le fil rouge du scoreur de fraude.
Les six pièces
Un magasin, même minimal, contient six composants distincts, chacun avec un rôle précis. La confusion entre eux est la première source de projets qui échouent.
1. Le registre. C'est le catalogue central. Il ne contient pas de données, il contient des déclarations : « voici l'entité porteur de carte, identifiée par le hachage card_id_hash ; voici la variable nb_transactions_24h, de type entier, propriétaire équipe fraude, calculée à partir de la source transactions ». Le registre est versionné (généralement dans un dépôt Git) et sert de source de vérité pour tout le reste.
2. Les entités. Une entité est le sujet d'une variable — la chose sur laquelle on prédit. Dans notre cas, l'entité est le porteur de carte, identifié par card_id_hash. On peut aussi avoir commerçant, terminal de paiement, couple porteur-commerçant. L'entité correspond, en SQL, à la clé de jointure. Un magasin sans entités explicites ne sait pas à quoi rattacher une variable et ne peut pas garantir la cohérence.
3. Les vues de variables (feature views). Une vue regroupe des variables partageant la même entité, la même source et la même durée de validité. La vue porteur_transactions_recentes contient nb_transactions_1h, nb_transactions_24h, nb_transactions_7j, montant_moyen_24h. Elle est rattachée à l'entité porteur et à la source transactions, avec une fenêtre de fraîcheur de 24 heures. Découper en vues cohérentes est le principal travail de conception.
4. Les sources. Une source est l'endroit où les données brutes vivent. On distingue les sources par lots (une table Parquet, une table Snowflake, un dossier S3) et les sources en flux (Kafka, Kinesis). Une même vue peut avoir deux sources — une historique pour l'entraînement, une temps réel pour la matérialisation vers l'en-ligne. C'est ici que se joue la cohérence.
5. Les magasins d'exécution. Deux instances stockent effectivement les valeurs calculées :
- Le magasin hors ligne conserve l'historique complet, indexé par entité et horodatage. Il sert à l'entraînement, aux réentraînements et aux analyses. Technologies typiques : Parquet, BigQuery, Snowflake.
- Le magasin en ligne conserve, pour chaque entité, la dernière valeur connue de chaque variable, avec un accès à faible latence. Il sert à l'inférence. Technologies typiques : Redis, DynamoDB, Cassandra.
6. Le serveur de variables. C'est l'API qui expose la lecture aux applications. Deux points d'entrée :
get_historical_features(entities, timestamps)interroge le magasin hors ligne et applique la jointure point-en-temps.get_online_features(entities)interroge le magasin en ligne pour une lecture en quelques millisecondes.
Le service de production appelle toujours le serveur ; il ne recalcule jamais une variable lui-même. C'est cette règle qui coupe la boucle du module précédent.
Comment les pièces s'articulent
Suivons un cycle complet sur notre cas de fraude :
- Un ingénieur ML modifie la vue
porteur_transactions_recentesdans le dépôt Git (par exemple, ajoutenb_transactions_7j). - Une commande d'application (
feast apply) publie la définition au registre. - Un ordonnanceur (Airflow, Dagster) déclenche périodiquement une matérialisation : le moteur lit la source
transactions, calcule les agrégats pour toutes les entités, écrit dans le magasin hors ligne l'historique correspondant à la nouvelle définition, et pousse la dernière valeur de chaque entité dans le magasin en ligne (Redis). - Au moment du réentraînement, un script demande
get_historical_features: il fournit une table d'événements (transactions à scorer historiquement, avec leur horodatage) et reçoit, pour chacun, l'état des variables tel qu'il était à cet instant précis. Aucune fuite du futur. - En production, le microservice de scoring reçoit une transaction, appelle
get_online_features(card_id_hash=...), obtient les sept variables en 4 ms, et les passe au modèle.
La même définition a été consommée deux fois. C'est le contrat.
Place dans l'architecture MLOps
Le magasin n'est pas isolé. Il se branche à trois voisins :
- Amont : le socle de données (lac, entrepôt, flux Kafka) alimente les sources.
- Aval côté entraînement : le magasin fournit les tables d'apprentissage au pipeline d'entraînement (cours 20).
- Aval côté service : le magasin fournit les variables au service d'inférence, qui héberge le modèle (cours 21 à 23 selon la plateforme).
La bonne question, avant d'adopter un magasin, n'est pas « pouvons-nous le faire fonctionner ? » — c'est « quelle limite du magasin va nous coincer en premier ? ». Les modules suivants la posent, un composant à la fois.
Le registre est le seul composant qui doit rester léger et humainement lisible. Si sa taille grossit avec les valeurs de variables, c'est qu'un ingénieur a confondu registre et magasin — l'erreur d'architecture la plus fréquente sur ce sujet.
En résumé
- Un magasin se décompose en six pièces : registre, entités, vues de variables, sources, magasins d'exécution, serveur.
- L'entité est la clé de jointure ; la vue regroupe les variables d'une même entité alimentées par une même source.
- Deux magasins d'exécution — hors ligne pour l'historique, en ligne pour la dernière valeur — assurent la cohérence entre entraînement et service.
- Le serveur expose une seule API, appelée à l'entraînement comme en production ; c'est le contrat qui interdit l'écart.
Module suivant : les deux magasins d'exécution en détail — pourquoi ils existent, comment ils diffèrent, comment ils restent synchrones.