Ce que nous faisons · expertise

Data & Lakehouse

Des données que l'on peut interroger, tracer et déplacer.

Nous concevons et exploitons des socles de données ouverts : formats de table ouverts, moteur de requête découplé du stockage, lignée et catalogue. Le critère qui guide chaque choix est la réversibilité — pouvoir partir sans réécrire.

TrinoApache IcebergMinIOCephAirflowKafkaPostgreSQL
Modes adaptés
Régie · Audit & architecture
Format de table
Apache Iceberg v2 · Parquet
Moteur
Trino — dont nous écrivons le client
Transport des résultats
Protocole de spooling : segments lus hors coordinateur
Stockage
Ceph ou MinIO, sur votre matériel
Dépendance à nous
Aucune pour lire vos données

Méthode

Nous écrivons le client du moteur que nous exploitons.

Un socle de données se juge à ce qu'il en coûte d'en sortir. Tant que le format de table, le catalogue et le moteur appartiennent au même fournisseur, la question « combien coûterait un changement » n'a pas de réponse : elle a un devis. Nous construisons dans l'autre sens — des tables Iceberg posées sur un stockage objet que vous possédez, un moteur découplé de ce stockage, un catalogue lisible sans licence.

Ce que nous savons faire de plus que la moyenne du marché commence un étage plus bas. Nous n'utilisons pas seulement Trino : nous écrivons le client Trino. trino-rust-client est un fork de prusto repris en décembre 2024 — support Presto retiré, fonctionnalités Trino avancées ajoutées — soit quarante-cinq fichiers Rust et 6 374 lignes, publiées sous licence MIT.

Le protocole de spooling est l'exemple qui rend la nuance concrète. Dans le protocole historique, chaque page de résultat remonte par le coordinateur, sérialisée en JSON : sur un gros export, c'est le coordinateur qui porte les octets, la mémoire et le processeur de tout le monde, et les requêtes interactives des autres équipes en pâtissent. Avec le spooling, le coordinateur ne renvoie plus les lignes — il renvoie des descripteurs de segments : une adresse sur le stockage objet, un décalage de ligne, un nombre de lignes, une taille.

Le client fait le reste, et c'est le code ci-contre. Il va chercher les segments directement sur le stockage, en parallèle borné, les décompresse — zstd ou lz4 selon ce que le coordinateur annonce — puis acquitte chaque segment pour que le stockage soit libéré au lieu d'attendre l'expiration. Descendre à ce niveau n'est pas un raffinement de bibliothèque : c'est la différence entre un export massif qui coexiste avec les requêtes du reste de l'entreprise et un export qui les fait tomber.

Ce type de socle traite 100s To (1) en pointe sur une plateforme que nous exploitons. Le chiffre est une pointe observée, pas un plafond garanti : il figure ici avec son périmètre, et nulle part sans lui. Ce que le format ouvert et le client ne règlent pas, en revanche, c'est la qualité de ce qui entre et le coût de ce qui calcule.

Le dépôt d'où vient ce code

trino-rust-client

Client Trino en Rust. Fork de prusto repris en décembre 2024 : support Presto retiré, fonctionnalités Trino avancées ajoutées, dont le protocole de spooling.

Fichiers
45
Lignes
6 374
Licence
MIT

github.com/nudibranches-tech/trino-rust-client

Mesures relevées sur src/, hors tests et exemples.

