Set the number. Then find the table that holds it.
Every query gets a target: how fast it comes back, how many rows it returns, how fresh they have to be. Kijun builds the table at the scale you're heading for, then diagnoses every missed target and re-lays the table out to fix its cause, until every target holds, under query latency and sustained write load. What comes back is a readable report of which layout won, what it cost, and the trade-offs. Apache Iceberg today, more engines next.
The benchmark run
The layout is how the target gets met. It is not the thing you were promised. Run the queries you're about to ship against data built to your own profile, not your staging snapshot and not TPC-DS, and Kijun diagnoses each miss and reworks the layout until every query is inside its own target.
- Bring the table, your queries, and your targets.Already have the table? Upload its metadata as a zip and Kijun reads your column types, cardinalities, null rates, ranges, row counts and arrival rate straight out of it. Building something new? Describe it instead. Either way you then say what each query needs: how fast it must return, how many rows it should return, how fresh the data has to be, and the scale you're growing into.
- Kijun builds the table at production scale.Synthetic data built to the profile you gave it, including the shape of your heavy accounts against the long tail. Materialized as real Parquet and real Iceberg metadata on Kijun's infrastructure. No copy of your data, because none is needed.
- We run your workload across candidate layouts.Every query you defined, measured against the target you gave it, on every candidate. Two tests per layout: query latency, and sustained commit load, where the write test ramps from a starting rate until commits begin to conflict, so what you get back is the ceiling rather than a pass mark. What changed between one candidate and the next is Kijun's problem, not yours. Measured on AWS Athena, on Kijun's own infrastructure. Nothing is read from or written to your storage.
- You get a readable report: which layout won, what it cost, the trade-offs.Including the maintenance the winner was measured under: the compaction schedule the result depends on, what it cost during the run, and a monthly projection with the arithmetic shown. Metered, not estimated. Export the winning layout as ready-to-run Iceberg DDL and the results as CSV or JSON. The layout is table metadata, so it goes with the table to whatever reads it next: Trino, Spark, Flink.
- Then check the answer yourself.The tables Kijun measured are real and they are still there when the run ends. Open the playground and query them: the winner, the ones that lost, or the winner grown to next year's volume. Read-only, same engine, same tables.
2.0B rows · 18 cols · 11min
candidate · measure · repeat
winning layout · export
Synthetic data, with your distributions.
A query plan is shaped by data statistics, not data itself. The cardinality of a join key, the skew of a partition, the correlation between two columns: these are what the optimizer reads when it picks a plan. Copy production and you copy PII. Generate from a schema alone and you get TPC-DS: synthetic data with synthetic statistics, which tells you nothing.
So Kijun builds from the profile you gave in step 01, whether it was read out of uploaded metadata or declared in the UI. A few megabytes of shape go in; hundreds of gigabytes of data come out, built on Kijun's own infrastructure. No row, no value, no identifier of yours is involved. This asymmetry is the whole point.
What we don't claim
Kijun is not a privacy-preserving DB clone. We don't pretend a synthetic row "is" a real row with a hat on. Differential privacy is a red herring here, because we never emit anything per-row, only aggregate distributions. If you need to test exact PII flows, you need anonymization, not synthesis. Different problem.
Every round exists to fix a diagnosed miss.
Iceberg's layout space runs to millions of points, and the dimensions interact: a change that helps one query can hurt another. Brute force is not a strategy; it's a budget.
So Kijun doesn't guess the next layout. After each round it works out, per failing query, what in the layout caused the miss, and the next candidate exists to remove that cause. The worst misses get fixed first, a fix that was already tried is never tried twice, and a layout that fixes one query by breaking another does not survive the next round.
The loop is capped at four rounds. When every query holds its target it stops early, and the verdict is MET_SLA. When the cap arrives first, you get the best measured layout with the gap on record: the verdict says BEST_AVAILABLE, never a silent pass. Either way the round history is stored on the run and you can read it back: which layout each round measured, what changed between rounds, and what that did to every query.
First a readable report. Then three exports.
What you get when a run finishes is a clean report in the UI: which layout won, what it cost, and the trade-offs across query latency and write load. That's the deliverable you act on. On top of it, three exports, every one read back from the finished run. Nothing is recomputed, re-run or estimated at export time.
Iceberg DDL. The winning configuration as ready-to-run CREATE TABLE statements, one per table plus a combined .sql. A run that never settled on a winner has no DDL export: Kijun refuses to hand you a table nobody chose.
Results as JSON. The exact payload the Results screen reads, so the file and the screen can never disagree. Abridged:
{
"status": "COMPLETED",
"winningConfigName": "cfg_3",
"winningConfigVerdict": "MET_SLA",
"params": {
"tableCount": 3, "configurationsTested": 4,
"totalRows": 2_104_886_412, "writeMeasurementEnabled": true
},
"perTable": [{
"tableName": "orders",
"layoutLabel": "day(event_ts) · bucket(16, customer_id)",
"writeVerdict": { "ceilingObserved": true, "ceilingMultiplier": 2.5,
"monthsUntilCeiling": 14 }
}, …],
"slaCompliance": [{
"queryName": "orders_by_customer", "targetLatencyMs": 1500,
"actualTotalTimeMs": 1120, "bytesScanned": 18_412_986_368,
"meetsSla": true, "outcomeKind": "PASS"
}, …],
"maintenance": {
"projectedMonthlyCost": { "measuredCostPerRunUsd": 0.42,
"runsPerMonth": 96, "monthlyCostUsd": 40.32, "monthDays": 30 }
}
}
Results as CSV. Two sections: one row per table (layout, rows, bytes, files, partitions) and one row per query (target, measured latency, bytes scanned, verdict). An unmeasured value is an empty cell, never a zero: zero is a measurement, and these are not.
On the roadmap (not built yet): a signed, content-addressed baseline built from these exports, for your monitoring stack and CI/CD pipeline to consume as ground truth.
Your data never moves.
A benchmark run executes end to end on Kijun's own infrastructure. Nothing of Kijun's runs in your environment: there is nothing to install, and no connection to open. The only thing that crosses the boundary is the profile, a few megabytes of shape. Rows, values and identifiers never do, and nothing is read from your storage or written to it.
What's shipped
On the roadmap, not built yet
Everything in this list is unbuilt. None of it is available today, at any price, in any plan.
What the run leaves behind.
Today's deliverable is the report and its exports: the measured run, the winning layout, the maintenance plan it was measured under, and ready-to-run Iceberg DDL. Dated, reproducible, and yours to keep. A signed, content-addressed baseline you can diff against later is on the roadmap.
On the roadmap (not built yet): your observability stack and CI/CD pipeline consume that baseline as ground truth, so a regression surfaces as a verifiable diff. The same workflow you run for unit tests, but for database performance.
See pricing →