Skip to content

Commit

Permalink
RLS: Adding a new filter! macro (#1849)
Browse files Browse the repository at this point in the history
Signed-off-by: Mario Montoya <mamcx@elmalabarista.com>
Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
  • Loading branch information
mamcx and joshua-spacetime authored Oct 17, 2024
1 parent 0a86b46 commit 637d6d7
Show file tree
Hide file tree
Showing 16 changed files with 269 additions and 22 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.

1 change: 1 addition & 0 deletions crates/bindings-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ bench = false

[dependencies]
spacetimedb-primitives.workspace = true
spacetimedb-sql-parser.workspace = true

bitflags.workspace = true
humantime.workspace = true
Expand Down
72 changes: 72 additions & 0 deletions crates/bindings-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use proc_macro::TokenStream as StdTokenStream;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use std::borrow::Cow;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::time::Duration;
use syn::ext::IdentExt;
use syn::meta::ParseNestedMeta;
Expand Down Expand Up @@ -1237,3 +1238,74 @@ pub fn schema_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}

fn parse_sql(input: ParseStream) -> syn::Result<String> {
use spacetimedb_sql_parser::parser::sub;

let lookahead = input.lookahead1();
let sql = if lookahead.peek(syn::LitStr) {
let s = input.parse::<syn::LitStr>()?;
// Checks the query is syntactically valid
let _ = sub::parse_subscription(&s.value()).map_err(|e| syn::Error::new(s.span(), format_args!("{e}")))?;

s.value()
} else {
return Err(lookahead.error());
};

Ok(sql)
}

/// Generates code for registering a row-level security `SQL` function.
///
/// A row-level security function takes a `SQL` query expression that is used to filter rows.
///
/// The query follows the same syntax as a subscription query.
///
/// **Example:**
///
/// ```rust,ignore
/// /// Players can only see what's in their chunk
/// spacetimedb::filter!("
/// SELECT * FROM LocationState WHERE chunk_index IN (
/// SELECT chunk_index FROM LocationState WHERE entity_id IN (
/// SELECT entity_id FROM UserState WHERE identity = @sender
/// )
/// )
/// ");
/// ```
///
/// **NOTE:** The `SQL` query expression is pre-parsed at compile time, but only check is a valid
/// subscription query *syntactically*, not that the query is valid when executed.
///
/// For example, it could refer to a non-existent table.
#[proc_macro]
pub fn filter(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let rls_sql = syn::parse_macro_input!(input with parse_sql);

let mut hasher = DefaultHasher::new();
rls_sql.hash(&mut hasher);
let rls_name = format_ident!("rls_{}", hasher.finish());

let register_rls_symbol = format!("__preinit__20_register_{rls_name}");

let generated_describe_function = quote! {
#[export_name = #register_rls_symbol]
extern "C" fn __register_rls() {
spacetimedb::rt::register_row_level_security::<#rls_name>()
}
};

let emission = quote! {
const _: () = {
#generated_describe_function
};
#[allow(non_camel_case_types)]
struct #rls_name { _never: ::core::convert::Infallible }
impl spacetimedb::rt::RowLevelSecurityInfo for #rls_name {
const SQL: &'static str = #rls_sql;
}
};

emission.into()
}
2 changes: 1 addition & 1 deletion crates/bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub use rng::StdbRng;
pub use sats::SpacetimeType;
#[doc(hidden)]
pub use spacetimedb_bindings_macro::__TableHelper;
pub use spacetimedb_bindings_macro::{duration, reducer, table};
pub use spacetimedb_bindings_macro::{duration, filter, reducer, table};
pub use spacetimedb_bindings_sys as sys;
pub use spacetimedb_lib;
pub use spacetimedb_lib::de::{Deserialize, DeserializeOwned};
Expand Down
13 changes: 13 additions & 0 deletions crates/bindings/src/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,12 @@ impl RepeaterArgs for (Timestamp,) {
}
}

/// A trait for types that can *describe* a row-level security policy.
pub trait RowLevelSecurityInfo {
/// The SQL expression for the row-level security policy.
const SQL: &'static str;
}

