Plateforme
Plateforme
Projection des réglages
Modèle de lecture Valkey : organisation des hashes, flux d'événements et handshake de reproject qui reconstruit l'état.
Sur cette page
Le hot path réagit à un message de chat et cette lecture ne peut pas toucher MySQL. Le projector (app/projector) maintient dans Valkey un modèle de lecture dénormalisé contenant tout ce dont le hot path a besoin sur une chaîne, lisible en un aller-retour. La projection est un cache, jamais la source de vérité ; MySQL peut la reconstruire à tout moment. L’organisation des clés se trouve dans internal/projection.
Organisation
Un hash par utilisateur, settings:<user_id>, lisible avec un seul HGETALL. Le TTL par défaut est de 24 heures, et une écriture ne raccourcit jamais un TTL existant plus long.
settings:<user_id>
status free | paid | vip
active 0 | 1
banned 0 | 1
locale langue de l'interface
commands_page_hidden 0 | 1
live 0 | 1
live_ver version de l'indicateur live
module:<name>:enabled 0 | 1
module:<name>:config JSON brut
module:<name>:revision jeton de concurrence optimiste
modules:projected marqueur : la section des modules est complète
command:<name> vue JSON de la commande
cmdalias:<alias> pointeur vers la commande
commands:projected marqueur : la section des commandes est complète
fetch:<name> définition de fetch personnalisé
fetches:projected marqueur : la section des fetch est complète
stream:*, streamctr:* titre, jeu, spectateurs et compteurs par stream
Les lecteurs n’analysent que le champ dont ils ont réellement besoin. Les noms de module sont validés à la frontière d’écriture (lettres minuscules, chiffres, tiret bas, tiret) précisément parce que le nom est intégré au champ : un deux-points dans un nom pourrait sinon forger un autre champ. Un marqueur *:projected distingue une section vide d’une section jamais chargée, si bien qu’une liste de commandes vide ne déclenche pas de lectures répétées chez le propriétaire.
D’autres clés accompagnent le hash : un enregistrement d’admission par utilisateur qui porte l’incarnation du compte et les barrières de révision, ainsi que les clés de compteurs en direct et de classements (ctr:live:<id>, ctr:board:v2:<name>). Sesame possède la clé versionnée live:<id> ; le projector ne l’écrit jamais.
Flux en direct
Le projector consomme les événements de changement des utilisateurs, modules et commandes, les événements de compteurs de fidélité et les événements d’état de stream via un groupe de files durable : chaque événement est donc appliqué une fois et le consommateur conserve sa position après un redémarrage. Chaque handler valide le contenu, puis l’écrit via un script qui vérifie l’incarnation du compte et la révision : un événement obsolète ou rejoué ne peut pas écraser un état plus récent, et les anciens événements d’un compte supprimé ne peuvent pas le faire revivre. La suppression d’un utilisateur marque le compte comme supprimé et retire le hash. L’invalidation du cache n’est publiée qu’après la réussite de l’écriture dans le magasin.
Reconstruction et réparation
Une section absente ou expirée converge sans que le projector lise jamais le schéma d’un autre service :
- Hydratation. Quand une lecture trouve une section absente, l’hydrateur du projector récupère l’instantané du propriétaire via les sujets
bagel.rpc.internal.projection.*et écrit la section. Les récupérations sont bornées (8 simultanées par défaut,PROJECTOR_HYDRATION_CONCURRENCY; trois tentatives chacune) et limitées par utilisateur. L’hydratation déclenchée par une requête conserve la section 2 heures (PROJECTOR_QUERY_HYDRATION_TTL) ; celle du passage en direct utilise la valeur par défaut de 24 heures. Le passage en direct déclenche aussi un rafraîchissement complet. - Handshake de reproject. Publier
data.reproject.requestfait rejouer chaque ligne par les services users et modules comme un événement de changement ordinaire, paginé pour ne jamais charger une table entière. Le projector ne publie pas cette requête lui-même ; aucun service du dépôt ne le fait aujourd’hui, et le rôleadmin_buspeut la publier (deploy/messaging/accounts.yaml).
sequenceDiagram
autonumber
participant O as Operator
participant N as NATS JetStream
participant U as users service
participant M as modules service
participant P as Projector
participant V as Valkey
O->>N: publish data.reproject.request
N-->>U: deliver, durable group users
N-->>M: deliver, durable group modules
Note over U,M: one instance per service answers, the rest of the group stays idle
loop pages of 500 rows, cursor on ID
U->>N: publish data.users.changed, full state
end
loop pages of 500 rows, cursor on ID
M->>N: publish data.modules.changed, full state
end
N-->>P: deliver events, durable group projector
P->>V: write settings hash per user, revision-fenced
Note over P,V: a replay racing live changes settles on the latest state
Le handshake sert aussi d’outil de réconciliation : si un événement est un jour manqué au-delà de la fenêtre de rétention JetStream (ADR 0003), la projection reste périmée pour l’utilisateur concerné seulement jusqu’au prochain changement, à la prochaine hydratation ou au prochain reproject.
Comportement en cas de panne
- Valkey indisponible : aucune donnée n’est perdue ; les sections s’hydratent de nouveau depuis les propriétaires au retour de Valkey. Les lecteurs du hot path doivent se dégrader de façon délibérée pendant l’absence, ce qui relève de leur décision et non de celle du projector.
- Projector indisponible : le groupe durable conserve sa position ; au redémarrage, il reprend là où il s’était arrêté, dans la fenêtre de rétention.
- Événement empoisonné : validé, journalisé et acquitté. La projection ne se bloque jamais sur un message mal formé. Les échecs de stockage renvoient une erreur afin que l’événement soit relivré.
- Pourquoi Valkey et non Redis : même protocole, mêmes clients, même modèle de données ; Valkey est le fork sous licence BSD à gouvernance ouverte, ce qui compte pour un logiciel potentiellement autohébergé indéfiniment sur la flotte décrite dans l’ADR 0004.