Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,14 @@ Run `make tpcc-dual` to mirror every TPCC statement to an in-memory SQLite datab

Recent stable-run 720-second local comparison on the machine above:

| Backend | TpmC | New-Order p90 | Payment p90 | Order-Status p90 | Delivery p90 | Stock-Level p90 |
| Backend | TpmC | New-Order p90 (µs) | Payment p90 (µs) | Order-Status p90 (µs) | Delivery p90 (µs) | Stock-Level p90 (µs) |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| KiteSQL LMDB | 82871 | 0.001s | 0.001s | 0.001s | 0.002s | 0.001s |
| KiteSQL RocksDB | 40960 | 0.001s | 0.001s | 0.001s | 0.011s | 0.001s |
| SQLite balanced | 51637 | 0.001s | 0.001s | 0.001s | 0.001s | 0.001s |
| SQLite practical | 61424 | 0.001s | 0.001s | 0.001s | 0.001s | 0.001s |
| KiteSQL LMDB | 70315 | 561 | 196 | 208 | 1089 | 1693 |
| KiteSQL RocksDB | 35556 | 747 | 394 | 365 | 10175 | 2125 |
| SQLite balanced | 54797 | 303 | 72 | 52 | 363 | 508 |
| SQLite practical | 44847 | 527 | 146 | 61 | 1248 | 486 |

These rows are from the stable runs on `2026-07-11`; the detailed raw outputs are recorded in [tpcc/README.md](tpcc/README.md).
These rows are from the local run on `2026-09-06–2026-09-07`; latencies are in microseconds and include transaction commit.

#### 👉[check more](tpcc/README.md)

Expand Down
11 changes: 5 additions & 6 deletions scripts/run_tpcc_stable.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,15 +311,14 @@ def extract_tpmc(log_text: str) -> str:


def extract_p90(log_text: str, label: str) -> str:
marker = "<90th Percentile RT (MaxRT)>"
marker = "<Latency Percentile RT in us (MaxRT)>"
if marker not in log_text:
return "-"
block = log_text.split(marker, 1)[1]
for line in block.splitlines():
if label not in line:
continue
parts = line.split()
return parts[2] if len(parts) >= 3 else "-"
columns = [column.strip() for column in line.strip().strip("|").split("|")]
if len(columns) == 3 and columns[0] == label:
return columns[1]
return "-"


