What we do · expertise

Data & Lakehouse

Data you can query, trace and move.

We design and run open data platforms: open table formats, a query engine decoupled from storage, lineage and catalogue. Reversibility guides every choice — being able to leave without a rewrite.

TrinoApache IcebergMinIOCephAirflowKafkaPostgreSQL
Engagement models
Embedded · Audit & architecture
Table format
Apache Iceberg v2 · Parquet
Engine
Trino — whose client we write
Result transport
Spooling protocol: segments read outside the coordinator
Storage
Ceph or MinIO, on your own hardware
Dependency on us
None, to read your own data

Method

We write the client of the engine we run.

A data platform is judged by what it costs to leave it. As long as the table format, the catalogue and the engine belong to the same vendor, the question “what would a change cost” has no answer — it has a quote. We build the other way round: Iceberg tables sitting on object storage you own, an engine decoupled from that storage, a catalogue readable without a licence.

What we can do beyond the market average starts one floor lower. We do not merely use Trino: we write the Trino client. trino-rust-client is a fork of prusto taken over in December 2024 — Presto support removed, advanced Trino features added — that is forty-five Rust files and 6,374 lines, published under the MIT licence.

The spooling protocol is the example that makes the difference concrete. In the historical protocol, every page of results travels back through the coordinator, serialised as JSON: on a large export, the coordinator carries everyone's bytes, memory and CPU, and other teams' interactive queries suffer for it. With spooling, the coordinator no longer returns rows — it returns segment descriptors: an address on object storage, a row offset, a row count, a size.

The client does the rest, and that is the code opposite. It fetches segments straight from storage, with bounded concurrency, decompresses them — zstd or lz4, depending on what the coordinator announces — then acknowledges each segment so the storage is reclaimed instead of waiting for expiry. Going down to that level is not a library refinement: it is the difference between a bulk export that coexists with the rest of the company's queries and one that takes them down.

This kind of platform handles 100s To (1) at peak on one platform we operate. That figure is an observed peak, not a guaranteed ceiling: it appears here with its scope, and nowhere without it. What the open format and the client do not settle, on the other hand, is the quality of what comes in and the cost of what computes.

The repository this code comes from

trino-rust-client

Trino client in Rust. Fork of prusto taken over in December 2024: Presto support removed, advanced Trino features added, including the spooling protocol.

Files
45
Lines
6,374
Licence
MIT

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

Measured over src/, excluding tests and examples.

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,
    },
}
The type the coordinator returns instead of rows. Two shapes: a short segment is inlined, base64-encoded; a large one is written to object storage and described by its address, its access headers and its ackUri. The metadata carries the row offset, the row count and the size — enough to reassemble the result's order without ever routing it through the coordinator.
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)
            }
        }
    }
Fetching one segment, then acknowledging it. The call to ackUri is what lets the spooling storage release the bytes; a client that skips it lets the bucket fill until expiry. A failed acknowledgement is logged without failing the query: the data is already with the caller, and losing an acknowledgement is not losing a result.

Scope

What we do, and what we do not do.

The deciding criterion is the cost of leaving, calculated on the day you arrive.

What we do

  • Design a platform on open table formats, sitting on object storage you own.
  • Decouple the query engine from storage, so that changing engines stops being a programme.
  • Take over what exists: ETL, document ingestion, OCR, high-volume streams, medallion or data vault modelling.
  • Publish the catalogue, the lineage and access rights, down to column level.
  • Fix the engine and its client where they get in our way, and send the fix back upstream.
  • Write the exit plan at the same time as the entry plan: where the files are, in what format, under what schema, and who can read them without us.

What we do not do

  • We do not resell a proprietary warehouse, and we build nothing that only one engine can read.
  • We quote no volume and no response time before seeing your data and your hardware.
  • We do not build business dashboards. We deliver the platform that feeds them.

Engagements

Two comparable engagements.

Described by the setup that was put in place and by what it produced.

Embedded engagement · ongoing, more than 12 months

National banking group

Engineers embedded in the client's teams, on their repository and their delivery chain. What was done by hand is now described, versioned and reviewed.

KubernetesArgo CDOpenTofuAnsibleGrafanaOpenTelemetry

Advisory and implementation · ongoing

Montpellier computing centre

A Kubernetes base on an immutable OS, described end to end in a GitOps repository that carries fourteen applications. That repository is the only way in: it holds the infrastructure as well as the services running on it.

Talos LinuxKubernetesRook-CephCloudNativePGOpenBaoEnvoy Gatewaycert-managerKServeNVIDIA GPU Operator

See all four engagements

Get in touch

Find out whether your stack is still reversible.

Thirty minutes are enough to place a data stack on a single axis: what it would cost to move off it. We will also say so when the answer is that nothing should change — it has happened, and it does not get invoiced.