252 lines
12 KiB
Markdown
252 lines
12 KiB
Markdown
<!-- file: crates/ksp-store-postgres-lib/USAGE.md -->
|
||
<!-- version: 9 -->
|
||
|
||
# Utilisation de ksp-store-postgres-lib
|
||
|
||
## 1. Quand utiliser cette crate directement
|
||
|
||
Le consumer applicatif normal utilise `ksp-store-lib`.
|
||
|
||
Une dépendance directe à `ksp-store-postgres-lib` est réservée aux composants qui implémentent ou testent le bridge physique PostgreSQL. La crate backend ne doit pas devenir une façade parallèle.
|
||
|
||
Un tel composant doit déclarer explicitement le backend et `ksp-store-api`, car `PostgresBackendSettings::new` reçoit le `RawNetworkId` backend-neutral sans le réexporter :
|
||
|
||
```toml
|
||
[dependencies]
|
||
ksp-store-api = { path = "../ksp-store-api" }
|
||
ksp-store-postgres-lib = { path = "../ksp-store-postgres-lib" }
|
||
```
|
||
|
||
## 2. Construire le bridge physique
|
||
|
||
`PostgresBackendSettings` reçoit des valeurs déjà possédées et validées par la couche appelante. L'URI est sensible et son `Debug` est redacted.
|
||
|
||
```rust
|
||
fn backend_settings(
|
||
network: ksp_store_api::RawNetworkId,
|
||
connection_uri: std::string::String,
|
||
) -> ksp_store_postgres_lib::PostgresBackendSettings {
|
||
return ksp_store_postgres_lib::PostgresBackendSettings::new(
|
||
network,
|
||
connection_uri,
|
||
8,
|
||
std::time::Duration::from_secs(10),
|
||
std::time::Duration::from_secs(5),
|
||
std::time::Duration::from_secs(10),
|
||
std::time::Duration::from_secs(5),
|
||
ksp_store_postgres_lib::PostgresBackendTlsMode::VerifyFull,
|
||
true,
|
||
std::time::Duration::from_secs(30),
|
||
std::time::Duration::from_secs(10),
|
||
);
|
||
}
|
||
```
|
||
|
||
Le backend reçoit un seul `RawNetworkId`. Une instance physique n'est pas un routeur multi-réseau.
|
||
|
||
## 3. Ouvrir, sonder et fermer
|
||
|
||
```rust
|
||
async fn use_backend(
|
||
settings: ksp_store_postgres_lib::PostgresBackendSettings,
|
||
) -> std::result::Result<(), ksp_store_postgres_lib::PostgresBackendError> {
|
||
let backend = ksp_store_postgres_lib::PostgresBackend::open(settings).await;
|
||
let backend = match backend {
|
||
std::result::Result::Ok(value) => value,
|
||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||
};
|
||
|
||
let runtime = backend.runtime_snapshot();
|
||
let _capacity = runtime.pool_capacity();
|
||
let _size = runtime.pool_size();
|
||
let _available = runtime.pool_available();
|
||
let _waiting = runtime.pool_waiting();
|
||
|
||
let health = backend.health().await;
|
||
let _ready = health.ready();
|
||
let _migration_version = health.migration_version();
|
||
let _pending = health.pending_migration_count();
|
||
let _safe_error_kind = health.last_error_kind();
|
||
|
||
return backend.close(std::time::Duration::from_secs(5)).await;
|
||
}
|
||
```
|
||
|
||
`open` prouve la connexion et le bootstrap avant de retourner. `close` ferme le pool puis attend son drain dans la deadline fournie.
|
||
|
||
## 4. Choisir le mode TLS
|
||
|
||
### `VerifyFull`
|
||
|
||
À utiliser pour les connexions PostgreSQL protégées :
|
||
|
||
```rust
|
||
ksp_store_postgres_lib::PostgresBackendTlsMode::VerifyFull
|
||
```
|
||
|
||
Le backend charge les roots système et vérifie certificat + identité serveur. Il rejette une configuration ne fournissant pas d'identité vérifiable.
|
||
|
||
### `Disabled`
|
||
|
||
```rust
|
||
ksp_store_postgres_lib::PostgresBackendTlsMode::Disabled
|
||
```
|
||
|
||
Ce mode désactive explicitement TLS. Il ne doit être utilisé que lorsque la topologie de déploiement justifie clairement une connexion non chiffrée.
|
||
|
||
La valeur typée choisie par KSP prime sur les paramètres SSL de l'URI.
|
||
|
||
## 5. Bootstrap et migrations
|
||
|
||
Le backend embarque son propre moteur de migrations. La migration historique V000 est désormais matérialisée par :
|
||
|
||
```text
|
||
migrations/v000_bootstrap/tables/001_ksp_store_schema_migrations.sql
|
||
```
|
||
|
||
Les migrations suivantes conservent une version logique unique tout en séparant leurs ressources physiques par famille sous `migrations/vNNN_name/{tables,constraints,indexes}/`. Le bootstrap maintient :
|
||
|
||
```text
|
||
ksp_store_schema_migrations
|
||
version
|
||
name
|
||
checksum SHA-256
|
||
```
|
||
|
||
Le runner est transactionnel et sérialisé par advisory transaction lock. Une divergence de checksum/nom/version ou une history plus récente est terminale ; aucun down migration automatique n'est exécuté.
|
||
|
||
`schema_autocreate` contrôle l'initialisation/adoption du schéma et `schema_autoupdate` les migrations pending ainsi que les réparations additives sûres. Le constructeur legacy `auto_migrate` mappe encore les deux politiques pour compatibilité source.
|
||
|
||
## 6. Classifier les erreurs sans fuite
|
||
|
||
```rust
|
||
match error.kind() {
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::ConfigInvalid => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::ConnectFailed => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::Conflict => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::DataInvalid => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::PoolTimeout => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::HealthFailed => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::MigrationFailed => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::MigrationMismatch => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::PageLimitUnsupported => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::QueryInvalid => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::ReadFailed => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::ReferenceNotFound => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::RetentionCompactionUnsupported => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::SchemaNewer => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::ShutdownTimeout => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::TlsFailed => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::WriteFailed => {}
|
||
ksp_store_postgres_lib::PostgresBackendErrorKind::WrongNetwork => {}
|
||
_ => {}
|
||
}
|
||
|
||
let _safe_phase = error.phase();
|
||
```
|
||
|
||
Ne pas reconstruire un diagnostic utilisateur à partir de l'erreur brute PostgreSQL : cette erreur n'est volontairement pas conservée par le bridge.
|
||
|
||
## 7. Lectures RAW transaction
|
||
|
||
Depuis `0.3.3-pre.004`, un backend ouvert expose quatre lectures backend-specific retournant exclusivement les modèles communs :
|
||
|
||
```rust
|
||
let transaction = backend.get_raw_transaction(&reference).await;
|
||
let observation = backend.get_raw_transaction_observation(&observation_key).await;
|
||
let retention = backend.get_raw_transaction_retention_state(&reference).await;
|
||
let tombstone = backend.get_raw_transaction_tombstone(&reference).await;
|
||
```
|
||
|
||
Le backend ne rend jamais `tokio_postgres::Row`, SQL, SQLSTATE ou valeur de bind. Pour les références réseau-scopées, un mauvais réseau est rejeté avant acquisition d'un client du pool. Une corruption de row est projetée vers `DataInvalid`, un échec physique de lecture vers `ReadFailed`, et les erreurs de pool conservent leur classification bornée existante.
|
||
|
||
`get_raw_transaction` retourne :
|
||
|
||
```text
|
||
Full -> payload chaud
|
||
Archived -> payload archive reconstruit
|
||
Purged -> None
|
||
absent -> None
|
||
```
|
||
|
||
Le tombstone `Purged` reste lisible séparément.
|
||
|
||
## 8. Écritures RAW transaction
|
||
|
||
Depuis `0.3.3-pre.005`, le backend physique expose également :
|
||
|
||
```rust
|
||
let acquisition = backend
|
||
.persist_raw_transaction_acquisition(transaction, observation, mode)
|
||
.await;
|
||
|
||
let observation = backend
|
||
.record_raw_transaction_observation(additional_observation)
|
||
.await;
|
||
```
|
||
|
||
La première opération est atomique : transaction canonique et observation sont toutes deux durables ou aucune ne l'est. Les doublons ne sont pas détectés par une prélecture `has_*` : l'insert unique est tenté directement, puis un conflit relit/verrouille la ligne gagnante et compare son contenu. Une divergence sous la même signature ou la même `observation_key` produit `PostgresBackendErrorKind::Conflict`.
|
||
|
||
Pour une transaction purgée, le mode normal retourne `SkippedPurged/NotRecorded` lorsque le tombstone est compatible. `ForceRehydrate` restaure le payload `Full` puis enregistre l'observation dans la même transaction. `record_raw_transaction_observation` ne crée jamais de canonique : référence absente -> `ReferenceNotFound`, canonique `Purged` -> `NotRecorded`.
|
||
|
||
Les références réseau-scopées sont toujours validées avant `pool.get()`.
|
||
|
||
## 9. Paginer les transactions RAW
|
||
|
||
Depuis `0.3.3-pre.006`, le backend fournit une navigation keyset déterministe :
|
||
|
||
```rust
|
||
let page = backend.list_raw_transactions(&query).await;
|
||
```
|
||
|
||
La query utilise les bornes inclusives de `RawSlotRange`, la direction demandée et le `RawPageLimit` exact. Les lignes `Purged` sont exclues. L'ordre est `(slot, signature)` dans les deux directions et la continuation utilise un cursor opaque de 109 octets lié au réseau, à la direction et aux bornes de la query.
|
||
|
||
Le cursor peut être transmis uniquement à une query ayant le même binding. Un changement de réseau/range/direction ou des bytes hostiles produit `QueryInvalid` avant acquisition du pool. Le backend n'utilise aucun `OFFSET` et ne fournit pas de snapshot transactionnel entre deux pages.
|
||
|
||
La seule limite physique est liée au `LIMIT + 1` PostgreSQL : `requested <= i64::MAX - 1`. Au-delà, `PageLimitUnsupported` est retourné ; la demande n'est jamais ramenée à 100, 500, 1000 ou une autre policy de worker.
|
||
|
||
## 10. Appliquer une transition de rétention
|
||
|
||
Depuis `0.3.3-pre.007`, le backend expose :
|
||
|
||
```rust
|
||
let outcome = backend.transition_raw_transaction_retention(transition).await;
|
||
```
|
||
|
||
Le backend ne choisit jamais lui-même la policy de rétention. Le caller fournit un `RawTransactionRetentionTransition` avec `expected` et `target`; PostgreSQL verrouille le canonical avec `FOR UPDATE`, puis retourne `Applied`, `AlreadyAtTarget` ou `ExpectedStateMismatch` selon l'état réellement observé.
|
||
|
||
Les transitions physiques supportées sont `Full -> Archived` puis `Archived -> Purged`. L'archivage copie le payload exact vers la relation archive avant de retirer les octets chauds, et la purge supprime cette archive puis efface le block time en conservant uniquement le tombstone minimal. Tout est transactionnel : aucun état intermédiaire n'est committé.
|
||
|
||
Une transition impliquant `Compacted` est rejetée avant acquisition du pool avec `PostgresBackendErrorKind::RetentionCompactionUnsupported`. Le code KSP correspondant est `ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED`; aucune compression transparente PostgreSQL n'est présentée comme une représentation compactée KSP.
|
||
|
||
## 11. Capabilities traitées directement
|
||
|
||
Depuis `0.3.3-pre.008`, `PostgresBackend` implémente directement :
|
||
|
||
```text
|
||
RawTransactionRead
|
||
RawTransactionWrite
|
||
RawTransactionObservationRead
|
||
RawTransactionObservationWrite
|
||
RawTransactionRetentionRead
|
||
RawTransactionRetentionWrite
|
||
```
|
||
|
||
Cette conformance est principalement utile aux tests backend et à la façade. Le consumer applicatif normal continue à dépendre de `ksp-store-lib`, qui dispatch les mêmes six capabilities sans exposer `PostgresBackend`.
|
||
|
||
## 12. Ce que cette crate ne permet pas encore
|
||
|
||
La tranche ne fournit pas les capabilities `RawAccount*`. Elles appartiennent à `0.3.4`.
|
||
|
||
## 13. Exécuter la preuve PostgreSQL live RawTransaction
|
||
|
||
Le test `postgres_raw_transaction_live` exige une base PostgreSQL dédiée et vide de toute table KSP gérée. Il lit son URI sur l’entrée standard afin de ne pas contourner Config par une variable d’environnement de test :
|
||
|
||
```bash
|
||
printf '%s\n' '<URI_POSTGRES_DEDIEE>' | cargo test -p ksp-store-postgres-lib --test postgres_raw_transaction_live -- --ignored --nocapture --test-threads=1
|
||
```
|
||
|
||
Le test refuse de démarrer si une table KSP V000/V001 existe déjà. Il ne logge pas l’URI et ne supprime que le schéma qu’il a prouvé absent avant son propre bootstrap.
|
||
|
||
Le gate technique final `0.3.3-pre.011` a rejoué cette preuve sur PostgreSQL 17 avec succès. Pour l'inventaire précis des scénarios et des corrections de compatibilité catalogue, voir [`../../docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md`](../../docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md).
|