Expand Down Expand Up @@ -443,7 +442,7 @@ def write_summary_header(args: argparse.Namespace, summary_file: Path) -> None:
f"sample_interval={args.sample_interval_sec}s"
),
"",
"| Variant | Status | Attempts | Measure Time | TpmC | New-Order p90 | Payment p90 | Order-Status p90 | Delivery p90 | Stock-Level p90 | Notes | Raw Log |",
"| Variant | Status | Attempts | Measure Time | TpmC | New-Order p90 (us) | Payment p90 (us) | Order-Status p90 (us) | Delivery p90 (us) | Stock-Level p90 (us) | Notes | Raw Log |",
"| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- |",
]
)
Expand Down
2 changes: 1 addition & 1 deletion tpcc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ pprof = ["dep:pprof"]
[dependencies]
clap = { version = "4", features = ["derive"] }
chrono = { version = "0.4" }
hdrhistogram = { version = "7", default-features = false }
kite_sql = { path = "..", package = "kite_sql", features = ["rocksdb", "lmdb", "decimal"] }
indicatif = { version = "0.17" }
ordered-float = { version = "4" }
rand = { version = "0.8" }
rust_decimal = { version = "1" }
sqlite = { version = "0.34" }
Expand Down
17 changes: 9 additions & 8 deletions tpcc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,21 @@ The benchmark stores `history.h_date` as `timestamp(6)`, so high-throughput `Pay
- Tips: TPC-C currently runs as a single worker.

## 720s comparison
Local stable-run 720-second comparison on the machine above:
Local 720-second comparison on the machine above:

| Backend | TpmC | New-Order p90 | Payment p90 | Order-Status p90 | Delivery p90 | Stock-Level p90 |
| Backend | TpmC | New-Order p90 (µs) | Payment p90 (µs) | Order-Status p90 (µs) | Delivery p90 (µs) | Stock-Level p90 (µs) |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| KiteSQL LMDB | 82871 | 0.001s | 0.001s | 0.001s | 0.002s | 0.001s |
| KiteSQL RocksDB | 40960 | 0.001s | 0.001s | 0.001s | 0.011s | 0.001s |
| SQLite balanced | 51637 | 0.001s | 0.001s | 0.001s | 0.001s | 0.001s |
| SQLite practical | 61424 | 0.001s | 0.001s | 0.001s | 0.001s | 0.001s |
| KiteSQL LMDB | 70315 | 561 | 196 | 208 | 1089 | 1693 |
| KiteSQL RocksDB | 35556 | 747 | 394 | 365 | 10175 | 2125 |
| SQLite balanced | 54797 | 303 | 72 | 52 | 363 | 508 |
| SQLite practical | 44847 | 527 | 146 | 61 | 1248 | 486 |

- The KiteSQL rows are from `2026-07-11_17-20-24`; the SQLite rows are from `2026-07-11_20-25-01`.
- The stable-run gates were `temp<=65.0C`, `cpu<=20.0%`, `min_cooldown=300s`, `stable_samples=3`, and `sample_interval=10.0s`.
- Run dates: `2026-09-06–2026-09-07`; results: `2026-09-06_19-04-32`. Latency is measured in microseconds and includes commit.
- All rows use `--num-ware 1`, `--max-retry 5`, and TPCC's default 720-second measure time.
- SQLite rows use the `balanced` and `practical` profiles respectively.

## Historical raw outputs — 2026-07-11

### KiteSQL LMDB
```shell
Transaction Summary (elapsed 720.0s)
Expand Down
139 changes: 43 additions & 96 deletions tpcc/src/backend/dual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ use super::{
BackendControl, BackendTransaction, DbParam, KiteSqlPreparedStatement, PreparedStatement,
SimpleExecutor, StatementSpec,
};
use crate::{TpccError, STOCK_LEVEL_DISTINCT_SQL, STOCK_LEVEL_DISTINCT_SQLITE};
use crate::TpccError;
use kite_sql::types::tuple::Tuple;
use kite_sql::types::value::DataValue;
use std::borrow::Cow;
use std::collections::HashMap;

