Files
khadhroony-solana-project/crates/ksp-store-postgres-lib/USAGE.md
2026-08-30 13:06:33 +02:00

9.3 KiB

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 :

[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.

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

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 :

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

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 :

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 :

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

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::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 :

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 :

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 :

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 :

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. Ce que cette crate ne permet pas encore

La tranche ne fournit pas encore :

transitions de rétention
implémentations complètes des six traits RawTransaction*
capabilities RawAccount*

Ces surfaces sont ajoutées dans les prereleases suivantes avant le dispatch ksp-store-lib.