733 lines
18 KiB
Markdown
733 lines
18 KiB
Markdown
<!-- file: docs/architecture/008-DATA_MATERIALIZATION_AND_STORE.md -->
|
|
<!-- version: 1 -->
|
|
|
|
# Data, Materialization et Store
|
|
|
|
## Objet
|
|
|
|
Ce document constitue la sortie principale de `0.0.3-pre.006`.
|
|
|
|
Il définit les frontières durables de données KSP et précise :
|
|
|
|
- les niveaux D1 à D4 ;
|
|
- la séparation `ksp-materializer-api` / `ksp-materializer-lib` / Store ;
|
|
- le rôle backend-agnostic de `ksp-store-api` ;
|
|
- PostgreSQL comme implémentation de référence de `ksp-store-lib` ;
|
|
- provenance et temporalités ;
|
|
- idempotence et versionnement des processors ;
|
|
- replay indépendant par frontière ;
|
|
- sémantique des notifications de données persistées ;
|
|
- stabilité différente entre niveaux structurants et projections spécialisées.
|
|
|
|
Cette tranche ne détaille pas encore le lifecycle complet, batching, concurrence ou supervision des workers/jobs. Ces sujets sont déplacés vers `pre.007`.
|
|
|
|
## Nomenclature des niveaux durables
|
|
|
|
Les niveaux de données ne réutilisent pas N1 à N4, déjà réservés aux couches architecturales KSP.
|
|
|
|
La nomenclature durable retenue est :
|
|
|
|
```text
|
|
D1 — Raw
|
|
D2 — Core canonique
|
|
D3 — Journal de matérialisation générique
|
|
D4 — Projections spécialisées/queryables par domaine
|
|
```
|
|
|
|
Flux général :
|
|
|
|
```text
|
|
source on-chain
|
|
|
|
|
v
|
|
ksp-worker-raw-retriever
|
|
|
|
|
v
|
|
D1 Raw
|
|
|
|
|
v
|
|
ksp-worker-core-processor
|
|
|
|
|
v
|
|
D2 Core canonique
|
|
|
|
|
v
|
|
ksp-worker-generic-materializer
|
|
|
|
|
v
|
|
D3 journal générique
|
|
|
|
|
v
|
|
ksp-worker-domain-projector
|
|
|
|
|
v
|
|
D4 projections de domaine
|
|
```
|
|
|
|
Le nom `ksp-worker-domain-projector` reste révisable ; la frontière D3 -> D4 est, elle, retenue.
|
|
|
|
# D1 — Raw
|
|
|
|
## Mission
|
|
|
|
D1 conserve l'acquisition suffisamment fidèlement pour permettre un nouveau processing sans redemander la donnée à la blockchain lorsque l'information nécessaire a déjà été capturée.
|
|
|
|
`raw` ne signifie pas nécessairement « enveloppe propriétaire du provider conservée sans aucune normalisation ». Le transport peut normaliser ses différentes sources vers des modèles KSP homogènes.
|
|
|
|
La persistance D1 doit toutefois rester **lossless pour les besoins de replay couverts** : toute information nécessaire à la reconstruction du Core doit être conservée, y compris le contenu brut et la provenance utile.
|
|
|
|
## Frontière transport -> D1
|
|
|
|
```text
|
|
Solana RPC ------\
|
|
Helius -----------+--> modèle homogène ksp-onchain-transport-lib
|
|
Yellowstone ------/
|
|
|
|
|
v
|
|
conversion explicite
|
|
|
|
|
v
|
|
DTO D1 de ksp-store-api
|
|
```
|
|
|
|
`ksp-onchain-transport-lib` ne dépend ni de `ksp-store-api` ni de `ksp-store-lib`.
|
|
|
|
La conversion appartient au composant de composition, principalement `ksp-worker-raw-retriever` ou `ksp-job-backfill`.
|
|
|
|
## Provenance D1
|
|
|
|
Selon la catégorie de donnée, D1 doit pouvoir conserver notamment :
|
|
|
|
- réseau ;
|
|
- identité blockchain : slot, signature, pubkey ou autre identifiant applicable ;
|
|
- contenu raw/replayable ;
|
|
- provider ;
|
|
- transport/source ;
|
|
- rôle d'acquisition : live, backfill, import ou autre ;
|
|
- instant d'observation/acquisition ;
|
|
- instant de persistence ;
|
|
- hash/identité d'idempotence ;
|
|
- informations de pagination/capture nécessaires à la reprise lorsque pertinentes.
|
|
|
|
`block_time` reste optionnel et n'est jamais inventé lorsqu'il n'est pas fourni ou reconstructible de manière fiable.
|
|
|
|
# D2 — Core canonique
|
|
|
|
## Mission
|
|
|
|
D2 contient les faits canoniques Solana produits à partir de D1.
|
|
|
|
Le Core doit être suffisamment durable et général pour être rejoué vers les matérialisations futures sans repasser par l'acquisition ou le décodage raw.
|
|
|
|
Conceptuellement, D2 peut accueillir notamment :
|
|
|
|
- transactions canoniques ;
|
|
- instructions top-level ;
|
|
- instructions CPI ;
|
|
- comptes/états canoniques ;
|
|
- résultats de décodage Program ouverts ;
|
|
- événements/return data lorsqu'ils sont supportés ;
|
|
- autres faits génériques Solana qui appartiennent réellement au Core.
|
|
|
|
## Top-level / CPI
|
|
|
|
Les instructions top-level et CPI restent des faits distincts.
|
|
|
|
Leur modèle peut partager des champs, mais KSP ne doit pas les fusionner artificiellement lorsque leurs invariants ou requêtes diffèrent.
|
|
|
|
La séparation physique exacte sera décidée dans la première release Store.
|
|
|
|
## Frontière D1 -> D2
|
|
|
|
```text
|
|
DTO D1 store
|
|
|
|
|
v
|
|
ksp-worker-core-processor
|
|
|
|
|
+--> conversion vers entrée ksp-program-api
|
|
|
|
|
+--> ksp-program-lib / extension compatible
|
|
|
|
|
+--> sortie Core ouverte
|
|
|
|
|
v
|
|
conversion vers DTO D2 store
|
|
```
|
|
|
|
`ksp-program-api` / `ksp-program-lib` restent indépendants du Store.
|
|
|
|
## Provenance D2
|
|
|
|
D2 doit pouvoir relier un résultat à :
|
|
|
|
- son input D1 ;
|
|
- l'identité/version du processor ;
|
|
- l'identité/version de l'implémentation Program/decoder lorsque pertinente ;
|
|
- la capacité de décodage utilisée ;
|
|
- un hash de l'input logique ;
|
|
- l'instant de processing/persistence ;
|
|
- l'état de processing lorsqu'un lifecycle durable est nécessaire.
|
|
|
|
# D3 — Journal de matérialisation générique
|
|
|
|
## Mission
|
|
|
|
D3 est un **journal durable**, pas une étape volatile.
|
|
|
|
Il conserve l'équivalent conceptuel obligatoire de l'ancien journal `k_sol_mat_outputs` de `ks-store`, sans figer encore le nom exact de la table KSP.
|
|
|
|
Il doit permettre de répondre à des questions telles que :
|
|
|
|
```text
|
|
quel input D2 ?
|
|
quel materializer ?
|
|
quelle version ?
|
|
quel output logique ?
|
|
quel type/domaine ?
|
|
quel hash ?
|
|
quel instant ?
|
|
quel état courant/superseded/failed/replay ?
|
|
```
|
|
|
|
D3 permet notamment de reconstruire D4 après évolution d'un projector sans refaire D1 -> D2 ou D2 -> D3.
|
|
|
|
## Frontière D2 -> D3
|
|
|
|
```text
|
|
D2 Core
|
|
|
|
|
v
|
|
ksp-worker-generic-materializer
|
|
|
|
|
+--> conversion vers ksp-materializer-api
|
|
|
|
|
+--> GenericMaterializer
|
|
|
|
|
v
|
|
generic materialization output
|
|
|
|
|
v
|
|
conversion vers DTO D3 store
|
|
```
|
|
|
|
Les noms Rust exacts ne sont pas figés.
|
|
|
|
## Extensibilité externe
|
|
|
|
Une implémentation externe de materializer doit pouvoir produire un output générique compatible avec D3 sans exiger une nouvelle table PostgreSQL spécialisée.
|
|
|
|
Cela rend possible :
|
|
|
|
```text
|
|
external materializer
|
|
|
|
|
v
|
|
ksp-materializer-api
|
|
|
|
|
v
|
|
D3 journal générique
|
|
```
|
|
|
|
avant une éventuelle intégration officielle complète en D4.
|
|
|
|
# D4 — Projections spécialisées de domaine
|
|
|
|
## Mission
|
|
|
|
D4 contient les représentations optimisées pour les requêtes métier, analytiques et produit.
|
|
|
|
Exemples futurs :
|
|
|
|
- assets/tokens ;
|
|
- metadata d'assets/tokens ;
|
|
- Solana Program Metadata ;
|
|
- liquidity pools ;
|
|
- order books ;
|
|
- swaps/trades ;
|
|
- prices/volumes ;
|
|
- positions ;
|
|
- autres projections de domaine.
|
|
|
|
## Faits canoniques, pas familles par protocole
|
|
|
|
D4 reste organisé par **fait canonique**, pas par Program ID/protocole.
|
|
|
|
Éviter par défaut :
|
|
|
|
```text
|
|
meteora_pools
|
|
raydium_pools
|
|
orca_pools
|
|
```
|
|
|
|
au profit d'une projection canonique telle que :
|
|
|
|
```text
|
|
liquidity_pools
|
|
```
|
|
|
|
avec provenance/identité du protocole lorsque nécessaire.
|
|
|
|
La même règle s'applique aux swaps, order books et autres faits pouvant être normalisés.
|
|
|
|
## Metadata
|
|
|
|
Les metadata d'assets/tokens constituent une famille canonique commune pouvant recevoir des données provenant notamment :
|
|
|
|
- Metaplex Token Metadata ;
|
|
- Token-2022 Metadata.
|
|
|
|
Solana Program Metadata reste une projection distincte car le domaine fonctionnel est différent.
|
|
|
|
## Nouvelle projection externe
|
|
|
|
Une nouvelle projection D4 relationnelle implique nécessairement un contrat de persistence et une implémentation backend.
|
|
|
|
KSP ne masque pas cette réalité derrière une API de matérialisation « magique ».
|
|
|
|
Une extension externe peut fonctionner jusqu'à D3 sans migration Store spécialisée. Pour obtenir une nouvelle projection D4 officielle, il faut intégrer :
|
|
|
|
- le contrat DTO/repository approprié dans `ksp-store-api` ;
|
|
- la migration/repository PostgreSQL dans `ksp-store-lib` ;
|
|
- la conversion/projector appropriée dans le composant de processing.
|
|
|
|
Un mécanisme de backend/projection entièrement externe pourra être étudié seulement si un besoin concret apparaît.
|
|
|
|
# Stabilité des niveaux durables
|
|
|
|
La direction retenue est :
|
|
|
|
```text
|
|
D1 — fortement stable
|
|
D2 — fortement stable
|
|
D3 — fortement stable
|
|
D4 — volontairement plus évolutif
|
|
```
|
|
|
|
Après stabilisation de la première série Store réelle, les contrats D1/D2/D3 doivent changer seulement en cas :
|
|
|
|
- d'erreur structurelle ;
|
|
- d'omission majeure ;
|
|
- de nécessité de compatibilité impossible à résoudre additivement.
|
|
|
|
D4 peut évoluer plus librement lorsque de nouveaux décodeurs, materializers ou produits révèlent des besoins queryables supplémentaires.
|
|
|
|
# `ksp-materializer-api`
|
|
|
|
## Rôle
|
|
|
|
Une seule crate publique est retenue :
|
|
|
|
```text
|
|
ksp-materializer-api
|
|
```
|
|
|
|
Elle expose les contrats de matérialisation réutilisables sans dépendre du Store.
|
|
|
|
Deux capacités conceptuelles doivent pouvoir être distinguées :
|
|
|
|
```text
|
|
GenericMaterializer
|
|
DomainProjector
|
|
```
|
|
|
|
Les noms exacts restent à valider.
|
|
|
|
### GenericMaterializer
|
|
|
|
Transforme une représentation Core/runtime en output générique persistable en D3.
|
|
|
|
### DomainProjector
|
|
|
|
Transforme un ou plusieurs inputs génériques/canoniques en représentation spécialisée de domaine destinée à D4.
|
|
|
|
Le second contrat peut évoluer en fonction des premiers cas réels. La séparation des responsabilités est plus importante que le nom du trait.
|
|
|
|
## Dépendances
|
|
|
|
```text
|
|
ksp-materializer-api
|
|
-> ksp-core-lib
|
|
-> ksp-program-api
|
|
```
|
|
|
|
Pas de dépendance vers Store.
|
|
|
|
# `ksp-materializer-lib`
|
|
|
|
`ksp-materializer-lib` contient les implémentations officielles KSP de `ksp-materializer-api`.
|
|
|
|
Organisation conceptuelle possible :
|
|
|
|
```text
|
|
generic/
|
|
...
|
|
|
|
domain/
|
|
metadata/
|
|
token/
|
|
dex/
|
|
...
|
|
```
|
|
|
|
La structure finale suivra les règles de domaine établies avec les premières implémentations.
|
|
|
|
Interdictions :
|
|
|
|
```text
|
|
ksp-materializer-lib -X-> ksp-store-api
|
|
ksp-materializer-lib -X-> ksp-store-lib
|
|
```
|
|
|
|
Un materializer transforme ; il ne persiste pas directement.
|
|
|
|
Le worker/job/pipeline spécialisé relie la transformation à la persistence.
|
|
|
|
# `ksp-store-api`
|
|
|
|
## Rôle
|
|
|
|
`ksp-store-api` expose la frontière backend-agnostic de persistence KSP.
|
|
|
|
Il possède les contrats persistants correspondant aux niveaux durables :
|
|
|
|
```text
|
|
raw
|
|
core
|
|
materialization
|
|
domain
|
|
```
|
|
|
|
La structure exacte des modules Rust sera définie avec la première implémentation.
|
|
|
|
`ksp-store-api` ne dépend pas de Program, Materializer ou Transport.
|
|
|
|
## Contrats génériques et contrats de domaine
|
|
|
|
D1/D2/D3 doivent rester génériques et fortement structurants.
|
|
|
|
D4 peut croître progressivement avec les domaines officiellement supportés.
|
|
|
|
Cette différence est volontaire : l'API Store doit pouvoir ajouter de nouvelles projections queryables sans déstabiliser les contrats de replay historiques.
|
|
|
|
# `ksp-store-lib`
|
|
|
|
`ksp-store-lib` est l'implémentation officielle de référence de `ksp-store-api`.
|
|
|
|
PostgreSQL est le backend de référence prévu.
|
|
|
|
La crate possède notamment :
|
|
|
|
- configuration backend/connexion ;
|
|
- pool/transactions backend ;
|
|
- migrations ;
|
|
- repositories ;
|
|
- queries ;
|
|
- persistence D1/D2/D3/D4 ;
|
|
- primitives de backlog/replay nécessaires au backend ;
|
|
- mécanismes PostgreSQL de notification lorsqu'ils sont retenus.
|
|
|
|
Les autres crates KSP ne contournent pas `ksp-store-lib` pour exécuter directement leurs propres opérations PostgreSQL.
|
|
|
|
Un second backend n'est pas créé abstraitement ; il devra justifier l'évolution de l'architecture lorsqu'un besoin réel apparaît.
|
|
|
|
# Temporalités
|
|
|
|
Les temporalités blockchain et locales sont distinctes.
|
|
|
|
Exemples blockchain :
|
|
|
|
```text
|
|
slot
|
|
block_time: Option<...>
|
|
```
|
|
|
|
Exemples locaux :
|
|
|
|
```text
|
|
observed_at
|
|
acquired_at
|
|
persisted_at
|
|
processed_at
|
|
materialized_at
|
|
projected_at
|
|
```
|
|
|
|
Toutes ne doivent pas nécessairement être présentes dans chaque DTO/table. Leur sémantique doit cependant être explicite lorsqu'elles existent.
|
|
|
|
Aucune date locale ne remplace silencieusement un `block_time` absent.
|
|
|
|
# Provenance
|
|
|
|
Un niveau dérivé doit permettre de remonter à son input durable et au processor qui l'a produit.
|
|
|
|
## D1
|
|
|
|
Provenance d'acquisition :
|
|
|
|
- network ;
|
|
- provider/source ;
|
|
- transport ;
|
|
- rôle d'acquisition ;
|
|
- identité blockchain ;
|
|
- temporalités d'observation/persistence.
|
|
|
|
## D2
|
|
|
|
En plus :
|
|
|
|
- input D1 ;
|
|
- processor/decoder identity ;
|
|
- processor/decoder version ;
|
|
- input hash ;
|
|
- processing time/state.
|
|
|
|
## D3
|
|
|
|
En plus :
|
|
|
|
- input D2 ;
|
|
- materializer identity/version ;
|
|
- output identity/type/domain ;
|
|
- output/input hash ;
|
|
- materialization time/state.
|
|
|
|
## D4
|
|
|
|
En plus :
|
|
|
|
- input(s) D3 ou références canoniques explicitement définies ;
|
|
- projector identity/version ;
|
|
- projection time/state.
|
|
|
|
La représentation exacte de la provenance sera conçue pour éviter de répéter inutilement de gros payloads.
|
|
|
|
# Idempotence et versionnement
|
|
|
|
Chaque frontière dérivée doit pouvoir rejouer le même input sans créer de doublons logiquement distincts.
|
|
|
|
Principe conceptuel :
|
|
|
|
```text
|
|
same logical input
|
|
+ same processor identity
|
|
+ same processor version
|
|
+ same logical output identity
|
|
= same durable result
|
|
```
|
|
|
|
Une nouvelle version du processor doit pouvoir coexister avec ou superséder le résultat précédent selon la politique du niveau.
|
|
|
|
Le système doit pouvoir représenter selon besoin des états tels que :
|
|
|
|
```text
|
|
current
|
|
superseded
|
|
pending
|
|
failed
|
|
replay
|
|
```
|
|
|
|
Les noms, colonnes et contraintes SQL exacts seront décidés avec la première implémentation.
|
|
|
|
L'idempotence ne doit pas reposer uniquement sur l'espoir qu'une notification soit livrée une seule fois.
|
|
|
|
# Replay
|
|
|
|
Les replays sont séparés par frontière durable :
|
|
|
|
```text
|
|
D1 -> D2
|
|
D2 -> D3
|
|
D3 -> D4
|
|
```
|
|
|
|
Ils doivent pouvoir être exécutés indépendamment.
|
|
|
|
Exemples :
|
|
|
|
- nouveau decoder/Core processor : rejouer D1 -> D2 ;
|
|
- nouveau materializer générique : rejouer D2 -> D3 ;
|
|
- nouvelle version d'un projector : rejouer D3 -> D4.
|
|
|
|
Il ne doit pas être nécessaire de refaire toute la chaîne lorsqu'un niveau inférieur inchangé contient déjà l'information requise.
|
|
|
|
Les replays sont des **jobs bornés**, pas des modes cachés des workers live.
|
|
|
|
Les noms exacts des futurs jobs de replay sont reportés à `pre.007`.
|
|
|
|
# Notifications de données persistées
|
|
|
|
## Contrat
|
|
|
|
`ksp-store-api` est le propriétaire du contrat canonique lorsqu'une notification signifie :
|
|
|
|
> une donnée durable de telle catégorie/référence est disponible.
|
|
|
|
Le même type de donnée utilise le même format de notification quelle que soit son origine :
|
|
|
|
```text
|
|
worker live
|
|
backfill job
|
|
import
|
|
replay
|
|
autre source
|
|
```
|
|
|
|
## Notification != source de vérité
|
|
|
|
Une notification est un **signal de réveil/accélération**, jamais la source de vérité du backlog.
|
|
|
|
Elle peut être :
|
|
|
|
- perdue ;
|
|
- dupliquée ;
|
|
- retardée ;
|
|
- reçue après un restart.
|
|
|
|
Le consumer doit toujours pouvoir reconstruire son travail depuis le Store.
|
|
|
|
Exemple conceptuel :
|
|
|
|
```text
|
|
notification RawAvailable
|
|
|
|
|
v
|
|
wake-up core worker
|
|
|
|
|
v
|
|
store query:
|
|
"quels inputs D1 ne sont pas encore traités
|
|
par CoreProcessor version X ?"
|
|
```
|
|
|
|
Le Store, l'idempotence et les checkpoints durables constituent la vérité.
|
|
|
|
Cette règle évite d'exiger immédiatement un broker exactly-once.
|
|
|
|
## Ordre de publication
|
|
|
|
Une donnée n'est annoncée disponible qu'après persistence réussie :
|
|
|
|
```text
|
|
persist
|
|
commit
|
|
notify
|
|
```
|
|
|
|
et non :
|
|
|
|
```text
|
|
notify
|
|
persist
|
|
```
|
|
|
|
## Payload
|
|
|
|
La notification privilégie une référence durable compacte plutôt que la duplication de tout le payload :
|
|
|
|
- catégorie/type ;
|
|
- réseau ;
|
|
- identifiant/range/cursor durable ;
|
|
- autres informations minimales nécessaires au consumer.
|
|
|
|
Les noms de DTO exacts restent à définir.
|
|
|
|
## Mécanisme de diffusion
|
|
|
|
Le contrat est indépendant du mécanisme.
|
|
|
|
Candidats :
|
|
|
|
```text
|
|
channel in-process
|
|
PostgreSQL LISTEN/NOTIFY
|
|
IPC
|
|
broker externe
|
|
```
|
|
|
|
`ksp-store-lib` peut fournir PostgreSQL LISTEN/NOTIFY comme mécanisme de référence si cela répond au premier besoin.
|
|
|
|
Le choix détaillé des mécanismes, reprise, batching et multi-process est reporté à `pre.007`.
|
|
|
|
# Acquisition live et backfill
|
|
|
|
`ksp-worker-raw-retriever` et `ksp-job-backfill` diffèrent par leur lifecycle/orchestration mais produisent le **même contrat D1** pour la même catégorie de donnée.
|
|
|
|
```text
|
|
live source ----------\
|
|
+--> D1 Raw
|
|
historical backfill --/
|
|
```
|
|
|
|
Cela garantit que le processing downstream ne dépend pas de la manière dont la donnée a été acquise.
|
|
|
|
Le backfill n'est pas un mode historique du worker live.
|
|
|
|
# Workers de processing
|
|
|
|
Les frontières de responsabilité sont désormais :
|
|
|
|
```text
|
|
ksp-worker-raw-retriever
|
|
transport -> D1
|
|
|
|
ksp-worker-core-processor
|
|
D1 -> D2
|
|
|
|
ksp-worker-generic-materializer
|
|
D2 -> D3
|
|
|
|
ksp-worker-domain-projector
|
|
D3 -> D4
|
|
```
|
|
|
|
Chaque worker :
|
|
|
|
- peut utiliser les notifications pour réduire la latence ;
|
|
- doit pouvoir reconstruire son backlog depuis le Store ;
|
|
- écrit seulement le niveau durable dont il est propriétaire ;
|
|
- ne transforme pas silencieusement plusieurs frontières en une étape monolithique.
|
|
|
|
Le détail lifecycle, concurrence, batching, cursors, checkpoints et hot reconfiguration est reporté à `pre.007`.
|
|
|
|
# Projections de trading et autres domaines
|
|
|
|
Le trading reste une priorité produit mais ne détermine pas la structure D1/D2/D3.
|
|
|
|
D4 doit accueillir progressivement des faits canoniques de nombreux domaines :
|
|
|
|
- token ;
|
|
- metadata ;
|
|
- staking ;
|
|
- programmes ;
|
|
- trading/DEX ;
|
|
- autres domaines futurs.
|
|
|
|
Pour le trading, les materializers/projectors doivent normaliser les noms et structures propres aux protocoles vers des faits communs lorsque les invariants le permettent.
|
|
|
|
# Questions laissées ouvertes
|
|
|
|
La première implémentation Store devra encore fixer :
|
|
|
|
- schémas SQL et noms exacts des tables ;
|
|
- clés/idempotence exactes ;
|
|
- représentation des hashes ;
|
|
- états de processing exacts ;
|
|
- temporalités obligatoires/optionnelles par table ;
|
|
- format exact des DTO D1/D2/D3 ;
|
|
- structure des repositories/transactions backend ;
|
|
- pagination/cursors ;
|
|
- conversion u64/slot/PostgreSQL ;
|
|
- stratégie des migrations initiales.
|
|
|
|
`pre.007` doit préciser :
|
|
|
|
- lifecycle des quatre workers ;
|
|
- jobs de replay ;
|
|
- checkpoint/backlog ;
|
|
- batching/concurrence ;
|
|
- mécanisme de notification de référence ;
|
|
- processus/IPC selon les managers retenus.
|