pub struct DualBackend {
kitesql: KiteSqlRocksDbBackend,
Expand Down Expand Up @@ -65,12 +63,8 @@ impl BackendControl for DualBackend {
&self,
specs: &[Vec<StatementSpec>],
) -> Result<Vec<Vec<Self::PreparedStatement<'_>>>, TpccError> {
let sqlite_specs: Vec<Vec<StatementSpec>> = specs
.iter()
.map(|group| group.iter().map(sqlite_statement_spec).collect())
.collect();
let kitesql_groups = self.kitesql.prepare_statements(specs)?;
let sqlite_groups = self.sqlite.prepare_statements(&sqlite_specs)?;
let sqlite_groups = self.sqlite.prepare_statements(specs)?;
let mut groups = Vec::with_capacity(kitesql_groups.len());

for (kitesql_group, sqlite_group) in kitesql_groups.into_iter().zip(sqlite_groups) {
Expand Down Expand Up @@ -130,13 +124,7 @@ impl<'a> BackendTransaction for DualTransaction<'a> {
let sqlite_iter = self.sqlite.execute_raw(&mut statement.sqlite, params)?;

if is_select_sql(&spec) {
if spec.sql == STOCK_LEVEL_DISTINCT_SQL {
let (kitesql_counts, kitesql_len) = collect_kitesql_value_counts(kitesql_iter)?;
let sqlite_rows = collect_sqlite_rows(sqlite_iter)?;
compare_unordered_rows(kitesql_counts, kitesql_len, &sqlite_rows, spec.sql)
} else {
drain_and_compare_ordered(kitesql_iter, sqlite_iter, spec.sql)
}
drain_and_compare_ordered(kitesql_iter, sqlite_iter, spec.sql)
} else {
drain_sqlite_iter(sqlite_iter)?;
drain_kitesql_iter(kitesql_iter)
Expand All @@ -152,6 +140,46 @@ impl<'a> BackendTransaction for DualTransaction<'a> {
self.with_query_nth(statement, params, 0, visitor)
}

fn with_query_all(
&mut self,
statement: &mut Self::PreparedStatement,
params: &[DbParam],
visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>,
) -> Result<(), TpccError> {
let mut rows = Vec::new();
self.kitesql
.with_query_all(&mut statement.kitesql, params, &mut |tuple| {
rows.push(tuple.clone());
Ok(())
})?;
let mut sqlite_rows = Vec::new();
self.sqlite
.with_query_all(&mut statement.sqlite, params, &mut |tuple| {
sqlite_rows.push(tuple.values.clone());
Ok(())
})?;
// SQL without ORDER BY may return the same rows in different orders.
for row in &rows {
let Some(index) = sqlite_rows.iter().position(|values| *values == row.values) else {
return Err(TpccError::BackendMismatch(format!(
"Result mismatch for SQL: {}",
statement.spec.sql
)));
};
sqlite_rows.swap_remove(index);
}
if !sqlite_rows.is_empty() {
return Err(TpccError::BackendMismatch(format!(
"SQLite returned extra rows for SQL: {}",
statement.spec.sql
)));
}
for row in &rows {
visitor(row)?;
}
Ok(())
}

fn with_query_nth(
&mut self,
statement: &mut Self::PreparedStatement,
Expand All @@ -164,14 +192,6 @@ impl<'a> BackendTransaction for DualTransaction<'a> {
let kitesql_iter = self.kitesql.execute_raw(&mut statement.kitesql, params)?;
let sqlite_iter = self.sqlite.execute_raw(&mut statement.sqlite, params)?;

if spec.sql == STOCK_LEVEL_DISTINCT_SQL {
let (kitesql_counts, kitesql_len) = collect_kitesql_value_counts(kitesql_iter)?;
let sqlite_rows = collect_sqlite_rows(sqlite_iter)?;
compare_unordered_rows(kitesql_counts, kitesql_len, &sqlite_rows, spec.sql)?;
let tuple = sqlite_rows.get(n).ok_or(TpccError::EmptyTuples)?;
return visitor(tuple);
}

if !is_select_sql(&spec) {
drain_sqlite_iter(sqlite_iter)?;
return with_kitesql_nth(kitesql_iter, n, visitor);
Expand Down Expand Up @@ -210,27 +230,6 @@ fn drain_kitesql_iter<T: kite_sql::storage::Transaction>(
Ok(())
}

fn collect_sqlite_rows(mut iter: SqliteResult<'_, '_>) -> Result<Vec<Tuple>, TpccError> {
let mut rows = Vec::new();
while let Some(row) = iter.next() {
rows.push(row?);
}
Ok(rows)
}

fn collect_kitesql_value_counts<T: kite_sql::storage::Transaction>(
mut iter: KiteSqlTxnResult<'_, T>,
) -> Result<(HashMap<Vec<DataValue>, usize>, usize), TpccError> {
let mut counts = HashMap::new();
let mut len = 0;
while let Some(()) = iter.with_next_tuple(|tuple| {
*counts.entry(tuple.values.clone()).or_insert(0) += 1;
len += 1;
Ok(())
})? {}
Ok((counts, len))
}

fn with_kitesql_nth<T: kite_sql::storage::Transaction>(
mut iter: KiteSqlTxnResult<'_, T>,
n: usize,
Expand Down Expand Up @@ -343,58 +342,6 @@ fn drain_and_compare_ordered<T: kite_sql::storage::Transaction>(
}
}

fn compare_unordered_rows(
mut counts: HashMap<Vec<DataValue>, usize>,
kitesql_len: usize,
sqlite_rows: &[Tuple],
sql: &'static str,
) -> Result<(), TpccError> {
if kitesql_len != sqlite_rows.len() {
return Err(TpccError::BackendMismatch(format!(
"SQLite returned different row count for SQL: {}",
sql
)));
}

for row in sqlite_rows {
match counts.get_mut(&row.values) {
Some(count) => {
if *count == 1 {
counts.remove(&row.values);
} else {
*count -= 1;
}
}
None => {
return Err(TpccError::BackendMismatch(format!(
"SQLite returned different distinct set for SQL: {}",
sql
)));
}
}
}

if counts.is_empty() {
Ok(())
} else {
Err(TpccError::BackendMismatch(format!(
"SQLite returned different distinct set for SQL: {}",
sql
)))
}
}

fn sqlite_statement_spec(spec: &StatementSpec) -> StatementSpec {
if spec.sql == STOCK_LEVEL_DISTINCT_SQL {
StatementSpec {
sql: STOCK_LEVEL_DISTINCT_SQLITE,
result_types: spec.result_types,
}
} else {
spec.clone()
}
}

fn is_select_sql(spec: &StatementSpec) -> bool {
spec.sql
.trim_start()
Expand Down
11 changes: 11 additions & 0 deletions tpcc/src/backend/kitesql_lmdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ impl<'a> BackendTransaction for KiteSqlLmdbTransactionWrapper<'a> {
.ok_or(TpccError::EmptyTuples)
}

fn with_query_all(
&mut self,
statement: &mut Self::PreparedStatement,
params: &[DbParam],
visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>,
) -> Result<(), TpccError> {
let mut iter = self.execute_raw(statement, params)?;
while iter.with_next_tuple(|tuple| visitor(tuple))?.is_some() {}
Ok(())
}

fn with_query_nth(
&mut self,
statement: &mut Self::PreparedStatement,
Expand Down
11 changes: 11 additions & 0 deletions tpcc/src/backend/kitesql_rocksdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,17 @@ impl<'a, S: Storage> BackendTransaction for KiteSqlRocksTransaction<'a, S> {
.ok_or(TpccError::EmptyTuples)
}

fn with_query_all(
&mut self,
statement: &mut Self::PreparedStatement,
params: &[DbParam],
visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>,
) -> Result<(), TpccError> {
let mut iter = self.execute_raw(statement, params)?;
while iter.with_next_tuple(|tuple| visitor(tuple))?.is_some() {}
Ok(())
}

fn with_query_nth(
&mut self,
statement: &mut Self::PreparedStatement,
Expand Down
7 changes: 7 additions & 0 deletions tpcc/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ pub trait BackendTransaction {
visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>,
) -> Result<(), TpccError>;

fn with_query_all(
&mut self,
statement: &mut Self::PreparedStatement,
params: &[DbParam],
visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>,
) -> Result<(), TpccError>;

fn with_query_nth(
&mut self,
statement: &mut Self::PreparedStatement,
Expand Down
13 changes: 13 additions & 0 deletions tpcc/src/backend/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,19 @@ impl<'a> BackendTransaction for SqliteTransaction<'a> {
visitor(&tuple)
}

fn with_query_all(
&mut self,
statement: &mut Self::PreparedStatement,
params: &[DbParam],
visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>,
) -> Result<(), TpccError> {
let iter = self.execute_raw(statement, params)?;
for row in iter {
visitor(&row?)?;
}
Ok(())
}

fn with_query_nth(
&mut self,
statement: &mut Self::PreparedStatement,
Expand Down
Loading
Loading