Skip to content

Commit

Permalink
perf(553): Optimize index joins for incremental evaluation
Browse files Browse the repository at this point in the history
Fixes #553.

Before this change, we would use the same plan for both query and incremental eval.
This is problematic for index joins.
In particular, table sizes are drastically different under incremental eval.
After this change, joins are reordered for incremental eval.
  • Loading branch information
joshua-spacetime committed Nov 15, 2023
1 parent 7058a10 commit 5a23fad
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 22 deletions.
98 changes: 97 additions & 1 deletion crates/core/src/sql/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,15 @@ mod tests {
use std::ops::Bound;

use crate::db::relational_db::tests_utils::make_test_db;
use crate::host::module_host::{DatabaseTableUpdate, TableOp};
use crate::subscription::query;
use spacetimedb_lib::error::ResultTest;
use spacetimedb_lib::operator::OpQuery;
use spacetimedb_primitives::TableId;
use spacetimedb_sats::data_key::ToDataKey;
use spacetimedb_sats::db::def::{ColumnDef, IndexDef, TableDef};
use spacetimedb_sats::AlgebraicType;
use spacetimedb_sats::relation::MemTable;
use spacetimedb_sats::{product, AlgebraicType};
use spacetimedb_vm::expr::{IndexJoin, IndexScan, JoinExpr, Query};

fn create_table(
Expand Down Expand Up @@ -1025,4 +1029,96 @@ mod tests {
};
Ok(())
}

#[test]
fn compile_incremental_index_join() -> ResultTest<()> {
let (db, _) = make_test_db()?;
let mut tx = db.begin_tx();

// Create table [lhs] with index on [b]
let schema = &[("a", AlgebraicType::U64), ("b", AlgebraicType::U64)];
let indexes = &[(1.into(), "b")];
let lhs_id = create_table(&db, &mut tx, "lhs", schema, indexes)?;

// Create table [rhs] with index on [b, c]
let schema = &[
("b", AlgebraicType::U64),
("c", AlgebraicType::U64),
("d", AlgebraicType::U64),
];
let indexes = &[(0.into(), "b"), (1.into(), "c")];
let rhs_id = create_table(&db, &mut tx, "rhs", schema, indexes)?;

// Should generate an index join since there is an index on `lhs.b`.
// Should push the sargable range condition into the index join's probe side.
let sql = "select lhs.* from lhs join rhs on lhs.b = rhs.b where rhs.c > 2 and rhs.c < 4 and rhs.d = 3";
let exp = compile_sql(&db, &tx, sql)?.remove(0);

let CrudExpr::Query(expr) = exp else {
panic!("unexpected result from compilation: {:#?}", exp);
};

// Create an insert for an incremental update.
let row = product!(0u64, 0u64);
let insert = TableOp {
op_type: 1,
row_pk: row.to_data_key().to_bytes(),
row,
};
let insert = DatabaseTableUpdate {
table_id: lhs_id,
table_name: String::from("lhs"),
ops: vec![insert],
};

// Optimize the query plan for the incremental update.
let expr = query::to_mem_table(expr, &insert);
let expr = expr.optimize();

let QueryExpr {
source:
SourceExpr::MemTable(MemTable {
head: Header { table_name, .. },
..
}),
query,
..
} = expr
else {
panic!("unexpected result after optimization: {:#?}", expr);
};

assert_eq!(table_name, "lhs");
assert_eq!(query.len(), 1);

let Query::IndexJoin(IndexJoin {
probe_side:
QueryExpr {
source: SourceExpr::MemTable(_),
query: ref lhs,
},
probe_field:
FieldName::Name {
table: ref probe_table,
field: ref probe_field,
},
index_header: _,
index_select: Some(_),
index_table,
index_col,
return_index_rows: false,
}) = query[0]
else {
panic!("unexpected operator {:#?}", query[0]);
};

assert!(lhs.is_empty());

// Assert that original index and probe tables have been swapped.
assert_eq!(index_table, rhs_id);
assert_eq!(index_col, 0.into());
assert_eq!(probe_field, "b");
assert_eq!(probe_table, "lhs");
Ok(())
}
}
5 changes: 3 additions & 2 deletions crates/core/src/subscription/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,13 +499,14 @@ mod tests {
let indexes = &[(0.into(), "id")];
let lhs_id = create_table(&db, &mut tx, "lhs", schema, indexes)?;

// Create table [rhs] with no indexes
// Create table [rhs] with index on [id]
let schema = &[
("rid", AlgebraicType::I32),
("id", AlgebraicType::I32),
("y", AlgebraicType::I32),
];
let rhs_id = create_table(&db, &mut tx, "rhs", schema, &[])?;
let indexes = &[(1.into(), "id")];
let rhs_id = create_table(&db, &mut tx, "rhs", schema, indexes)?;

// Insert into lhs
for i in 0..5 {
Expand Down
8 changes: 6 additions & 2 deletions crates/core/src/subscription/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,9 @@ impl<'a> IncrementalJoin<'a> {
auth: &AuthCtx,
) -> Result<impl Iterator<Item = Op>, DBError> {
let mut inserts = {
let lhs_virt = query::to_mem_table(self.expr.clone(), &self.lhs.inserts());
// Replan query after replacing left table with virtual table,
// since join order may need to be reversed.
let lhs_virt = query::to_mem_table(self.expr.clone(), &self.lhs.inserts()).optimize();
let rhs_virt = self.to_mem_table_rhs(self.rhs.inserts());

// {A+ join B}
Expand All @@ -551,7 +553,9 @@ impl<'a> IncrementalJoin<'a> {
set
};
let mut deletes = {
let lhs_virt = query::to_mem_table(self.expr.clone(), &self.lhs.deletes());
// Replan query after replacing left table with virtual table,
// since join order may need to be reversed.
let lhs_virt = query::to_mem_table(self.expr.clone(), &self.lhs.deletes()).optimize();
let rhs_virt = self.to_mem_table_rhs(self.rhs.deletes());

// {A- join B}
Expand Down
80 changes: 64 additions & 16 deletions crates/core/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ use spacetimedb_lib::identity::AuthCtx;
use spacetimedb_primitives::{ColId, TableId};
use spacetimedb_sats::db::auth::{StAccess, StTableType};
use spacetimedb_sats::db::def::{ColumnDef, IndexDef, ProductTypeMeta, TableDef};
use spacetimedb_sats::relation::{
DbTable, FieldExpr, FieldName, Header, MemTable, RelIter, RelValue, Relation, RowCount, Table,
};
use spacetimedb_sats::relation::{DbTable, FieldExpr, FieldName, RelValueRef, Relation};
use spacetimedb_sats::relation::{Header, MemTable, RelIter, RelValue, RowCount, Table};
use spacetimedb_sats::{AlgebraicValue, ProductValue};
use spacetimedb_vm::env::EnvDb;
use spacetimedb_vm::errors::ErrorVm;
Expand Down Expand Up @@ -53,13 +52,31 @@ pub fn build_query<'a>(
let iter = result.select(move |row| cmp.compare(row, &header));
Box::new(iter)
}
// If this is an index join between two virtual tables, replace with an inner join.
// Such a plan is possible under incremental evaluation,
// when there are updates to both base tables,
// however an index lookup is invalid on a virtual table.
//
// TODO: This logic should be entirely encapsulated within the query planner.
// It should not be possible for the planner to produce an invalid plan.
Query::IndexJoin(join)
if !db_table
&& matches!(join.probe_side.source, SourceExpr::MemTable(_))
&& join.probe_side.source.table_name() != result.head().table_name =>
{
let join: JoinExpr = join.into();
let iter = join_inner(ctx, stdb, tx, result, join, true)?;
Box::new(iter)
}
Query::IndexJoin(IndexJoin {
probe_side,
probe_field,
index_header,
index_select,
index_table,
index_col,
}) if db_table => {
return_index_rows,
}) => {
let probe_side = build_query(ctx, stdb, tx, probe_side.into())?;
Box::new(IndexSemiJoin {
ctx,
Expand All @@ -68,16 +85,13 @@ pub fn build_query<'a>(
probe_side,
probe_field,
index_header,
index_select,
index_table,
index_col,
index_iter: None,
return_index_rows,
})
}
Query::IndexJoin(join) => {
let join: JoinExpr = join.into();
let iter = join_inner(ctx, stdb, tx, result, join, true)?;
Box::new(iter)
}
Query::Select(cmp) => {
let header = result.head().clone();
let iter = result.select(move |row| cmp.compare(row, &header));
Expand Down Expand Up @@ -189,12 +203,15 @@ pub struct IndexSemiJoin<'a, Rhs: RelOps> {
// The field whose value will be used to probe the index.
pub probe_field: FieldName,
// The header for the index side of the join.
// Also the return header since we are returning values from the index side.
pub index_header: Header,
// An optional predicate to evaluate over the matching rows of the index.
pub index_select: Option<ColumnOp>,
// The table id on which the index is defined.
pub index_table: TableId,
// The column id for which the index is defined.
pub index_col: ColId,
// Is this a left or right semijion?
pub return_index_rows: bool,
// An iterator for the index side.
// A new iterator will be instantiated for each row on the probe side.
pub index_iter: Option<IterByColEq<'a>>,
Expand All @@ -206,9 +223,32 @@ pub struct IndexSemiJoin<'a, Rhs: RelOps> {
ctx: &'a ExecutionContext<'a>,
}

impl<'a, Rhs: RelOps> IndexSemiJoin<'a, Rhs> {
fn filter(&self, index_row: RelValueRef) -> Result<bool, ErrorVm> {
if let Some(op) = &self.index_select {
Ok(op.compare(index_row, &self.index_header)?)
} else {
Ok(true)
}
}

fn map(&self, index_row: RelValue, probe_row: Option<RelValue>) -> RelValue {
if let Some(value) = probe_row {
if !self.return_index_rows {
return value;
}
}
index_row
}
}

impl<'a, Rhs: RelOps> RelOps for IndexSemiJoin<'a, Rhs> {
fn head(&self) -> &Header {
&self.index_header
if self.return_index_rows {
&self.index_header
} else {
self.probe_side.head()
}
}

fn row_count(&self) -> RowCount {
Expand All @@ -218,8 +258,13 @@ impl<'a, Rhs: RelOps> RelOps for IndexSemiJoin<'a, Rhs> {
#[tracing::instrument(skip_all)]
fn next(&mut self) -> Result<Option<RelValue>, ErrorVm> {
// Return a value from the current index iterator, if not exhausted.
if let Some(value) = self.index_iter.as_mut().and_then(|iter| iter.next()) {
return Ok(Some(value.to_rel_value()));
if self.return_index_rows {
while let Some(value) = self.index_iter.as_mut().and_then(|iter| iter.next()) {
let value = value.to_rel_value();
if self.filter(value.as_val_ref())? {
return Ok(Some(self.map(value, None)));
}
}
}
// Otherwise probe the index with a row from the probe side.
while let Some(row) = self.probe_side.next()? {
Expand All @@ -229,9 +274,12 @@ impl<'a, Rhs: RelOps> RelOps for IndexSemiJoin<'a, Rhs> {
let col_id = self.index_col;
let value = value.clone();
let mut index_iter = self.db.iter_by_col_eq(self.ctx, self.tx, table_id, col_id, value)?;
if let Some(value) = index_iter.next() {
self.index_iter = Some(index_iter);
return Ok(Some(value.to_rel_value()));
while let Some(value) = index_iter.next() {
let value = value.to_rel_value();
if self.filter(value.as_val_ref())? {
self.index_iter = Some(index_iter);
return Ok(Some(self.map(value, Some(row))));
}
}
}
}
Expand Down
Loading

1 comment on commit 5a23fad

@github-actions
Copy link

@github-actions github-actions bot commented on 5a23fad Nov 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark results

Benchmark Report

Legend:

  • load: number of rows pre-loaded into the database
  • count: number of rows touched by the transaction
  • index types:
    • unique: a single index on the id column
    • non_unique: no indexes
    • multi_index: non-unique index on every column
  • schemas:
    • person(id: u32, name: String, age: u64)
    • location(id: u32, x: u64, y: u64)

All throughputs are single-threaded.

Empty transaction

db on disk new latency old latency new throughput old throughput
sqlite 💿 433.8±2.07ns 432.2±1.32ns - -
sqlite 🧠 424.9±1.53ns 425.1±1.37ns - -
stdb_module 💿 18.8±1.63µs 18.1±1.36µs - -
stdb_module 🧠 17.9±0.86µs 18.4±1.35µs - -
stdb_raw 💿 729.1±0.78ns 726.2±1.24ns - -
stdb_raw 🧠 725.1±1.83ns 717.0±1.38ns - -

Single-row insertions

db on disk schema index type load new latency old latency new throughput old throughput
sqlite 💿 location multi_index 0 15.0±1.70µs 15.0±1.41µs 65.3 Ktx/sec 65.2 Ktx/sec
sqlite 💿 location multi_index 1000 15.9±0.12µs 15.9±0.16µs 61.5 Ktx/sec 61.5 Ktx/sec
sqlite 💿 location non_unique 0 7.2±0.05µs 7.3±0.39µs 135.1 Ktx/sec 133.6 Ktx/sec
sqlite 💿 location non_unique 1000 7.3±1.41µs 7.1±0.05µs 134.1 Ktx/sec 137.1 Ktx/sec
sqlite 💿 location unique 0 7.3±0.05µs 7.2±0.04µs 134.4 Ktx/sec 136.2 Ktx/sec
sqlite 💿 location unique 1000 7.1±0.05µs 7.2±0.03µs 137.2 Ktx/sec 135.9 Ktx/sec
sqlite 💿 person multi_index 0 14.7±1.17µs 14.5±0.03µs 66.2 Ktx/sec 67.4 Ktx/sec
sqlite 💿 person multi_index 1000 16.1±0.07µs 16.2±0.12µs 60.6 Ktx/sec 60.3 Ktx/sec
sqlite 💿 person non_unique 0 7.4±0.43µs 7.3±0.54µs 131.4 Ktx/sec 133.1 Ktx/sec
sqlite 💿 person non_unique 1000 7.3±0.06µs 7.4±0.05µs 134.3 Ktx/sec 131.8 Ktx/sec
sqlite 💿 person unique 0 7.4±0.51µs 7.3±0.03µs 131.8 Ktx/sec 133.6 Ktx/sec
sqlite 💿 person unique 1000 7.3±0.03µs 7.4±0.04µs 133.9 Ktx/sec 131.7 Ktx/sec
sqlite 🧠 location multi_index 0 4.1±0.01µs 4.1±0.01µs 235.7 Ktx/sec 237.6 Ktx/sec
sqlite 🧠 location multi_index 1000 5.4±0.05µs 5.3±0.04µs 182.2 Ktx/sec 185.2 Ktx/sec
sqlite 🧠 location non_unique 0 1877.4±6.25ns 1840.6±5.33ns 520.2 Ktx/sec 530.6 Ktx/sec
sqlite 🧠 location non_unique 1000 1956.5±18.05ns 1907.5±12.61ns 499.1 Ktx/sec 512.0 Ktx/sec
sqlite 🧠 location unique 0 1856.5±6.19ns 1828.6±7.42ns 526.0 Ktx/sec 534.0 Ktx/sec
sqlite 🧠 location unique 1000 1963.1±12.44ns 1965.7±15.27ns 497.4 Ktx/sec 496.8 Ktx/sec
sqlite 🧠 person multi_index 0 3.7±0.01µs 3.8±0.02µs 263.0 Ktx/sec 258.2 Ktx/sec
sqlite 🧠 person multi_index 1000 5.5±0.04µs 5.7±0.04µs 177.6 Ktx/sec 172.4 Ktx/sec
sqlite 🧠 person non_unique 0 1941.2±5.13ns 1958.5±6.34ns 503.1 Ktx/sec 498.6 Ktx/sec
sqlite 🧠 person non_unique 1000 2.0±0.01µs 2.0±0.01µs 484.0 Ktx/sec 478.5 Ktx/sec
sqlite 🧠 person unique 0 1944.6±4.23ns 1925.4±13.70ns 502.2 Ktx/sec 507.2 Ktx/sec
sqlite 🧠 person unique 1000 2.0±0.01µs 2.1±0.02µs 479.5 Ktx/sec 467.5 Ktx/sec
stdb_module 💿 location multi_index 0 55.5±4.71µs 53.9±5.94µs 17.6 Ktx/sec 18.1 Ktx/sec
stdb_module 💿 location multi_index 1000 292.7±74.15µs 152.1±81.59µs 3.3 Ktx/sec 6.4 Ktx/sec
stdb_module 💿 location non_unique 0 47.9±5.28µs 42.7±5.60µs 20.4 Ktx/sec 22.9 Ktx/sec
stdb_module 💿 location non_unique 1000 76.7±18.01µs 207.0±70.51µs 12.7 Ktx/sec 4.7 Ktx/sec
stdb_module 💿 location unique 0 51.2±4.91µs 51.7±5.71µs 19.1 Ktx/sec 18.9 Ktx/sec
stdb_module 💿 location unique 1000 100.1±38.66µs 129.6±74.66µs 9.8 Ktx/sec 7.5 Ktx/sec
stdb_module 💿 person multi_index 0 64.2±6.64µs 63.5±6.21µs 15.2 Ktx/sec 15.4 Ktx/sec
stdb_module 💿 person multi_index 1000 300.5±10.91µs 354.5±18.33µs 3.2 Ktx/sec 2.8 Ktx/sec
stdb_module 💿 person non_unique 0 47.3±5.76µs 48.2±4.46µs 20.7 Ktx/sec 20.2 Ktx/sec
stdb_module 💿 person non_unique 1000 295.6±32.90µs 345.5±17.73µs 3.3 Ktx/sec 2.8 Ktx/sec
stdb_module 💿 person unique 0 54.5±6.43µs 60.1±4.94µs 17.9 Ktx/sec 16.2 Ktx/sec
stdb_module 💿 person unique 1000 117.7±13.96µs 173.5±115.81µs 8.3 Ktx/sec 5.6 Ktx/sec
stdb_module 🧠 location multi_index 0 37.4±3.02µs 40.1±4.33µs 26.1 Ktx/sec 24.3 Ktx/sec
stdb_module 🧠 location multi_index 1000 138.2±13.17µs 178.7±14.33µs 7.1 Ktx/sec 5.5 Ktx/sec
stdb_module 🧠 location non_unique 0 29.0±2.70µs 29.5±2.47µs 33.6 Ktx/sec 33.1 Ktx/sec
stdb_module 🧠 location non_unique 1000 264.3±8.20µs 165.8±17.05µs 3.7 Ktx/sec 5.9 Ktx/sec
stdb_module 🧠 location unique 0 33.5±3.57µs 33.0±3.01µs 29.1 Ktx/sec 29.6 Ktx/sec
stdb_module 🧠 location unique 1000 134.3±2.05µs 173.8±12.09µs 7.3 Ktx/sec 5.6 Ktx/sec
stdb_module 🧠 person multi_index 0 47.2±4.03µs 47.8±4.48µs 20.7 Ktx/sec 20.4 Ktx/sec
stdb_module 🧠 person multi_index 1000 281.4±7.47µs 171.2±20.98µs 3.5 Ktx/sec 5.7 Ktx/sec
stdb_module 🧠 person non_unique 0 32.3±3.53µs 34.9±3.63µs 30.2 Ktx/sec 28.0 Ktx/sec
stdb_module 🧠 person non_unique 1000 118.9±14.68µs 270.9±6.89µs 8.2 Ktx/sec 3.6 Ktx/sec
stdb_module 🧠 person unique 0 40.4±3.48µs 38.4±3.63µs 24.2 Ktx/sec 25.5 Ktx/sec
stdb_module 🧠 person unique 1000 150.2±11.97µs 236.2±8.30µs 6.5 Ktx/sec 4.1 Ktx/sec
stdb_raw 💿 location multi_index 0 7.2±0.02µs 7.1±0.02µs 135.3 Ktx/sec 137.7 Ktx/sec
stdb_raw 💿 location multi_index 1000 10.1±0.63µs 9.8±0.18µs 96.9 Ktx/sec 99.6 Ktx/sec
stdb_raw 💿 location non_unique 0 4.8±0.01µs 4.7±0.01µs 203.8 Ktx/sec 205.9 Ktx/sec
stdb_raw 💿 location non_unique 1000 6.2±0.15µs 6.2±0.14µs 156.9 Ktx/sec 156.9 Ktx/sec
stdb_raw 💿 location unique 0 6.1±0.01µs 6.1±0.01µs 159.1 Ktx/sec 161.1 Ktx/sec
stdb_raw 💿 location unique 1000 8.6±0.24µs 23.5±151.43µs 113.2 Ktx/sec 41.5 Ktx/sec
stdb_raw 💿 person multi_index 0 11.0±0.05µs 10.8±0.04µs 88.6 Ktx/sec 90.3 Ktx/sec
stdb_raw 💿 person multi_index 1000 55.7±412.40µs 46.6±324.48µs 17.5 Ktx/sec 21.0 Ktx/sec
stdb_raw 💿 person non_unique 0 5.4±0.13µs 5.3±0.01µs 182.5 Ktx/sec 183.3 Ktx/sec
stdb_raw 💿 person non_unique 1000 7.1±0.11µs 23.0±159.63µs 138.3 Ktx/sec 42.4 Ktx/sec
stdb_raw 💿 person unique 0 7.8±0.02µs 7.7±0.03µs 124.7 Ktx/sec 126.2 Ktx/sec
stdb_raw 💿 person unique 1000 38.4±278.41µs 31.3±210.89µs 25.4 Ktx/sec 31.2 Ktx/sec
stdb_raw 🧠 location multi_index 0 4.2±0.01µs 4.2±0.01µs 234.6 Ktx/sec 234.3 Ktx/sec
stdb_raw 🧠 location multi_index 1000 5.8±0.04µs 5.7±0.02µs 167.5 Ktx/sec 171.6 Ktx/sec
stdb_raw 🧠 location non_unique 0 1941.9±5.04ns 1951.9±4.51ns 502.9 Ktx/sec 500.3 Ktx/sec
stdb_raw 🧠 location non_unique 1000 2.5±0.01µs 2.4±0.02µs 394.4 Ktx/sec 405.1 Ktx/sec
stdb_raw 🧠 location unique 0 3.1±0.00µs 3.2±0.01µs 311.0 Ktx/sec 308.8 Ktx/sec
stdb_raw 🧠 location unique 1000 4.4±0.03µs 4.3±0.03µs 222.6 Ktx/sec 228.5 Ktx/sec
stdb_raw 🧠 person multi_index 0 7.9±0.08µs 7.8±0.01µs 123.9 Ktx/sec 124.5 Ktx/sec
stdb_raw 🧠 person multi_index 1000 10.1±0.09µs 10.0±0.12µs 96.7 Ktx/sec 97.8 Ktx/sec
stdb_raw 🧠 person non_unique 0 2.4±0.01µs 2.5±0.00µs 398.7 Ktx/sec 396.6 Ktx/sec
stdb_raw 🧠 person non_unique 1000 3.2±0.01µs 3.2±0.02µs 302.6 Ktx/sec 301.8 Ktx/sec
stdb_raw 🧠 person unique 0 4.8±0.01µs 4.8±0.01µs 205.5 Ktx/sec 204.9 Ktx/sec
stdb_raw 🧠 person unique 1000 6.2±0.06µs 6.0±0.05µs 157.6 Ktx/sec 161.9 Ktx/sec

Multi-row insertions

db on disk schema index type load count new latency old latency new throughput old throughput
sqlite 💿 location multi_index 0 100 133.0±0.42µs 129.4±2.65µs 7.3 Ktx/sec 7.5 Ktx/sec
sqlite 💿 location multi_index 1000 100 208.8±34.29µs 201.4±3.36µs 4.7 Ktx/sec 4.8 Ktx/sec
sqlite 💿 location non_unique 0 100 50.9±1.52µs 49.6±1.03µs 19.2 Ktx/sec 19.7 Ktx/sec
sqlite 💿 location non_unique 1000 100 53.4±0.23µs 51.8±0.37µs 18.3 Ktx/sec 18.9 Ktx/sec
sqlite 💿 location unique 0 100 53.0±1.43µs 51.0±1.41µs 18.4 Ktx/sec 19.2 Ktx/sec
sqlite 💿 location unique 1000 100 57.0±0.36µs 55.7±0.17µs 17.1 Ktx/sec 17.5 Ktx/sec
sqlite 💿 person multi_index 0 100 119.4±3.34µs 116.9±0.99µs 8.2 Ktx/sec 8.4 Ktx/sec
sqlite 💿 person multi_index 1000 100 230.3±0.35µs 231.8±2.40µs 4.2 Ktx/sec 4.2 Ktx/sec
sqlite 💿 person non_unique 0 100 49.0±1.24µs 47.5±1.96µs 19.9 Ktx/sec 20.6 Ktx/sec
sqlite 💿 person non_unique 1000 100 61.0±0.22µs 58.8±0.31µs 16.0 Ktx/sec 16.6 Ktx/sec
sqlite 💿 person unique 0 100 50.4±6.28µs 48.6±1.77µs 19.4 Ktx/sec 20.1 Ktx/sec
sqlite 💿 person unique 1000 100 56.1±11.65µs 55.3±0.36µs 17.4 Ktx/sec 17.7 Ktx/sec
sqlite 🧠 location multi_index 0 100 120.1±0.53µs 118.2±0.51µs 8.1 Ktx/sec 8.3 Ktx/sec
sqlite 🧠 location multi_index 1000 100 172.8±0.30µs 168.4±0.26µs 5.7 Ktx/sec 5.8 Ktx/sec
sqlite 🧠 location non_unique 0 100 44.1±0.36µs 42.1±0.55µs 22.1 Ktx/sec 23.2 Ktx/sec
sqlite 🧠 location non_unique 1000 100 45.1±0.27µs 43.5±0.46µs 21.7 Ktx/sec 22.5 Ktx/sec
sqlite 🧠 location unique 0 100 45.6±0.29µs 42.8±0.23µs 21.4 Ktx/sec 22.8 Ktx/sec
sqlite 🧠 location unique 1000 100 50.6±0.35µs 47.7±0.21µs 19.3 Ktx/sec 20.5 Ktx/sec
sqlite 🧠 person multi_index 0 100 106.9±0.26µs 105.5±0.59µs 9.1 Ktx/sec 9.3 Ktx/sec
sqlite 🧠 person multi_index 1000 100 191.7±0.21µs 190.9±0.48µs 5.1 Ktx/sec 5.1 Ktx/sec
sqlite 🧠 person non_unique 0 100 42.1±0.19µs 40.3±0.34µs 23.2 Ktx/sec 24.2 Ktx/sec
sqlite 🧠 person non_unique 1000 100 46.2±0.39µs 43.7±0.33µs 21.1 Ktx/sec 22.4 Ktx/sec
sqlite 🧠 person unique 0 100 43.6±0.71µs 42.1±0.27µs 22.4 Ktx/sec 23.2 Ktx/sec
sqlite 🧠 person unique 1000 100 47.8±0.36µs 46.9±0.30µs 20.4 Ktx/sec 20.8 Ktx/sec
stdb_module 💿 location multi_index 0 100 1018.2±36.75µs 789.7±166.52µs 982 tx/sec 1266 tx/sec
stdb_module 💿 location multi_index 1000 100 773.8±146.17µs 1021.4±56.36µs 1292 tx/sec 979 tx/sec
stdb_module 💿 location non_unique 0 100 629.6±20.84µs 428.7±113.66µs 1588 tx/sec 2.3 Ktx/sec
stdb_module 💿 location non_unique 1000 100 777.1±82.44µs 828.4±35.31µs 1286 tx/sec 1207 tx/sec
stdb_module 💿 location unique 0 100 752.2±103.58µs 735.6±54.00µs 1329 tx/sec 1359 tx/sec
stdb_module 💿 location unique 1000 100 862.6±75.48µs 699.2±71.73µs 1159 tx/sec 1430 tx/sec
stdb_module 💿 person multi_index 0 100 1175.0±167.53µs 1068.8±31.58µs 851 tx/sec 935 tx/sec
stdb_module 💿 person multi_index 1000 100 1245.4±75.45µs 1171.1±48.06µs 802 tx/sec 853 tx/sec
stdb_module 💿 person non_unique 0 100 638.7±123.54µs 783.2±3.39µs 1565 tx/sec 1276 tx/sec
stdb_module 💿 person non_unique 1000 100 754.9±19.58µs 611.2±12.86µs 1324 tx/sec 1636 tx/sec
stdb_module 💿 person unique 0 100 794.7±1.56µs 673.1±19.97µs 1258 tx/sec 1485 tx/sec
stdb_module 💿 person unique 1000 100 1042.0±7.35µs 964.4±93.17µs 959 tx/sec 1036 tx/sec
stdb_module 🧠 location multi_index 0 100 593.2±4.43µs 674.9±124.67µs 1685 tx/sec 1481 tx/sec
stdb_module 🧠 location multi_index 1000 100 629.6±2.85µs 529.9±5.99µs 1588 tx/sec 1887 tx/sec
stdb_module 🧠 location non_unique 0 100 315.4±45.68µs 318.0±21.25µs 3.1 Ktx/sec 3.1 Ktx/sec
stdb_module 🧠 location non_unique 1000 100 459.5±89.36µs 360.7±39.05µs 2.1 Ktx/sec 2.7 Ktx/sec
stdb_module 🧠 location unique 0 100 613.7±7.63µs 456.6±48.75µs 1629 tx/sec 2.1 Ktx/sec
stdb_module 🧠 location unique 1000 100 539.2±97.41µs 585.5±39.37µs 1854 tx/sec 1707 tx/sec
stdb_module 🧠 person multi_index 0 100 871.6±46.95µs 904.9±47.33µs 1147 tx/sec 1105 tx/sec
stdb_module 🧠 person multi_index 1000 100 914.8±25.26µs 909.1±53.41µs 1093 tx/sec 1099 tx/sec
stdb_module 🧠 person non_unique 0 100 524.6±21.88µs 330.8±28.31µs 1906 tx/sec 3.0 Ktx/sec
stdb_module 🧠 person non_unique 1000 100 581.7±11.50µs 345.3±1.30µs 1719 tx/sec 2.8 Ktx/sec
stdb_module 🧠 person unique 0 100 589.7±12.18µs 667.7±64.17µs 1695 tx/sec 1497 tx/sec
stdb_module 🧠 person unique 1000 100 827.3±25.34µs 572.9±4.32µs 1208 tx/sec 1745 tx/sec
stdb_raw 💿 location multi_index 0 100 401.8±1.30µs 390.8±20.01µs 2.4 Ktx/sec 2.5 Ktx/sec
stdb_raw 💿 location multi_index 1000 100 460.2±311.38µs 423.8±93.01µs 2.1 Ktx/sec 2.3 Ktx/sec
stdb_raw 💿 location non_unique 0 100 172.7±0.50µs 169.3±0.43µs 5.7 Ktx/sec 5.8 Ktx/sec
stdb_raw 💿 location non_unique 1000 100 175.1±0.92µs 172.0±0.94µs 5.6 Ktx/sec 5.7 Ktx/sec
stdb_raw 💿 location unique 0 100 297.9±0.47µs 293.2±0.46µs 3.3 Ktx/sec 3.3 Ktx/sec
stdb_raw 💿 location unique 1000 100 316.7±1.39µs 311.8±1.61µs 3.1 Ktx/sec 3.1 Ktx/sec
stdb_raw 💿 person multi_index 0 100 728.2±2.94µs 716.5±3.50µs 1373 tx/sec 1395 tx/sec
stdb_raw 💿 person multi_index 1000 100 770.4±141.68µs 743.6±2.17µs 1298 tx/sec 1344 tx/sec
stdb_raw 💿 person non_unique 0 100 229.7±0.19µs 227.4±0.18µs 4.3 Ktx/sec 4.3 Ktx/sec
stdb_raw 💿 person non_unique 1000 100 249.5±176.16µs 238.6±80.72µs 3.9 Ktx/sec 4.1 Ktx/sec
stdb_raw 💿 person unique 0 100 440.9±0.32µs 435.4±0.46µs 2.2 Ktx/sec 2.2 Ktx/sec
stdb_raw 💿 person unique 1000 100 489.4±300.04µs 454.5±1.06µs 2043 tx/sec 2.1 Ktx/sec
stdb_raw 🧠 location multi_index 0 100 298.8±0.81µs 295.9±0.81µs 3.3 Ktx/sec 3.3 Ktx/sec
stdb_raw 🧠 location multi_index 1000 100 325.2±0.55µs 322.2±1.00µs 3.0 Ktx/sec 3.0 Ktx/sec
stdb_raw 🧠 location non_unique 0 100 76.0±0.14µs 74.2±0.12µs 12.9 Ktx/sec 13.2 Ktx/sec
stdb_raw 🧠 location non_unique 1000 100 77.1±0.34µs 75.8±0.26µs 12.7 Ktx/sec 12.9 Ktx/sec
stdb_raw 🧠 location unique 0 100 200.3±0.31µs 196.8±2.19µs 4.9 Ktx/sec 5.0 Ktx/sec
stdb_raw 🧠 location unique 1000 100 218.3±0.73µs 216.2±0.40µs 4.5 Ktx/sec 4.5 Ktx/sec
stdb_raw 🧠 person multi_index 0 100 626.2±1.54µs 614.9±0.44µs 1597 tx/sec 1626 tx/sec
stdb_raw 🧠 person multi_index 1000 100 653.9±1.33µs 642.5±0.55µs 1529 tx/sec 1556 tx/sec
stdb_raw 🧠 person non_unique 0 100 127.8±0.11µs 128.0±0.16µs 7.6 Ktx/sec 7.6 Ktx/sec
stdb_raw 🧠 person non_unique 1000 100 129.9±0.19µs 130.4±0.26µs 7.5 Ktx/sec 7.5 Ktx/sec
stdb_raw 🧠 person unique 0 100 338.9±0.34µs 335.8±0.44µs 2.9 Ktx/sec 2.9 Ktx/sec
stdb_raw 🧠 person unique 1000 100 356.4±0.46µs 353.3±1.80µs 2.7 Ktx/sec 2.8 Ktx/sec

Full table iterate

db on disk schema index type new latency old latency new throughput old throughput
sqlite 💿 location unique 8.9±0.04µs 8.8±0.12µs 109.8 Ktx/sec 110.6 Ktx/sec
sqlite 💿 person unique 9.4±0.07µs 9.5±0.15µs 104.2 Ktx/sec 103.2 Ktx/sec
sqlite 🧠 location unique 7.7±0.04µs 7.6±0.15µs 127.2 Ktx/sec 128.9 Ktx/sec
sqlite 🧠 person unique 8.1±0.08µs 8.2±0.09µs 121.1 Ktx/sec 119.1 Ktx/sec
stdb_module 💿 location unique 51.8±3.66µs 49.8±5.53µs 18.8 Ktx/sec 19.6 Ktx/sec
stdb_module 💿 person unique 61.2±11.16µs 55.3±10.89µs 16.0 Ktx/sec 17.7 Ktx/sec
stdb_module 🧠 location unique 46.9±8.09µs 50.6±4.18µs 20.8 Ktx/sec 19.3 Ktx/sec
stdb_module 🧠 person unique 67.1±7.32µs 62.5±9.54µs 14.5 Ktx/sec 15.6 Ktx/sec
stdb_raw 💿 location unique 10.5±0.02µs 9.3±0.28µs 92.9 Ktx/sec 105.3 Ktx/sec
stdb_raw 💿 person unique 10.5±0.05µs 9.0±0.14µs 92.9 Ktx/sec 108.3 Ktx/sec
stdb_raw 🧠 location unique 10.5±0.13µs 9.1±0.21µs 92.8 Ktx/sec 107.7 Ktx/sec
stdb_raw 🧠 person unique 10.5±0.08µs 9.2±0.24µs 92.9 Ktx/sec 106.2 Ktx/sec

Find unique key

db on disk key type load new latency old latency new throughput old throughput
sqlite 💿 u32 1000 2.4±0.01µs 2.4±0.01µs 410.4 Ktx/sec 412.9 Ktx/sec
sqlite 🧠 u32 1000 1156.3±5.38ns 1122.4±6.42ns 844.6 Ktx/sec 870.1 Ktx/sec
stdb_module 💿 u32 1000 25.0±1.63µs 24.2±1.42µs 39.0 Ktx/sec 40.4 Ktx/sec
stdb_module 🧠 u32 1000 24.4±1.79µs 24.8±1.71µs 40.1 Ktx/sec 39.3 Ktx/sec
stdb_raw 💿 u32 1000 1970.2±4.38ns 1926.3±5.24ns 495.7 Ktx/sec 507.0 Ktx/sec
stdb_raw 🧠 u32 1000 1948.4±5.63ns 1911.3±4.21ns 501.2 Ktx/sec 510.9 Ktx/sec

Filter

db on disk key type index strategy load count new latency old latency new throughput old throughput
sqlite 💿 string indexed 1000 10 5.6±0.01µs 5.7±0.02µs 174.1 Ktx/sec 172.0 Ktx/sec
sqlite 💿 string non_indexed 1000 10 49.0±0.50µs 50.2±0.57µs 19.9 Ktx/sec 19.4 Ktx/sec
sqlite 💿 u64 indexed 1000 10 5.4±0.02µs 5.5±0.03µs 179.3 Ktx/sec 176.0 Ktx/sec
sqlite 💿 u64 non_indexed 1000 10 32.9±0.11µs 32.9±0.07µs 29.7 Ktx/sec 29.7 Ktx/sec
sqlite 🧠 string indexed 1000 10 4.2±0.01µs 4.3±0.01µs 233.3 Ktx/sec 229.6 Ktx/sec
sqlite 🧠 string non_indexed 1000 10 47.3±0.44µs 48.7±0.62µs 20.6 Ktx/sec 20.0 Ktx/sec
sqlite 🧠 u64 indexed 1000 10 4.1±0.02µs 4.1±0.02µs 240.3 Ktx/sec 238.3 Ktx/sec
sqlite 🧠 u64 non_indexed 1000 10 33.3±0.21µs 31.6±0.06µs 29.4 Ktx/sec 30.9 Ktx/sec
stdb_module 💿 string indexed 1000 10 36.8±2.92µs 36.4±3.49µs 26.6 Ktx/sec 26.8 Ktx/sec
stdb_module 💿 string non_indexed 1000 10 183.7±3.04µs 179.9±4.04µs 5.3 Ktx/sec 5.4 Ktx/sec
stdb_module 💿 u64 indexed 1000 10 33.4±2.17µs 33.6±1.96µs 29.3 Ktx/sec 29.1 Ktx/sec
stdb_module 💿 u64 non_indexed 1000 10 157.2±3.56µs 153.3±1.39µs 6.2 Ktx/sec 6.4 Ktx/sec
stdb_module 🧠 string indexed 1000 10 36.4±3.17µs 36.8±2.26µs 26.8 Ktx/sec 26.5 Ktx/sec
stdb_module 🧠 string non_indexed 1000 10 184.3±7.36µs 174.1±1.13µs 5.3 Ktx/sec 5.6 Ktx/sec
stdb_module 🧠 u64 indexed 1000 10 32.6±2.69µs 32.9±2.45µs 30.0 Ktx/sec 29.7 Ktx/sec
stdb_module 🧠 u64 non_indexed 1000 10 156.9±3.45µs 150.8±4.61µs 6.2 Ktx/sec 6.5 Ktx/sec
stdb_raw 💿 string indexed 1000 10 4.5±0.01µs 4.3±0.09µs 216.1 Ktx/sec 226.4 Ktx/sec
stdb_raw 💿 string non_indexed 1000 10 150.3±0.29µs 140.6±1.91µs 6.5 Ktx/sec 6.9 Ktx/sec
stdb_raw 💿 u64 indexed 1000 10 4.4±0.01µs 4.1±0.01µs 220.3 Ktx/sec 237.0 Ktx/sec
stdb_raw 💿 u64 non_indexed 1000 10 128.9±0.19µs 116.3±1.00µs 7.6 Ktx/sec 8.4 Ktx/sec
stdb_raw 🧠 string indexed 1000 10 4.5±0.01µs 4.3±0.01µs 217.6 Ktx/sec 227.1 Ktx/sec
stdb_raw 🧠 string non_indexed 1000 10 147.6±0.27µs 140.2±1.96µs 6.6 Ktx/sec 7.0 Ktx/sec
stdb_raw 🧠 u64 indexed 1000 10 4.4±0.01µs 4.1±0.02µs 221.8 Ktx/sec 238.0 Ktx/sec
stdb_raw 🧠 u64 non_indexed 1000 10 127.8±0.17µs 118.8±2.96µs 7.6 Ktx/sec 8.2 Ktx/sec

Serialize

schema format count new latency old latency new throughput old throughput
location bsatn 100 1696.7±56.37ns 1859.9±34.10ns 56.2 Mtx/sec 51.3 Mtx/sec
location json 100 3.3±0.03µs 3.2±0.02µs 29.0 Mtx/sec 30.1 Mtx/sec
location product_value 100 579.3±0.44ns 573.7±1.26ns 164.6 Mtx/sec 166.2 Mtx/sec
person bsatn 100 2.9±0.02µs 2.6±0.00µs 33.1 Mtx/sec 36.5 Mtx/sec
person json 100 5.0±0.03µs 4.7±0.03µs 18.9 Mtx/sec 20.1 Mtx/sec
person product_value 100 1029.9±1.06ns 1005.7±1.13ns 92.6 Mtx/sec 94.8 Mtx/sec

Module: invoke with large arguments

arg size new latency old latency new throughput old throughput
64KiB 80.3±3.03µs 79.6±7.26µs - -

Module: print bulk

line count new latency old latency new throughput old throughput
1 24.8±1.93µs 22.7±1.24µs - -
100 199.3±1.87µs 201.8±1.11µs - -
1000 1835.2±27.70µs 1841.3±40.67µs - -

Remaining benchmarks

name new latency old latency new throughput old throughput

Please sign in to comment.