/// Registers into `DESCRIBERS` a function `f` to modify the module builder.
fn register_describer(f: fn(&mut ModuleBuilder)) {
DESCRIBERS.lock().unwrap().push(f)
Expand Down Expand Up @@ -352,6 +358,13 @@ pub fn register_reducer<'a, A: Args<'a>, I: ReducerInfo>(_: impl Reducer<'a, A>)
})
}

/// Registers a row-level security policy.
pub fn register_row_level_security<R: RowLevelSecurityInfo>() {
register_describer(|module| {
module.inner.add_row_level_security(R::SQL);
})
}

/// A builder for a module.
#[derive(Default)]
struct ModuleBuilder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
source: crates/bindings/tests/deps.rs
expression: "cargo tree -p spacetimedb -f {lib} -e no-dev"
---
total crates: 62
total crates: 64
spacetimedb
├── bytemuck
├── derive_more
Expand Down Expand Up @@ -48,6 +48,15 @@ spacetimedb
│ │ ├── itertools
│ │ │ └── either
│ │ └── nohash_hasher
│ ├── spacetimedb_sql_parser
│ │ ├── derive_more (*)
│ │ ├── sqlparser
│ │ │ └── log
│ │ └── thiserror
│ │ └── thiserror_impl
│ │ ├── proc_macro2 (*)
│ │ ├── quote (*)
│ │ └── syn (*)
│ └── syn (*)
├── spacetimedb_bindings_sys
│ └── spacetimedb_primitives (*)
Expand Down Expand Up @@ -75,11 +84,7 @@ spacetimedb
│ │ │ └── equivalent
│ │ ├── nohash_hasher
│ │ ├── smallvec
│ │ └── thiserror
│ │ └── thiserror_impl
│ │ ├── proc_macro2 (*)
│ │ ├── quote (*)
│ │ └── syn (*)
│ │ └── thiserror (*)
│ ├── spacetimedb_primitives (*)
│ ├── spacetimedb_sats
│ │ ├── arrayvec
Expand Down
20 changes: 19 additions & 1 deletion crates/core/src/db/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use super::datastore::locking_tx_datastore::MutTxId;
use super::relational_db::RelationalDB;
use crate::database_logger::SystemLogger;
use crate::execution_context::ExecutionContext;
use crate::sql::parser::RowLevelExpr;
use spacetimedb_data_structures::map::HashMap;
use spacetimedb_lib::db::auth::StTableType;
use spacetimedb_lib::identity::AuthCtx;
use spacetimedb_lib::AlgebraicValue;
use spacetimedb_primitives::ColSet;
use spacetimedb_schema::auto_migrate::{AutoMigratePlan, ManualMigratePlan, MigratePlan};
Expand All @@ -24,6 +26,7 @@ use std::sync::Arc;
pub fn update_database(
stdb: &RelationalDB,
tx: &mut MutTxId,
auth_ctx: AuthCtx,
plan: MigratePlan,
system_logger: &SystemLogger,
) -> anyhow::Result<()> {
Expand All @@ -44,7 +47,7 @@ pub fn update_database(

match plan {
MigratePlan::Manual(plan) => manual_migrate_database(stdb, tx, plan, system_logger, existing_tables),
MigratePlan::Auto(plan) => auto_migrate_database(stdb, tx, plan, system_logger, existing_tables),
MigratePlan::Auto(plan) => auto_migrate_database(stdb, tx, auth_ctx, plan, system_logger, existing_tables),
}
}

Expand All @@ -63,6 +66,7 @@ fn manual_migrate_database(
fn auto_migrate_database(
stdb: &RelationalDB,
tx: &mut MutTxId,
auth_ctx: AuthCtx,
plan: AutoMigratePlan,
system_logger: &SystemLogger,
existing_tables: Vec<Arc<TableSchema>>,
Expand Down Expand Up @@ -119,6 +123,7 @@ fn auto_migrate_database(

system_logger.info(&format!("Creating table `{}`", table_name));
log::info!("Creating table `{}`", table_name);

stdb.create_table(tx, table_schema)?;
}
spacetimedb_schema::auto_migrate::AutoMigrateStep::AddIndex(index_name) => {
Expand Down Expand Up @@ -221,6 +226,19 @@ fn auto_migrate_database(
spacetimedb_schema::auto_migrate::AutoMigrateStep::RemoveSchedule(_) => {
anyhow::bail!("Removing schedules is not yet implemented");
}
spacetimedb_schema::auto_migrate::AutoMigrateStep::AddRowLevelSecurity(sql_rls) => {
system_logger.info(&format!("Adding row-level security `{sql_rls}`"));
log::info!("Adding row-level security `{sql_rls}`");
let rls = plan.new.lookup_expect(sql_rls);
let rls = RowLevelExpr::build_row_level_expr(stdb, tx, &auth_ctx, rls)?;

stdb.create_row_level_security(tx, rls.def)?;
}
spacetimedb_schema::auto_migrate::AutoMigrateStep::RemoveRowLevelSecurity(sql_rls) => {
system_logger.info(&format!("Removing-row level security `{sql_rls}`"));
log::info!("Removing row-level security `{sql_rls}`");
stdb.drop_row_level_security(tx, sql_rls.clone())?;
}
}
}

Expand Down
24 changes: 20 additions & 4 deletions crates/core/src/host/wasm_common/module_host_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ use spacetimedb_schema::schema::{Schema, TableSchema};
use std::sync::Arc;
use std::time::Duration;

use spacetimedb_lib::buffer::DecodeError;
use spacetimedb_lib::{bsatn, Address, RawModuleDef};

use super::instrumentation::CallTimes;
use crate::database_logger::SystemLogger;
use crate::db::datastore::locking_tx_datastore::MutTxId;
Expand All @@ -28,10 +25,14 @@ use crate::identity::Identity;
use crate::messages::control_db::HostType;
use crate::module_host_context::ModuleCreationContext;
use crate::replica_context::ReplicaContext;
use crate::sql::parser::RowLevelExpr;
use crate::subscription::module_subscription_actor::WriteConflict;
use crate::util::const_unwrap;
use crate::util::prometheus_handle::HistogramExt;
use crate::worker_metrics::WORKER_METRICS;
use spacetimedb_lib::buffer::DecodeError;
use spacetimedb_lib::identity::AuthCtx;
use spacetimedb_lib::{bsatn, Address, RawModuleDef};

use super::*;

Expand Down Expand Up @@ -263,6 +264,7 @@ impl<T: WasmInstance> ModuleInstance for WasmModuleInstance<T> {
let timestamp = Timestamp::now();
let stdb = &*self.replica_context().relational_db;
let ctx = ExecutionContext::internal(stdb.address());
let auth_ctx = AuthCtx::for_current(self.replica_context().database.owner_identity);
let tx = stdb.begin_mut_tx(IsolationLevel::Serializable);
let (tx, ()) = stdb
.with_auto_rollback(&ctx, tx, |tx| {
Expand All @@ -276,6 +278,19 @@ impl<T: WasmInstance> ModuleInstance for WasmModuleInstance<T> {
stdb.create_table(tx, schema)
.with_context(|| format!("failed to create table {table_name}"))?;
}
// Insert the late-bound row-level security expressions.
for rls in self.info.module_def.row_level_security() {
self.system_logger()
.info(&format!("Creating row level security `{}`", rls.sql));

let rls = RowLevelExpr::build_row_level_expr(stdb, tx, &auth_ctx, rls)
.with_context(|| format!("failed to create row-level security: `{}`", rls.sql))?;
let table_id = rls.def.table_id;
let sql = rls.def.sql.clone();
stdb.create_row_level_security(tx, rls.def).with_context(|| {
format!("failed to create row-level security for table `{table_id}`: `{sql}`",)
})?;
}

stdb.set_initialized(tx, HostType::Wasm, program)?;

Expand Down Expand Up @@ -335,7 +350,8 @@ impl<T: WasmInstance> ModuleInstance for WasmModuleInstance<T> {
let (mut tx, _) = stdb.with_auto_rollback(&ctx, tx, |tx| stdb.update_program(tx, HostType::Wasm, program))?;
self.system_logger().info(&format!("Updated program to {program_hash}"));

let res = crate::db::update::update_database(stdb, &mut tx, plan, self.system_logger());
let auth_ctx = AuthCtx::for_current(self.replica_context().database.owner_identity);
let res = crate::db::update::update_database(stdb, &mut tx, auth_ctx, plan, self.system_logger());

match res {
Err(e) => {
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/sql/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod ast;
pub mod compiler;
pub mod execute;
pub mod parser;
mod type_check;
35 changes: 35 additions & 0 deletions crates/core/src/sql/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use crate::db::datastore::locking_tx_datastore::MutTxId;
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_lib::db::raw_def::v9::RawRowLevelSecurityDefV9;
use spacetimedb_lib::identity::AuthCtx;
use spacetimedb_schema::schema::RowLevelSecuritySchema;

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

impl RowLevelExpr {
pub fn build_row_level_expr(
stdb: &RelationalDB,
tx: &mut MutTxId,
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))?;

Ok(Self {
def: RowLevelSecuritySchema {
table_id: sql.table_id(&mut ctx)?,
sql: rls.sql.clone(),
},
sql,
})
}
}
12 changes: 9 additions & 3 deletions crates/expr/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use spacetimedb_sql_parser::{ast::BinOp, parser::errors::SqlParseError};
use thiserror::Error;

use super::{
statement::InvalidVar,
ty::{InvalidTypeId, TypeWithCtx},
};
use spacetimedb_sql_parser::ast::BinOp;
use spacetimedb_sql_parser::parser::errors::SqlParseError;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Unresolved {
Expand Down Expand Up @@ -134,6 +134,10 @@ impl UnexpectedType {
#[error("Duplicate name `{0}`")]
pub struct DuplicateName(pub String);

#[derive(Debug, Error)]
#[error("`filter!` does not support column projections; Must return table rows")]
pub struct FilterReturnType;

#[derive(Error, Debug)]
pub enum TypingError {
#[error(transparent)]
Expand Down Expand Up @@ -163,4 +167,6 @@ pub enum TypingError {
Wildcard(#[from] InvalidWildcard),
#[error(transparent)]
DuplicateName(#[from] DuplicateName),
#[error(transparent)]
FilterReturnType(#[from] FilterReturnType),
}
Loading

2 comments on commit 637d6d7

@github-actions
Copy link

@github-actions github-actions bot commented on 637d6d7 Oct 17, 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 💿 419.4±2.73ns 420.6±4.59ns - -
sqlite 🧠 409.5±1.91ns 410.6±2.05ns - -
stdb_raw 💿 623.1±1.20ns 625.8±3.62ns - -
stdb_raw 🧠 623.7±1.32ns 626.2±3.26ns - -

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 589.4±0.52µs 583.9±0.97µs 1696 tx/sec 1712 tx/sec
sqlite 💿 u32_u64_str unique_0 2048 256 152.5±0.33µs 150.2±0.52µs 6.4 Ktx/sec 6.5 Ktx/sec
sqlite 💿 u32_u64_u64 btree_each_column 2048 256 467.7±0.82µs 467.0±0.71µs 2.1 Ktx/sec 2.1 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 2048 256 138.2±0.97µs 137.2±0.28µs 7.1 Ktx/sec 7.1 Ktx/sec
sqlite 🧠 u32_u64_str btree_each_column 2048 256 449.0±0.64µs 447.7±0.73µs 2.2 Ktx/sec 2.2 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 2048 256 124.6±0.49µs 123.9±0.71µs 7.8 Ktx/sec 7.9 Ktx/sec
sqlite 🧠 u32_u64_u64 btree_each_column 2048 256 366.3±0.33µs 367.5±0.59µs 2.7 Ktx/sec 2.7 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 2048 256 104.4±1.10µs 108.9±0.92µs 9.4 Ktx/sec 9.0 Ktx/sec
stdb_raw 💿 u32_u64_str btree_each_column 2048 256 640.0±22.13µs 627.6±24.88µs 1562 tx/sec 1593 tx/sec
stdb_raw 💿 u32_u64_str unique_0 2048 256 416.9±13.11µs 420.1±19.50µs 2.3 Ktx/sec 2.3 Ktx/sec
stdb_raw 💿 u32_u64_u64 btree_each_column 2048 256 390.1±7.84µs 393.0±7.65µs 2.5 Ktx/sec 2.5 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 2048 256 353.3±20.41µs 362.8±6.32µs 2.8 Ktx/sec 2.7 Ktx/sec
stdb_raw 🧠 u32_u64_str btree_each_column 2048 256 307.9±0.19µs 316.8±0.45µs 3.2 Ktx/sec 3.1 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 2048 256 242.1±0.69µs 244.7±0.67µs 4.0 Ktx/sec 4.0 Ktx/sec
stdb_raw 🧠 u32_u64_u64 btree_each_column 2048 256 247.2±0.14µs 260.0±0.10µs 4.0 Ktx/sec 3.8 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 2048 256 218.9±0.38µs 228.9±0.24µs 4.5 Ktx/sec 4.3 Ktx/sec

iterate

db on disk schema indices new latency old latency new throughput old throughput
sqlite 💿 u32_u64_str unique_0 23.5±0.13µs 23.9±0.24µs 41.6 Ktx/sec 40.9 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 21.7±0.17µs 22.3±0.19µs 44.9 Ktx/sec 43.8 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 21.4±0.11µs 20.7±0.22µs 45.7 Ktx/sec 47.2 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 19.2±0.23µs 20.0±0.17µs 50.8 Ktx/sec 48.9 Ktx/sec
stdb_raw 💿 u32_u64_str unique_0 4.8±0.00µs 3.8±0.00µs 204.8 Ktx/sec 256.6 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 4.6±0.00µs 3.7±0.00µs 210.1 Ktx/sec 263.5 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 4.8±0.00µs 3.8±0.00µs 204.6 Ktx/sec 257.3 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 4.6±0.00µs 3.7±0.00µs 210.1 Ktx/sec 263.4 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 69.5±0.29µs 67.7±0.22µs 14.1 Ktx/sec 14.4 Ktx/sec
sqlite 💿 u64 index 2048 256 65.8±0.04µs 66.1±0.14µs 14.8 Ktx/sec 14.8 Ktx/sec
sqlite 🧠 string index 2048 256 65.2±0.12µs 65.1±0.11µs 15.0 Ktx/sec 15.0 Ktx/sec
sqlite 🧠 u64 index 2048 256 59.9±0.08µs 60.8±0.14µs 16.3 Ktx/sec 16.1 Ktx/sec
stdb_raw 💿 string index 2048 256 7.0±0.00µs 4.8±0.00µs 138.7 Ktx/sec 203.7 Ktx/sec
stdb_raw 💿 u64 index 2048 256 4.7±0.00µs 4.9±0.00µs 208.4 Ktx/sec 201.1 Ktx/sec
stdb_raw 🧠 string index 2048 256 7.0±0.00µs 4.8±0.00µs 138.9 Ktx/sec 203.6 Ktx/sec
stdb_raw 🧠 u64 index 2048 256 4.7±0.00µs 4.9±0.00µs 208.5 Ktx/sec 201.3 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.01µs 3.3±0.01µs 26.8 Mtx/sec 29.2 Mtx/sec
u32_u64_str bflatn_to_bsatn_slow_path 100 3.1±0.04µs 3.4±0.00µs 30.3 Mtx/sec 28.2 Mtx/sec
u32_u64_str bsatn 100 2.3±0.05µs 2.3±0.02µs 41.8 Mtx/sec 41.3 Mtx/sec
u32_u64_str bsatn 100 39.8±0.15ns 40.5±0.04ns 2.3 Gtx/sec 2.3 Gtx/sec
u32_u64_str json 100 4.8±0.05µs 5.5±0.03µs 19.9 Mtx/sec 17.3 Mtx/sec
u32_u64_str json 100 8.1±0.01µs 7.7±0.01µs 11.8 Mtx/sec 12.4 Mtx/sec
u32_u64_str product_value 100 1019.3±3.21ns 1022.3±0.52ns 93.6 Mtx/sec 93.3 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_fast_path 100 951.2±2.00ns 1127.7±1.29ns 100.3 Mtx/sec 84.6 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_slow_path 100 2.4±0.01µs 2.8±0.01µs 39.8 Mtx/sec 34.2 Mtx/sec
u32_u64_u64 bsatn 100 1558.2±18.97ns 1597.5±24.73ns 61.2 Mtx/sec 59.7 Mtx/sec
u32_u64_u64 bsatn 100 28.8±0.15ns 39.4±0.04ns 3.2 Gtx/sec 2.4 Gtx/sec
u32_u64_u64 json 100 3.3±0.02µs 3.4±0.04µs 29.0 Mtx/sec 28.1 Mtx/sec
u32_u64_u64 json 100 4.8±0.06µs 4.9±0.02µs 20.0 Mtx/sec 19.4 Mtx/sec
u32_u64_u64 product_value 100 1012.9±1.47ns 1013.9±1.50ns 94.2 Mtx/sec 94.1 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_fast_path 100 714.1±0.70ns 926.0±10.57ns 133.5 Mtx/sec 103.0 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_slow_path 100 2.4±0.01µs 2.8±0.00µs 39.9 Mtx/sec 34.2 Mtx/sec
u64_u64_u32 bsatn 100 1078.2±0.55ns 942.8±1.38ns 88.4 Mtx/sec 101.2 Mtx/sec
u64_u64_u32 bsatn 100 1532.5±14.80ns 1588.9±41.22ns 62.2 Mtx/sec 60.0 Mtx/sec
u64_u64_u32 json 100 3.3±0.03µs 3.9±0.01µs 28.8 Mtx/sec 24.8 Mtx/sec
u64_u64_u32 json 100 5.0±0.04µs 4.8±0.04µs 19.0 Mtx/sec 19.7 Mtx/sec
u64_u64_u32 product_value 100 1015.5±4.23ns 1015.8±0.87ns 93.9 Mtx/sec 93.9 Mtx/sec

stdb_module_large_arguments

arg size new latency old latency new throughput old throughput
64KiB 111.6±14.20µs 108.3±7.83µs - -

stdb_module_print_bulk

line count new latency old latency new throughput old throughput
1 42.8±6.04µs 45.2±7.53µs - -
100 600.6±6.70µs 591.0±3.94µs - -
1000 3.9±0.87ms 3.7±0.57ms - -

remaining

name new latency old latency new throughput old throughput
special/db_game/circles/load=10 304.1±2.34µs 285.4±4.11µs - -
special/db_game/circles/load=100 302.0±3.21µs 288.0±2.17µs - -
special/db_game/ia_loop/load=10 0.0±0.00ns 0.0±0.00ns - -
special/db_game/ia_loop/load=100 0.0±0.00ns 0.0±0.00ns - -
sqlite/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 57.3±0.12µs 57.0±0.09µs 17.0 Ktx/sec 17.1 Ktx/sec
sqlite/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 47.7±0.32µs 47.5±0.26µs 20.5 Ktx/sec 20.6 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 41.2±0.30µs 41.7±0.23µs 23.7 Ktx/sec 23.4 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 35.3±0.32µs 36.8±0.16µs 27.6 Ktx/sec 26.5 Ktx/sec
stdb_module/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 1289.9±33.15µs 1305.0±14.94µs 775 tx/sec 766 tx/sec
stdb_module/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 1023.3±11.03µs 1017.6±13.12µs 977 tx/sec 982 tx/sec
stdb_raw/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 650.0±17.83µs 663.9±23.34µs 1538 tx/sec 1506 tx/sec
stdb_raw/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 496.1±8.15µs 495.3±6.78µs 2015 tx/sec 2019 tx/sec
stdb_raw/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 378.2±0.28µs 372.5±0.20µs 2.6 Ktx/sec 2.6 Ktx/sec
stdb_raw/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 339.3±0.59µs 342.3±0.15µs 2.9 Ktx/sec 2.9 Ktx/sec

@github-actions
Copy link

@github-actions github-actions bot commented on 637d6d7 Oct 17, 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 5393 5385 0.15% 5469 5463 0.11%
sqlite 5579 5575 0.07% 6009 6019 -0.17%

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 75556 75578 -0.03% 75962 76078 -0.15%
stdb_raw u32_u64_str no_index 64 128 2 string 118054 118075 -0.02% 118606 118633 -0.02%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 24080 24070 0.04% 24504 24572 -0.28%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 23048 23037 0.05% 23426 23457 -0.13%
sqlite u32_u64_str no_index 64 128 2 string 144695 144663 0.02% 146071 146263 -0.13%
sqlite u32_u64_str no_index 64 128 1 u64 124044 124013 0.02% 125222 125355 -0.11%
sqlite u32_u64_str btree_each_column 64 128 1 u64 131361 131340 0.02% 132787 132910 -0.09%
sqlite u32_u64_str btree_each_column 64 128 2 string 134494 134462 0.02% 136090 136182 -0.07%

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 900518 900431 0.01% 925766 926355 -0.06%
stdb_raw u32_u64_str btree_each_column 64 128 1052748 1048656 0.39% 1083814 1108490 -2.23%
sqlite u32_u64_str unique_0 64 128 398320 398284 0.01% 419592 420380 -0.19%
sqlite u32_u64_str btree_each_column 64 128 983637 983611 0.00% 1022371 1024907 -0.25%

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 152823 152705 0.08% 152973 152823 0.10%
stdb_raw u32_u64_str unique_0 64 15848 15730 0.75% 15994 15848 0.92%
sqlite u32_u64_str unique_0 1024 1067255 1067233 0.00% 1070611 1070553 0.01%
sqlite u32_u64_str unique_0 64 76201 76179 0.03% 77267 77209 0.08%

callgrind: serialize_product_value

count format total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
64 json 47528 47182 0.73% 50184 49834 0.70%
64 bsatn 25509 25716 -0.80% 27753 27858 -0.38%
16 bsatn 8200 8117 1.02% 9560 9375 1.97%
16 json 12188 12078 0.91% 14092 13982 0.79%

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 20517129 20658732 -0.69% 21277235 21390706 -0.53%
stdb_raw u32_u64_str unique_0 64 128 1308794 1307968 0.06% 1393308 1388952 0.31%
sqlite u32_u64_str unique_0 1024 1024 1802182 1802083 0.01% 1811664 1811233 0.02%
sqlite u32_u64_str unique_0 64 128 128528 128429 0.08% 131378 131249 0.10%
On-disk benchmarks

callgrind: empty transaction

db total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw 5398 5395 0.06% 5478 5469 0.16%
sqlite 5621 5617 0.07% 6107 6129 -0.36%

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 75561 75588 -0.04% 75967 76084 -0.15%
stdb_raw u32_u64_str no_index 64 128 2 string 118059 118085 -0.02% 118595 118663 -0.06%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 24086 24080 0.02% 24502 24574 -0.29%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 23053 23047 0.03% 23427 23459 -0.14%
sqlite u32_u64_str no_index 64 128 1 u64 125965 125934 0.02% 127495 127584 -0.07%
sqlite u32_u64_str no_index 64 128 2 string 146616 146584 0.02% 148316 148432 -0.08%
sqlite u32_u64_str btree_each_column 64 128 2 string 136616 136584 0.02% 138682 138714 -0.02%
sqlite u32_u64_str btree_each_column 64 128 1 u64 133457 133426 0.02% 135333 135354 -0.02%

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 851415 849350 0.24% 907415 905146 0.25%
stdb_raw u32_u64_str btree_each_column 64 128 999915 997901 0.20% 1060275 1057393 0.27%
sqlite u32_u64_str unique_0 64 128 415857 415821 0.01% 436387 437247 -0.20%
sqlite u32_u64_str btree_each_column 64 128 1021898 1021862 0.00% 1061168 1062378 -0.11%

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 152828 152715 0.07% 152970 152825 0.09%
stdb_raw u32_u64_str unique_0 64 15853 15740 0.72% 15995 15850 0.91%
sqlite u32_u64_str unique_0 1024 1070323 1070301 0.00% 1074157 1074035 0.01%
sqlite u32_u64_str unique_0 64 77973 77951 0.03% 79367 79201 0.21%

callgrind: serialize_product_value

count format total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
64 json 47528 47182 0.73% 50184 49834 0.70%
64 bsatn 25509 25716 -0.80% 27753 27858 -0.38%
16 bsatn 8200 8117 1.02% 9560 9375 1.97%
16 json 12188 12078 0.91% 14092 13982 0.79%

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 19443287 19401537 0.22% 20252453 20210525 0.21%
stdb_raw u32_u64_str unique_0 64 128 1261327 1258623 0.21% 1344625 1338217 0.48%
sqlite u32_u64_str unique_0 1024 1024 1809743 1809644 0.01% 1818265 1818258 0.00%
sqlite u32_u64_str unique_0 64 128 132654 132555 0.07% 135580 135447 0.10%

Please sign in to comment.