Skip to content

Commit

Permalink
query rewriter (#2031)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshua-spacetime authored Dec 12, 2024
1 parent cdfa7aa commit 9211708
Show file tree
Hide file tree
Showing 19 changed files with 2,351 additions and 1,877 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

10 changes: 4 additions & 6 deletions crates/core/src/sql/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ use crate::db::relational_db::RelationalDB;
use crate::sql::ast::SchemaViewer;
use spacetimedb_expr::check::parse_and_type_sub;
use spacetimedb_expr::errors::TypingError;
use spacetimedb_expr::expr::RelExpr;
use spacetimedb_expr::ty::TyCtx;
use spacetimedb_expr::expr::Project;
use spacetimedb_lib::db::raw_def::v9::RawRowLevelSecurityDefV9;
use spacetimedb_lib::identity::AuthCtx;
use spacetimedb_schema::schema::RowLevelSecuritySchema;

pub struct RowLevelExpr {
pub sql: RelExpr,
pub sql: Project,
pub def: RowLevelSecuritySchema,
}

Expand All @@ -21,12 +20,11 @@ impl RowLevelExpr {
auth_ctx: &AuthCtx,
rls: &RawRowLevelSecurityDefV9,
) -> Result<Self, TypingError> {
let mut ctx = TyCtx::default();
let sql = parse_and_type_sub(&mut ctx, &rls.sql, &SchemaViewer::new(stdb, tx, auth_ctx))?;
let sql = parse_and_type_sub(&rls.sql, &SchemaViewer::new(stdb, tx, auth_ctx))?;

Ok(Self {
def: RowLevelSecuritySchema {
table_id: sql.table_id(&mut ctx)?,
table_id: sql.table_id().unwrap(),
sql: rls.sql.clone(),
},
sql,
Expand Down
7 changes: 1 addition & 6 deletions crates/core/src/subscription/module_subscription_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use crate::worker_metrics::WORKER_METRICS;
use parking_lot::RwLock;
use spacetimedb_client_api_messages::websocket::FormatSwitch;
use spacetimedb_expr::check::compile_sql_sub;
use spacetimedb_expr::ty::TyCtx;
use spacetimedb_lib::identity::AuthCtx;
use spacetimedb_lib::Identity;
use spacetimedb_vm::errors::ErrorVm;
Expand Down Expand Up @@ -88,11 +87,7 @@ impl ModuleSubscriptions {
} else {
// NOTE: The following ensures compliance with the 1.0 sql api.
// Come 1.0, it will have replaced the current compilation stack.
compile_sql_sub(
&mut TyCtx::default(),
sql,
&SchemaViewer::new(&self.relational_db, &*tx, &auth),
)?;
compile_sql_sub(sql, &SchemaViewer::new(&self.relational_db, &*tx, &auth))?;

let mut compiled = compile_read_only_query(&self.relational_db, &auth, &tx, sql)?;
// Note that no error path is needed here.
Expand Down
223 changes: 88 additions & 135 deletions crates/expr/src/check.rs
Original file line number Diff line number Diff line change
@@ -1,187 +1,150 @@
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;

use crate::statement::Statement;
use crate::ty::TyId;
use spacetimedb_schema::schema::{ColumnSchema, TableSchema};
use crate::expr::{Expr, Project};
use crate::{expr::LeftDeepJoin, statement::Statement};
use spacetimedb_lib::AlgebraicType;
use spacetimedb_schema::schema::TableSchema;
use spacetimedb_sql_parser::ast::BinOp;
use spacetimedb_sql_parser::{
ast::{
self,
sub::{SqlAst, SqlSelect},
SqlFrom, SqlIdent, SqlJoin,
},
ast::{sub::SqlSelect, SqlFrom, SqlIdent, SqlJoin},
parser::sub::parse_subscription,
};

use super::{
assert_eq_types,
errors::{DuplicateName, TypingError, Unresolved, Unsupported},
expr::{Expr, Let, RelExpr},
ty::{Symbol, TyCtx, TyEnv},
expr::RelExpr,
type_expr, type_proj, type_select, StatementCtx, StatementSource,
};

/// The result of type checking and name resolution
pub type TypingResult<T> = core::result::Result<T, TypingError>;

/// A view of the database schema
pub trait SchemaView {
fn schema(&self, name: &str) -> Option<Arc<TableSchema>>;
}

#[derive(Default)]
pub struct Relvars(HashMap<Box<str>, Arc<TableSchema>>);

impl Deref for Relvars {
type Target = HashMap<Box<str>, Arc<TableSchema>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for Relvars {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

pub trait TypeChecker {
type Ast;
type Set;

fn type_ast(ctx: &mut TyCtx, ast: Self::Ast, tx: &impl SchemaView) -> TypingResult<RelExpr>;
fn type_ast(ast: Self::Ast, tx: &impl SchemaView) -> TypingResult<Project>;

fn type_set(ctx: &mut TyCtx, ast: Self::Set, tx: &impl SchemaView) -> TypingResult<RelExpr>;
fn type_set(ast: Self::Set, vars: &mut Relvars, tx: &impl SchemaView) -> TypingResult<Project>;

fn type_from(
ctx: &mut TyCtx,
from: SqlFrom<Self::Ast>,
tx: &impl SchemaView,
) -> TypingResult<(RelExpr, Option<Symbol>)> {
fn type_from(from: SqlFrom, vars: &mut Relvars, tx: &impl SchemaView) -> TypingResult<RelExpr> {
match from {
SqlFrom::Expr(expr, None) => Self::type_rel(ctx, expr, tx),
SqlFrom::Expr(expr, Some(SqlIdent(alias))) => {
let (expr, _) = Self::type_rel(ctx, expr, tx)?;
let symbol = ctx.gen_symbol(alias);
Ok((expr, Some(symbol)))
SqlFrom::Expr(SqlIdent(name), SqlIdent(alias)) => {
let schema = Self::type_relvar(tx, &name)?;
vars.insert(alias.clone(), schema.clone());
Ok(RelExpr::RelVar(schema, alias))
}
SqlFrom::Join(r, SqlIdent(alias), joins) => {
// The type environment with which to type the join expressions
let mut env = TyEnv::default();
// The lowered inputs to the join operator
let mut inputs = Vec::new();
// The join expressions or predicates
let mut exprs = Vec::new();
// The types of the join variables or aliases
let mut types = Vec::new();

let input = Self::type_rel(ctx, r, tx)?.0;
let ty = input.ty_id();
let name = ctx.gen_symbol(alias);

env.add(name, ty);
inputs.push(input);
types.push((name, ty));
SqlFrom::Join(SqlIdent(name), SqlIdent(alias), joins) => {
let schema = Self::type_relvar(tx, &name)?;
vars.insert(alias.clone(), schema.clone());
let mut join = RelExpr::RelVar(schema, alias);

for SqlJoin {
expr,
var: SqlIdent(name),
alias: SqlIdent(alias),
on,
} in joins
{
let input = Self::type_rel(ctx, expr, tx)?.0;
let ty = input.ty_id();
let name = ctx.gen_symbol(&alias);

// New join variable is now in scope
if env.add(name, ty).is_some() {
// Check for duplicate aliases
if vars.contains_key(&alias) {
return Err(DuplicateName(alias.into_string()).into());
}

inputs.push(input);
types.push((name, ty));
let rhs = Self::type_relvar(tx, &name)?;
let lhs = Box::new(join);
let var = alias;

vars.insert(var.clone(), rhs.clone());

// Type check join expression with current type environment
if let Some(on) = on {
exprs.push(type_expr(ctx, &env, on, Some(TyId::BOOL))?);
if let Expr::BinOp(BinOp::Eq, a, b) = type_expr(vars, on, Some(&AlgebraicType::Bool))? {
if let (Expr::Field(a), Expr::Field(b)) = (*a, *b) {
join = RelExpr::EqJoin(LeftDeepJoin { lhs, rhs, var }, a, b);
continue;
}
}
unreachable!("Unreachability guaranteed by parser")
}

join = RelExpr::LeftDeepJoin(LeftDeepJoin { lhs, rhs, var });
}

let ty = ctx.add_row_type(types.clone());
let input = RelExpr::Join(inputs.into(), ty);
let vars = types
.into_iter()
.enumerate()
.map(|(i, (name, ty))| (name, Expr::Field(Box::new(Expr::Input(input.ty_id())), i, ty)))
.collect();
Ok((RelExpr::select(input, Let { vars, exprs }), None))
Ok(join)
}
}
}

fn type_rel(
ctx: &mut TyCtx,
expr: ast::RelExpr<Self::Ast>,
tx: &impl SchemaView,
) -> TypingResult<(RelExpr, Option<Symbol>)> {
match expr {
ast::RelExpr::Var(SqlIdent(var)) => {
let schema = tx
.schema(&var)
.ok_or_else(|| Unresolved::table(&var))
.map_err(TypingError::from)?;
let mut types = Vec::new();
for ColumnSchema { col_name, col_type, .. } in schema.columns() {
let id = ctx.add_algebraic_type(col_type);
let name = ctx.gen_symbol(col_name);
types.push((name, id));
}
let id = ctx.add_var_type(schema.table_id, types);
let symbol = ctx.gen_symbol(var);
Ok((RelExpr::RelVar(schema, id), Some(symbol)))
}
ast::RelExpr::Ast(ast) => Ok((Self::type_ast(ctx, *ast, tx)?, None)),
}
fn type_relvar(tx: &impl SchemaView, name: &str) -> TypingResult<Arc<TableSchema>> {
tx.schema(name)
.ok_or_else(|| Unresolved::table(name))
.map_err(TypingError::from)
}
}

/// Type checker for subscriptions
struct SubChecker;

impl TypeChecker for SubChecker {
type Ast = SqlAst;
type Set = SqlAst;
type Ast = SqlSelect;
type Set = SqlSelect;

fn type_ast(ctx: &mut TyCtx, ast: Self::Ast, tx: &impl SchemaView) -> TypingResult<RelExpr> {
Self::type_set(ctx, ast, tx)
fn type_ast(ast: Self::Ast, tx: &impl SchemaView) -> TypingResult<Project> {
Self::type_set(ast, &mut Relvars::default(), tx)
}

fn type_set(ctx: &mut TyCtx, ast: Self::Set, tx: &impl SchemaView) -> TypingResult<RelExpr> {
fn type_set(ast: Self::Set, vars: &mut Relvars, tx: &impl SchemaView) -> TypingResult<Project> {
match ast {
SqlAst::Union(a, b) => {
let a = Self::type_ast(ctx, *a, tx)?;
let b = Self::type_ast(ctx, *b, tx)?;
assert_eq_types(ctx, a.ty_id(), b.ty_id())?;
Ok(RelExpr::Union(Box::new(a), Box::new(b)))
}
SqlAst::Minus(a, b) => {
let a = Self::type_ast(ctx, *a, tx)?;
let b = Self::type_ast(ctx, *b, tx)?;
assert_eq_types(ctx, a.ty_id(), b.ty_id())?;
Ok(RelExpr::Minus(Box::new(a), Box::new(b)))
}
SqlAst::Select(SqlSelect {
SqlSelect {
project,
from,
filter: None,
}) => {
let (input, alias) = Self::type_from(ctx, from, tx)?;
type_proj(ctx, input, alias, project)
} => {
let input = Self::type_from(from, vars, tx)?;
type_proj(input, project, vars)
}
SqlAst::Select(SqlSelect {
SqlSelect {
project,
from,
filter: Some(expr),
}) => {
let (from, alias) = Self::type_from(ctx, from, tx)?;
let input = type_select(ctx, from, alias, expr)?;
type_proj(ctx, input, alias, project)
} => {
let input = Self::type_from(from, vars, tx)?;
type_proj(type_select(input, expr, vars)?, project, vars)
}
}
}
}

/// Parse and type check a subscription query
pub fn parse_and_type_sub(ctx: &mut TyCtx, sql: &str, tx: &impl SchemaView) -> TypingResult<RelExpr> {
let expr = SubChecker::type_ast(ctx, parse_subscription(sql)?, tx)?;
expect_table_type(ctx, expr)
pub fn parse_and_type_sub(sql: &str, tx: &impl SchemaView) -> TypingResult<Project> {
expect_table_type(SubChecker::type_ast(parse_subscription(sql)?, tx)?)
}

/// Parse and type check a *subscription* query into a `StatementCtx`
pub fn compile_sql_sub<'a>(ctx: &mut TyCtx, sql: &'a str, tx: &impl SchemaView) -> TypingResult<StatementCtx<'a>> {
let expr = parse_and_type_sub(ctx, sql, tx)?;
pub fn compile_sql_sub<'a>(sql: &'a str, tx: &impl SchemaView) -> TypingResult<StatementCtx<'a>> {
let expr = parse_and_type_sub(sql, tx)?;
Ok(StatementCtx {
statement: Statement::Select(expr),
sql,
Expand All @@ -190,8 +153,10 @@ pub fn compile_sql_sub<'a>(ctx: &mut TyCtx, sql: &'a str, tx: &impl SchemaView)
}

/// Returns an error if the input type is not a table type or relvar
fn expect_table_type(ctx: &TyCtx, expr: RelExpr) -> TypingResult<RelExpr> {
let _ = expr.ty(ctx)?.expect_relvar().map_err(|_| Unsupported::ReturnType)?;
fn expect_table_type(expr: Project) -> TypingResult<Project> {
if let Project::Fields(..) = expr {
return Err(Unsupported::ReturnType.into());
}
Ok(expr)
}

Expand Down Expand Up @@ -233,7 +198,6 @@ pub mod test_utils {
#[cfg(test)]
mod tests {
use crate::check::test_utils::{build_module_def, SchemaViewer};
use crate::ty::TyCtx;
use spacetimedb_lib::{AlgebraicType, ProductType};
use spacetimedb_schema::def::ModuleDef;

Expand Down Expand Up @@ -274,14 +238,11 @@ mod tests {
"select * from t where t.u32 = 1 or t.str = ''",
"select * from s where s.bytes = 0xABCD or bytes = X'ABCD'",
"select * from s as r where r.bytes = 0xABCD or bytes = X'ABCD'",
"select * from (select t.* from t join s)",
"select * from (select t.* from t join s join s as r where t.u32 = s.u32 and s.u32 = r.u32)",
"select * from (select t.* from t join s on t.u32 = s.u32 where t.f32 = 0.1)",
"select * from (select t.* from t join (select s.u32 from s) s on t.u32 = s.u32)",
"select * from (select t.* from t join (select u32 as a from s) s on t.u32 = s.a)",
"select * from (select * from t union all select * from t)",
"select t.* from t join s",
"select t.* from t join s join s as r where t.u32 = s.u32 and s.u32 = r.u32",
"select t.* from t join s on t.u32 = s.u32 where t.f32 = 0.1",
] {
let result = parse_and_type_sub(&mut TyCtx::default(), sql, &tx);
let result = parse_and_type_sub(sql, &tx);
assert!(result.is_ok());
}
}
Expand All @@ -308,21 +269,13 @@ mod tests {
// Subscriptions must be typed to a single table
"select * from t join s",
// Self join requires aliases
"select * from (select t.* from t join t)",
"select t.* from t join t",
// Product values are not comparable
"select * from (select t.* from t join s on t.arr = s.arr)",
// Subscriptions must be typed to a single table
"select * from (select s.* from t join (select u32 from s) s on t.u32 = s.u32)",
// Field u32 has been renamed
"select * from (select t.* from t join (select u32 as a from s) s on t.u32 = s.u32)",
// Field bytes is no longer in scope
"select * from (select t.* from t join (select u32 from s) s on s.bytes = 0xABCD)",
"select t.* from t join s on t.arr = s.arr",
// Alias r is not in scope when it is referenced
"select * from (select t.* from t join s on t.u32 = r.u32 join s as r)",
// Union arguments are of different types
"select * from (select * from t union all select * from s)",
"select t.* from t join s on t.u32 = r.u32 join s as r",
] {
let result = parse_and_type_sub(&mut TyCtx::default(), sql, &tx);
let result = parse_and_type_sub(sql, &tx);
assert!(result.is_err());
}
}
Expand Down
Loading

2 comments on commit 9211708

@github-actions
Copy link

@github-actions github-actions bot commented on 9211708 Dec 12, 2024

Choose a reason for hiding this comment

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

Criterion benchmark results

Criterion benchmark report

YOU SHOULD PROBABLY IGNORE THESE RESULTS.

Criterion is a wall time based benchmarking system that is extremely noisy when run on CI. We collect these results for longitudinal analysis, but they are not reliable for comparing individual PRs.

Go look at the callgrind report instead.

empty

db on disk new latency old latency new throughput old throughput
sqlite 💿 418.4±3.72ns 409.6±2.01ns - -
sqlite 🧠 418.6±2.06ns 401.5±2.03ns - -
stdb_raw 💿 771.0±1.79ns 773.4±2.15ns - -
stdb_raw 🧠 771.3±1.96ns 773.3±3.78ns - -

insert_1

db on disk schema indices preload new latency old latency new throughput old throughput

insert_bulk

db on disk schema indices preload count new latency old latency new throughput old throughput
sqlite 💿 u32_u64_str btree_each_column 2048 256 584.9±0.80µs 577.1±0.96µs 1709 tx/sec 1732 tx/sec
sqlite 💿 u32_u64_str unique_0 2048 256 151.9±0.50µs 147.7±10.64µs 6.4 Ktx/sec 6.6 Ktx/sec
sqlite 💿 u32_u64_u64 btree_each_column 2048 256 467.9±0.75µs 461.7±0.31µs 2.1 Ktx/sec 2.1 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 2048 256 138.8±0.55µs 134.7±1.23µs 7.0 Ktx/sec 7.3 Ktx/sec
sqlite 🧠 u32_u64_str btree_each_column 2048 256 448.2±0.21µs 438.1±0.54µs 2.2 Ktx/sec 2.2 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 2048 256 127.4±0.79µs 118.1±0.93µs 7.7 Ktx/sec 8.3 Ktx/sec
sqlite 🧠 u32_u64_u64 btree_each_column 2048 256 367.7±0.65µs 365.0±0.41µs 2.7 Ktx/sec 2.7 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 2048 256 109.4±0.30µs 103.8±0.55µs 8.9 Ktx/sec 9.4 Ktx/sec
stdb_raw 💿 u32_u64_str btree_each_column 2048 256 604.5±20.60µs 596.4±18.39µs 1654 tx/sec 1676 tx/sec
stdb_raw 💿 u32_u64_str unique_0 2048 256 448.9±21.65µs 444.4±19.48µs 2.2 Ktx/sec 2.2 Ktx/sec
stdb_raw 💿 u32_u64_u64 btree_each_column 2048 256 379.1±7.58µs 329.5±6.49µs 2.6 Ktx/sec 3.0 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 2048 256 323.8±11.78µs 312.8±12.59µs 3.0 Ktx/sec 3.1 Ktx/sec
stdb_raw 🧠 u32_u64_str btree_each_column 2048 256 305.0±0.26µs 299.0±0.18µs 3.2 Ktx/sec 3.3 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 2048 256 197.0±0.17µs 191.1±0.26µs 5.0 Ktx/sec 5.1 Ktx/sec
stdb_raw 🧠 u32_u64_u64 btree_each_column 2048 256 242.5±0.08µs 239.2±0.23µs 4.0 Ktx/sec 4.1 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 2048 256 180.0±0.20µs 170.7±0.14µs 5.4 Ktx/sec 5.7 Ktx/sec

iterate

db on disk schema indices new latency old latency new throughput old throughput
sqlite 💿 u32_u64_str unique_0 24.0±0.14µs 21.7±0.24µs 40.7 Ktx/sec 45.0 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 21.6±0.07µs 19.9±0.16µs 45.1 Ktx/sec 49.0 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 21.2±0.12µs 19.1±0.10µs 46.0 Ktx/sec 51.0 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 19.1±0.06µs 17.2±0.11µs 51.1 Ktx/sec 56.8 Ktx/sec
stdb_raw 💿 u32_u64_str unique_0 4.5±0.01µs 4.4±0.00µs 215.2 Ktx/sec 221.0 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 4.4±0.01µs 4.3±0.00µs 222.4 Ktx/sec 226.1 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 4.5±0.02µs 4.4±0.00µs 214.8 Ktx/sec 220.6 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 4.4±0.01µs 4.3±0.00µs 221.6 Ktx/sec 226.2 Ktx/sec

find_unique

db on disk key type preload new latency old latency new throughput old throughput

filter

db on disk key type index strategy load count new latency old latency new throughput old throughput
sqlite 💿 string index 2048 256 67.5±0.14µs 69.2±0.08µs 14.5 Ktx/sec 14.1 Ktx/sec
sqlite 💿 u64 index 2048 256 64.0±0.21µs 65.1±0.12µs 15.3 Ktx/sec 15.0 Ktx/sec
sqlite 🧠 string index 2048 256 63.0±0.08µs 64.6±0.07µs 15.5 Ktx/sec 15.1 Ktx/sec
sqlite 🧠 u64 index 2048 256 58.9±0.26µs 59.9±0.06µs 16.6 Ktx/sec 16.3 Ktx/sec
stdb_raw 💿 string index 2048 256 5.0±0.00µs 4.9±0.00µs 195.2 Ktx/sec 197.8 Ktx/sec
stdb_raw 💿 u64 index 2048 256 4.8±0.01µs 4.9±0.01µs 202.2 Ktx/sec 200.8 Ktx/sec
stdb_raw 🧠 string index 2048 256 5.0±0.00µs 4.9±0.00µs 195.3 Ktx/sec 198.0 Ktx/sec
stdb_raw 🧠 u64 index 2048 256 4.8±0.01µs 4.9±0.01µs 202.4 Ktx/sec 201.0 Ktx/sec

serialize

schema format count new latency old latency new throughput old throughput
u32_u64_str bflatn_to_bsatn_fast_path 100 3.6±0.00µs 3.7±0.00µs 26.4 Mtx/sec 26.1 Mtx/sec
u32_u64_str bflatn_to_bsatn_slow_path 100 3.6±0.00µs 3.6±0.01µs 26.8 Mtx/sec 26.7 Mtx/sec
u32_u64_str bsatn 100 17.3±0.01ns 15.3±0.01ns 5.4 Gtx/sec 6.1 Gtx/sec
u32_u64_str bsatn 100 2.4±0.00µs 2.4±0.00µs 40.3 Mtx/sec 40.2 Mtx/sec
u32_u64_str json 100 4.8±0.14µs 4.8±0.04µs 19.8 Mtx/sec 20.0 Mtx/sec
u32_u64_str json 100 8.9±0.02µs 8.7±0.03µs 10.7 Mtx/sec 11.0 Mtx/sec
u32_u64_str product_value 100 1072.6±0.68ns 1073.8±2.97ns 88.9 Mtx/sec 88.8 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_fast_path 100 944.3±20.55ns 947.6±1.19ns 101.0 Mtx/sec 100.6 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_slow_path 100 2.8±0.00µs 2.8±0.00µs 34.4 Mtx/sec 34.2 Mtx/sec
u32_u64_u64 bsatn 100 15.3±0.00ns 15.9±0.01ns 6.1 Gtx/sec 5.9 Gtx/sec
u32_u64_u64 bsatn 100 1600.3±23.96ns 1706.8±21.84ns 59.6 Mtx/sec 55.9 Mtx/sec
u32_u64_u64 json 100 3.2±0.04µs 3.0±0.03µs 30.1 Mtx/sec 31.6 Mtx/sec
u32_u64_u64 json 100 6.0±0.07µs 6.0±0.22µs 15.8 Mtx/sec 16.0 Mtx/sec
u32_u64_u64 product_value 100 1076.9±0.84ns 1073.1±0.92ns 88.6 Mtx/sec 88.9 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_fast_path 100 773.6±3.43ns 726.8±0.52ns 123.3 Mtx/sec 131.2 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_slow_path 100 2.8±0.00µs 2.8±0.00µs 34.3 Mtx/sec 34.2 Mtx/sec
u64_u64_u32 bsatn 100 1594.8±14.79ns 1716.5±39.67ns 59.8 Mtx/sec 55.6 Mtx/sec
u64_u64_u32 bsatn 100 251.9±0.86ns 249.7±1.12ns 378.6 Mtx/sec 381.9 Mtx/sec
u64_u64_u32 json 100 3.2±0.15µs 3.0±0.03µs 29.4 Mtx/sec 31.6 Mtx/sec
u64_u64_u32 json 100 6.3±0.09µs 6.1±0.05µs 15.2 Mtx/sec 15.5 Mtx/sec
u64_u64_u32 product_value 100 1074.2±1.35ns 1072.9±0.56ns 88.8 Mtx/sec 88.9 Mtx/sec

stdb_module_large_arguments

arg size new latency old latency new throughput old throughput

stdb_module_print_bulk

line count new latency old latency new throughput old throughput

remaining

name new latency old latency new throughput old throughput
special/db_game/csharp/circles/load=10 2.7±0.01s 2.7±0.01s - -
special/db_game/csharp/circles/load=100 2.7±0.01s 2.7±0.01s - -
special/db_game/csharp/ia_loop/load=10 349.1±1.13ms 350.6±1.35ms - -
special/db_game/csharp/ia_loop/load=100 919.6±5.23ms 923.0±5.35ms - -
special/db_game/rust/circles/load=10 48.9±4.98ms 47.2±4.05ms - -
special/db_game/rust/circles/load=100 39.5±3.17ms 54.8±3.98ms - -
special/db_game/rust/ia_loop/load=10 9.5±0.50ms 9.3±0.52ms - -
special/db_game/rust/ia_loop/load=100 10.9±0.56ms 11.1±0.24ms - -
special/stdb_module/csharp/large_arguments/64KiB 2.5±0.51ms 2.6±1.06ms - -
special/stdb_module/csharp/print_bulk/lines=1 164.3±10.28µs 175.0±10.55µs - -
special/stdb_module/csharp/print_bulk/lines=100 1828.4±279.31µs 1797.5±292.46µs - -
special/stdb_module/csharp/print_bulk/lines=1000 18.1±0.54ms 14.4±2.96ms - -
special/stdb_module/rust/large_arguments/64KiB 103.3±6.22µs 106.5±7.16µs - -
special/stdb_module/rust/print_bulk/lines=1 51.0±5.19µs 53.7±5.37µs - -
special/stdb_module/rust/print_bulk/lines=100 593.1±4.74µs 597.5±8.65µs - -
special/stdb_module/rust/print_bulk/lines=1000 4.5±0.96ms 4.6±0.77ms - -
sqlite/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 56.1±0.20µs 52.3±0.08µs 17.4 Ktx/sec 18.7 Ktx/sec
sqlite/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 47.9±0.15µs 45.0±0.10µs 20.4 Ktx/sec 21.7 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 40.8±0.17µs 37.4±0.15µs 23.9 Ktx/sec 26.1 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 36.3±0.21µs 33.6±0.12µs 26.9 Ktx/sec 29.0 Ktx/sec
stdb_module/csharp/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 11.9±0.55ms 11.7±0.26ms 84 tx/sec 85 tx/sec
stdb_module/csharp/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 9.6±0.64ms 9.4±0.62ms 103 tx/sec 106 tx/sec
stdb_module/rust/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 1239.6±22.85µs 1237.8±31.67µs 806 tx/sec 807 tx/sec
stdb_module/rust/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 962.3±12.25µs 963.3±29.65µs 1039 tx/sec 1038 tx/sec
stdb_raw/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 519.5±11.00µs 619.0±20.58µs 1924 tx/sec 1615 tx/sec
stdb_raw/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 412.5±5.82µs 462.2±11.91µs 2.4 Ktx/sec 2.1 Ktx/sec
stdb_raw/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 345.2±0.31µs 341.5±0.37µs 2.8 Ktx/sec 2.9 Ktx/sec
stdb_raw/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 307.9±0.16µs 307.9±0.39µs 3.2 Ktx/sec 3.2 Ktx/sec

@github-actions
Copy link

@github-actions github-actions bot commented on 9211708 Dec 12, 2024

Choose a reason for hiding this comment

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

Callgrind benchmark results

Callgrind Benchmark Report

These benchmarks were run using callgrind,
an instruction-level profiler. They allow comparisons between sqlite (sqlite), SpacetimeDB running through a module (stdb_module), and the underlying SpacetimeDB data storage engine (stdb_raw). Callgrind emulates a CPU to collect the below estimates.

Measurement changes larger than five percent are in bold.

In-memory benchmarks

callgrind: empty transaction

db total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw 6433 6455 -0.34% 6567 6559 0.12%
sqlite 5609 5609 0.00% 6075 5951 2.08%

callgrind: filter

db schema indices count preload _column data_type total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str no_index 64 128 1 u64 75245 75267 -0.03% 75685 75703 -0.02%
stdb_raw u32_u64_str no_index 64 128 2 string 117529 117551 -0.02% 118203 118369 -0.14%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 25049 25090 -0.16% 25519 25668 -0.58%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 24017 24039 -0.09% 24385 24463 -0.32%
sqlite u32_u64_str no_index 64 128 2 string 144358 144351 0.00% 145788 145901 -0.08%
sqlite u32_u64_str no_index 64 128 1 u64 123749 123742 0.01% 125021 125194 -0.14%
sqlite u32_u64_str btree_each_column 64 128 1 u64 131066 131059 0.01% 132372 132691 -0.24%
sqlite u32_u64_str btree_each_column 64 128 2 string 134165 134158 0.01% 135625 135824 -0.15%

callgrind: insert bulk

db schema indices count preload total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 64 128 813790 814989 -0.15% 859926 832365 3.31%
stdb_raw u32_u64_str btree_each_column 64 128 1041415 1037127 0.41% 1072691 1064925 0.73%
sqlite u32_u64_str unique_0 64 128 399362 399360 0.00% 414254 415586 -0.32%
sqlite u32_u64_str btree_each_column 64 128 984619 984611 0.00% 1021229 1024095 -0.28%

callgrind: iterate

db schema indices count total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 1024 138426 138448 -0.02% 138550 138564 -0.01%
stdb_raw u32_u64_str unique_0 64 15851 15873 -0.14% 15959 15985 -0.16%
sqlite u32_u64_str unique_0 1024 1042718 1042718 0.00% 1046142 1046104 0.00%
sqlite u32_u64_str unique_0 64 74704 74704 0.00% 75858 75892 -0.04%

callgrind: serialize_product_value

count format total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
64 json 47528 47528 0.00% 50282 50316 -0.07%
64 bsatn 25509 25509 0.00% 27753 27787 -0.12%
16 bsatn 8200 8200 0.00% 9594 9628 -0.35%
16 json 12188 12188 0.00% 14194 14194 0.00%

callgrind: update bulk

db schema indices count preload total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 1024 1024 19248422 19247980 0.00% 19781022 19736830 0.22%
stdb_raw u32_u64_str unique_0 64 128 1237895 1250433 -1.00% 1306897 1317695 -0.82%
sqlite u32_u64_str unique_0 1024 1024 1802085 1802137 -0.00% 1811197 1811113 0.00%
sqlite u32_u64_str unique_0 64 128 128488 128540 -0.04% 131404 131306 0.07%
On-disk benchmarks

callgrind: empty transaction

db total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw 6438 6460 -0.34% 6576 6564 0.18%
sqlite 5651 5651 0.00% 6143 6019 2.06%

callgrind: filter

db schema indices count preload _column data_type total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str no_index 64 128 1 u64 75250 75272 -0.03% 75694 75704 -0.01%
stdb_raw u32_u64_str no_index 64 128 2 string 117534 117556 -0.02% 118136 118218 -0.07%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 25055 25076 -0.08% 25489 25602 -0.44%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 24022 24044 -0.09% 24378 24460 -0.34%
sqlite u32_u64_str no_index 64 128 1 u64 125664 125663 0.00% 127074 127307 -0.18%
sqlite u32_u64_str no_index 64 128 2 string 146285 146272 0.01% 147941 148030 -0.06%
sqlite u32_u64_str btree_each_column 64 128 2 string 136345 136350 -0.00% 138301 138360 -0.04%
sqlite u32_u64_str btree_each_column 64 128 1 u64 133156 133155 0.00% 134882 135095 -0.16%

callgrind: insert bulk

db schema indices count preload total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 64 128 763005 763589 -0.08% 808489 810989 -0.31%
stdb_raw u32_u64_str btree_each_column 64 128 987701 986499 0.12% 1048835 1014731 3.36%
sqlite u32_u64_str unique_0 64 128 416910 416908 0.00% 431138 432638 -0.35%
sqlite u32_u64_str btree_each_column 64 128 1023160 1023158 0.00% 1058266 1062400 -0.39%

callgrind: iterate

db schema indices count total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 1024 138415 138453 -0.03% 138511 138549 -0.03%
stdb_raw u32_u64_str unique_0 64 15856 15878 -0.14% 15956 15978 -0.14%
sqlite u32_u64_str unique_0 1024 1045786 1045786 0.00% 1049568 1049538 0.00%
sqlite u32_u64_str unique_0 64 76476 76476 0.00% 77842 77788 0.07%

callgrind: serialize_product_value

count format total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
64 json 47528 47528 0.00% 50282 50316 -0.07%
64 bsatn 25509 25509 0.00% 27753 27787 -0.12%
16 bsatn 8200 8200 0.00% 9594 9628 -0.35%
16 json 12188 12188 0.00% 14194 14194 0.00%

callgrind: update bulk

db schema indices count preload total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw u32_u64_str unique_0 1024 1024 17962579 17965952 -0.02% 18517075 18521832 -0.03%
stdb_raw u32_u64_str unique_0 64 128 1189133 1191056 -0.16% 1255501 1257898 -0.19%
sqlite u32_u64_str unique_0 1024 1024 1809727 1809785 -0.00% 1818319 1818521 -0.01%
sqlite u32_u64_str unique_0 64 128 132629 132687 -0.04% 135641 135517 0.09%

Please sign in to comment.