src/spooling/segment.rs Rust
// Segment is a part of a query result when using the spooling protocol
#[derive(Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum Segment {
    // Inlined segment
    Inlined {
        #[serde(rename = "type")]
        segment_type: String,
        data: String,
        metadata: DataAttributes,
    },
    // Spooled segment
    Spooled {
        #[serde(rename = "type")]
        segment_type: String,
        uri: String,
        #[serde(rename = "ackUri")]
        #[serde(skip_serializing_if = "Option::is_none")]
        ack_uri: Option<String>,
        #[serde(skip_serializing_if = "Option::is_none")]
        headers: Option<HashMap<String, Vec<String>>>,
        metadata: DataAttributes,
    },
}
Le type que le coordinateur renvoie à la place des lignes. Deux formes : un segment court est transmis en ligne, encodé en base64 ; un segment volumineux est déposé sur le stockage objet et décrit par son adresse, ses en-têtes d'accès et son ackUri. Les métadonnées portent le décalage de ligne, le nombre de lignes et la taille — de quoi reconstituer l'ordre du résultat sans jamais le faire transiter par le coordinateur.
src/spooling/fetcher.rs Rust
    /// Fetch a single segment and return the decoded data
    pub async fn fetch_segment(&self, segment: &Segment) -> Result<Vec<u8>> {
        match segment {
            Inlined { data, .. } => self.fetch_inline_segment(data).await,
            Segment::Spooled {
                uri,
                ack_uri,
                headers,
                ..
            } => {
                let data = self.fetch_spooled_segment(uri, headers.as_ref()).await?;

                // Acknowledge the segment if ackUri is provided
                if let Some(ack) = ack_uri {
                    if let Err(e) = self.acknowledge_segment(ack, headers.as_ref()).await {
                        tracing::warn!("Failed to acknowledge segment {}: {}", ack, e);
                    }
                }

                Ok(data)
            }
        }
    }
La lecture d'un segment, puis son acquittement. L'appel à ackUri est ce qui autorise le stockage de spooling à libérer les octets ; un client qui l'oublie laisse le bucket se remplir jusqu'à expiration. L'échec de l'acquittement est journalisé sans faire échouer la requête : les données sont déjà chez l'appelant, perdre un acquittement n'est pas perdre un résultat.

Périmètre

Ce que nous faisons, ce que nous ne faisons pas.

Le critère qui tranche est le coût du départ, calculé le jour de l'entrée.

Ce que nous faisons

  • Concevoir un socle en formats de table ouverts, posé sur un stockage objet que vous possédez.
  • Découpler le moteur de requête du stockage, pour que changer de moteur cesse d'être un projet.
  • Reprendre l'existant : ETL, ingestion documentaire, OCR, flux à haut volume, modélisation medallion ou data vault.
  • Publier le catalogue, la lignée et les droits d'accès, jusqu'au niveau de la colonne.
  • Corriger le moteur et son client là où ils nous gênent, et reverser le correctif en amont.
  • Écrire le plan de sortie en même temps que le plan d'entrée : où sont les fichiers, dans quel format, sous quel schéma, et qui peut les lire sans nous.

Ce que nous ne faisons pas

  • Nous ne revendons pas d'entrepôt propriétaire, et nous ne construisons rien qui ne se lise que par un seul moteur.
  • Nous n'annonçons ni volume ni temps de réponse avant d'avoir vu vos données et votre matériel.
  • Nous ne produisons pas de tableaux de bord métier. Nous livrons le socle qui les alimente.

Missions

Deux engagements comparables.

Décrits par le dispositif mis en place et par ce qu'il a produit.

Régie · en cours, plus de 12 mois

Groupe bancaire national

Des ingénieurs embarqués dans les équipes du client, sur leur dépôt et leur chaîne de production. Ce qui se faisait à la main est décrit, versionné et relu.

KubernetesArgo CDOpenTofuAnsibleGrafanaOpenTelemetry

Conseil et mise en œuvre · en cours

Centre de calcul de Montpellier

Un socle Kubernetes sur système immuable, décrit intégralement dans un dépôt GitOps qui porte quatorze applications. Ce dépôt est le seul point d'entrée : il tient l'infrastructure comme les services qui tournent dessus.

Talos LinuxKubernetesRook-CephCloudNativePGOpenBaoEnvoy Gatewaycert-managerKServeNVIDIA GPU Operator

Voir les quatre missions

Prendre contact

Savoir si votre pile est encore réversible.

Trente minutes suffisent pour situer une pile de données sur un seul axe : ce qu'il en coûterait d'en changer. Nous vous le dirons aussi quand la réponse est qu'il n'y a rien à changer — c'est arrivé, et cela ne se facture pas.