Skip to content

Commit

Permalink
Adding the system table for row level security (#1746)
Browse files Browse the repository at this point in the history
  • Loading branch information
mamcx authored Oct 11, 2024
1 parent 66ea22f commit b1b58ac
Show file tree
Hide file tree
Showing 16 changed files with 409 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.

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

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

Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ use crate::{
system_tables, StColumnRow, StConstraintData, StConstraintRow, StIndexAlgorithm, StIndexRow,
StSequenceRow, StTableFields, StTableRow, SystemTable, ST_CLIENT_ID, ST_CLIENT_IDX, ST_COLUMN_ID,
ST_COLUMN_IDX, ST_COLUMN_NAME, ST_CONSTRAINT_ID, ST_CONSTRAINT_IDX, ST_CONSTRAINT_NAME, ST_INDEX_ID,
ST_INDEX_IDX, ST_INDEX_NAME, ST_MODULE_ID, ST_MODULE_IDX, ST_RESERVED_SEQUENCE_RANGE, ST_SCHEDULED_ID,
ST_SCHEDULED_IDX, ST_SEQUENCE_ID, ST_SEQUENCE_IDX, ST_SEQUENCE_NAME, ST_TABLE_ID, ST_TABLE_IDX,
ST_VAR_ID, ST_VAR_IDX,
ST_INDEX_IDX, ST_INDEX_NAME, ST_MODULE_ID, ST_MODULE_IDX, ST_RESERVED_SEQUENCE_RANGE,
ST_ROW_LEVEL_SECURITY_ID, ST_ROW_LEVEL_SECURITY_IDX, ST_SCHEDULED_ID, ST_SCHEDULED_IDX, ST_SEQUENCE_ID,
ST_SEQUENCE_IDX, ST_SEQUENCE_NAME, ST_TABLE_ID, ST_TABLE_IDX, ST_VAR_ID, ST_VAR_IDX,
},
traits::TxData,
},
Expand Down Expand Up @@ -225,6 +225,10 @@ impl CommittedState {

self.create_table(ST_SCHEDULED_ID, schemas[ST_SCHEDULED_IDX].clone());

self.create_table(ST_ROW_LEVEL_SECURITY_ID, schemas[ST_ROW_LEVEL_SECURITY_IDX].clone());

// IMPORTANT: It is crucial that the `st_sequences` table is created last

// Insert the sequences into `st_sequences`
let (st_sequences, blob_store) =
self.get_table_and_blob_store_or_create(ST_SEQUENCE_ID, &schemas[ST_SEQUENCE_IDX]);
Expand Down
60 changes: 51 additions & 9 deletions crates/core/src/db/datastore/locking_tx_datastore/datastore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub struct Locking {
/// The state of sequence generation in this database.
sequence_state: Arc<Mutex<SequencesState>>,
/// The address of this database.
database_address: Address,
pub(crate) database_address: Address,
}

impl Locking {
Expand Down Expand Up @@ -379,6 +379,10 @@ impl MutTxDatastore for Locking {
tx.table_id_from_name(table_name, self.database_address)
}

fn table_id_exists_mut_tx(&self, tx: &Self::MutTx, table_id: &TableId) -> bool {
tx.table_name(*table_id).is_some()
}

fn table_name_from_id_mut_tx<'a>(
&'a self,
ctx: &'a ExecutionContext,
Expand Down Expand Up @@ -511,10 +515,6 @@ impl MutTxDatastore for Locking {
Ok((gens, row_ref.collapse()))
}

fn table_id_exists_mut_tx(&self, tx: &Self::MutTx, table_id: &TableId) -> bool {
tx.table_name(*table_id).is_some()
}

fn metadata_mut_tx(&self, tx: &Self::MutTx) -> Result<Option<Metadata>> {
let ctx = ExecutionContext::internal(self.database_address);
tx.iter(&ctx, ST_MODULE_ID)?.next().map(metadata_from_row).transpose()
Expand Down Expand Up @@ -927,9 +927,10 @@ mod tests {
use super::*;
use crate::db::datastore::system_tables::{
system_tables, StColumnRow, StConstraintData, StConstraintFields, StConstraintRow, StIndexAlgorithm,
StIndexFields, StIndexRow, StScheduledFields, StSequenceFields, StSequenceRow, StTableRow, StVarFields,
StVarValue, ST_CLIENT_NAME, ST_COLUMN_ID, ST_COLUMN_NAME, ST_CONSTRAINT_ID, ST_CONSTRAINT_NAME, ST_INDEX_ID,
ST_INDEX_NAME, ST_MODULE_NAME, ST_RESERVED_SEQUENCE_RANGE, ST_SCHEDULED_ID, ST_SCHEDULED_NAME, ST_SEQUENCE_ID,
StIndexFields, StIndexRow, StRowLevelSecurityFields, StScheduledFields, StSequenceFields, StSequenceRow,
StTableRow, StVarFields, StVarValue, ST_CLIENT_NAME, ST_COLUMN_ID, ST_COLUMN_NAME, ST_CONSTRAINT_ID,
ST_CONSTRAINT_NAME, ST_INDEX_ID, ST_INDEX_NAME, ST_MODULE_NAME, ST_RESERVED_SEQUENCE_RANGE,
ST_ROW_LEVEL_SECURITY_ID, ST_ROW_LEVEL_SECURITY_NAME, ST_SCHEDULED_ID, ST_SCHEDULED_NAME, ST_SEQUENCE_ID,
ST_SEQUENCE_NAME, ST_TABLE_NAME, ST_VAR_ID, ST_VAR_NAME,
};
use crate::db::datastore::traits::{IsolationLevel, MutTx};
Expand All @@ -944,7 +945,9 @@ mod tests {
use spacetimedb_primitives::{col_list, ColId, ScheduleId};
use spacetimedb_sats::{product, AlgebraicType, GroundSpacetimeType};
use spacetimedb_schema::def::{BTreeAlgorithm, ConstraintData, IndexAlgorithm, UniqueConstraintData};
use spacetimedb_schema::schema::{ColumnSchema, ConstraintSchema, IndexSchema, SequenceSchema};
use spacetimedb_schema::schema::{
ColumnSchema, ConstraintSchema, IndexSchema, RowLevelSecuritySchema, SequenceSchema,
};
use spacetimedb_table::table::UniqueConstraintViolation;

/// For the first user-created table, sequences in the system tables start
Expand Down Expand Up @@ -1320,6 +1323,7 @@ mod tests {
TableRow { id: ST_CLIENT_ID.into(), name: ST_CLIENT_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: None },
TableRow { id: ST_VAR_ID.into(), name: ST_VAR_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: Some(StVarFields::Name.into()) },
TableRow { id: ST_SCHEDULED_ID.into(), name: ST_SCHEDULED_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: Some(StScheduledFields::ScheduleId.into()) },
TableRow { id: ST_ROW_LEVEL_SECURITY_ID.into(), name: ST_ROW_LEVEL_SECURITY_NAME, ty: StTableType::System, access: StAccess::Public, primary_key: Some(StRowLevelSecurityFields::Sql.into()) },
]));
#[rustfmt::skip]
assert_eq!(query.scan_st_columns()?, map_array([
Expand Down Expand Up @@ -1371,6 +1375,9 @@ mod tests {
ColRow { table: ST_SCHEDULED_ID.into(), pos: 1, name: "table_id", ty: TableId::get_type() },
ColRow { table: ST_SCHEDULED_ID.into(), pos: 2, name: "reducer_name", ty: AlgebraicType::String },
ColRow { table: ST_SCHEDULED_ID.into(), pos: 3, name: "schedule_name", ty: AlgebraicType::String },

ColRow { table: ST_ROW_LEVEL_SECURITY_ID.into(), pos: 0, name: "table_id", ty: TableId::get_type() },
ColRow { table: ST_ROW_LEVEL_SECURITY_ID.into(), pos: 1, name: "sql", ty: AlgebraicType::String },
]));
#[rustfmt::skip]
assert_eq!(query.scan_st_indexes()?, map_array([
Expand All @@ -1384,6 +1391,8 @@ mod tests {
IndexRow { id: 8, table: ST_VAR_ID.into(), col: col(0), name: "idx_st_var_name_unique", },
IndexRow { id: 9, table: ST_SCHEDULED_ID.into(), col: col(0), name: "idx_st_scheduled_schedule_id_unique", },
IndexRow { id: 10, table: ST_SCHEDULED_ID.into(), col: col(1), name: "idx_st_scheduled_table_id_unique", },
IndexRow { id: 11, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(0), name: "idx_st_row_level_security_btree_table_id"},
IndexRow { id: 12, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(1), name: "idx_st_row_level_security_sql_unique"},
]));
let start = FIRST_NON_SYSTEM_ID as i128;
#[rustfmt::skip]
Expand Down Expand Up @@ -1412,6 +1421,7 @@ mod tests {
ConstraintRow { constraint_id: 8, table_id: ST_VAR_ID.into(), unique_columns: col(0), constraint_name: "ct_st_var_name_unique" },
ConstraintRow { constraint_id: 9, table_id: ST_SCHEDULED_ID.into(), unique_columns: col(0), constraint_name: "ct_st_scheduled_schedule_id_unique" },
ConstraintRow { constraint_id: 10, table_id: ST_SCHEDULED_ID.into(), unique_columns: col(1), constraint_name: "ct_st_scheduled_table_id_unique" },
ConstraintRow { constraint_id: 11, table_id: ST_ROW_LEVEL_SECURITY_ID.into(), unique_columns: col(1), constraint_name: "ct_st_row_level_security_sql_unique" },
]));

// Verify we get back the tables correctly with the proper ids...
Expand Down Expand Up @@ -1823,6 +1833,8 @@ mod tests {
IndexRow { id: 8, table: ST_VAR_ID.into(), col: col(0), name: "idx_st_var_name_unique", },
IndexRow { id: 9, table: ST_SCHEDULED_ID.into(), col: col(0), name: "idx_st_scheduled_schedule_id_unique", },
IndexRow { id: 10, table: ST_SCHEDULED_ID.into(), col: col(1), name: "idx_st_scheduled_table_id_unique", },
IndexRow { id: 11, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(0), name: "idx_st_row_level_security_btree_table_id"},
IndexRow { id: 12, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(1), name: "idx_st_row_level_security_sql_unique"},
IndexRow { id: seq_start, table: FIRST_NON_SYSTEM_ID, col: col(0), name: "id_idx", },
IndexRow { id: seq_start + 1, table: FIRST_NON_SYSTEM_ID, col: col(1), name: "name_idx", },
IndexRow { id: seq_start + 2, table: FIRST_NON_SYSTEM_ID, col: col(2), name: "age_idx", },
Expand Down Expand Up @@ -1876,6 +1888,8 @@ mod tests {
IndexRow { id: 8, table: ST_VAR_ID.into(), col: col(0), name: "idx_st_var_name_unique", },
IndexRow { id: 9, table: ST_SCHEDULED_ID.into(), col: col(0), name: "idx_st_scheduled_schedule_id_unique", },
IndexRow { id: 10, table: ST_SCHEDULED_ID.into(), col: col(1), name: "idx_st_scheduled_table_id_unique", },
IndexRow { id: 11, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(0), name: "idx_st_row_level_security_btree_table_id"},
IndexRow { id: 12, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(1), name: "idx_st_row_level_security_sql_unique"},
IndexRow { id: seq_start , table: FIRST_NON_SYSTEM_ID, col: col(0), name: "id_idx" },
IndexRow { id: seq_start + 1, table: FIRST_NON_SYSTEM_ID, col: col(1), name: "name_idx" },
IndexRow { id: seq_start + 2, table: FIRST_NON_SYSTEM_ID, col: col(2), name: "age_idx" },
Expand Down Expand Up @@ -1930,6 +1944,8 @@ mod tests {
IndexRow { id: 8, table: ST_VAR_ID.into(), col: col(0), name: "idx_st_var_name_unique", },
IndexRow { id: 9, table: ST_SCHEDULED_ID.into(), col: col(0), name: "idx_st_scheduled_schedule_id_unique", },
IndexRow { id: 10, table: ST_SCHEDULED_ID.into(), col: col(1), name: "idx_st_scheduled_table_id_unique", },
IndexRow { id: 11, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(0), name: "idx_st_row_level_security_btree_table_id"},
IndexRow { id: 12, table: ST_ROW_LEVEL_SECURITY_ID.into(), col: col(1), name: "idx_st_row_level_security_sql_unique"},
IndexRow { id: seq_start, table: FIRST_NON_SYSTEM_ID, col: col(0), name: "id_idx" },
IndexRow { id: seq_start + 1, table: FIRST_NON_SYSTEM_ID, col: col(1), name: "name_idx" },
].map(Into::into));
Expand Down Expand Up @@ -2019,6 +2035,32 @@ mod tests {
Ok(())
}

#[test]
fn test_row_level_security() -> ResultTest<()> {
let (_, mut tx, table_id) = setup_table()?;

let rls = RowLevelSecuritySchema {
sql: "SELECT * FROM bar".into(),
table_id,
};
let ctx = ExecutionContext::default();
tx.create_row_level_security(&ctx, rls.clone())?;

let result = tx.row_level_security_for_table_id(&ctx, table_id)?;
assert_eq!(
result,
vec![RowLevelSecuritySchema {
sql: "SELECT * FROM bar".into(),
table_id,
}]
);

tx.drop_row_level_security(&ctx, rls.sql)?;
assert_eq!(tx.row_level_security_for_table_id(&ctx, table_id)?, []);

Ok(())
}

// TODO: Add the following tests
// - Create index with unique constraint and immediately insert a row that violates the constraint before committing.
// - Create a tx that inserts 2000 rows with an auto_inc column
Expand Down
90 changes: 89 additions & 1 deletion crates/core/src/db/datastore/locking_tx_datastore/mut_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use super::{
tx_state::{DeleteTable, IndexIdMap, TxState},
SharedMutexGuard, SharedWriteGuard,
};
use crate::db::datastore::system_tables::{StRowLevelSecurityFields, StRowLevelSecurityRow, ST_ROW_LEVEL_SECURITY_ID};
use crate::db::datastore::{
system_tables::{
StColumnFields, StColumnRow, StConstraintFields, StConstraintRow, StFields as _, StIndexFields, StIndexRow,
Expand All @@ -22,6 +23,7 @@ use crate::{
use core::ops::RangeBounds;
use core::{iter, ops::Bound};
use smallvec::SmallVec;
use spacetimedb_lib::db::raw_def::v9::RawSql;
use spacetimedb_lib::{
address::Address,
bsatn::Deserializer,
Expand All @@ -36,7 +38,7 @@ use spacetimedb_sats::{
};
use spacetimedb_schema::{
def::{BTreeAlgorithm, IndexAlgorithm},
schema::{ConstraintSchema, IndexSchema, SequenceSchema, TableSchema},
schema::{ConstraintSchema, IndexSchema, RowLevelSecuritySchema, SequenceSchema, TableSchema},
};
use spacetimedb_table::{
blob_store::{BlobStore, HashMapBlobStore},
Expand Down Expand Up @@ -946,6 +948,92 @@ impl MutTxId {
})
}

/// Create a row level security policy.
///
/// Requires:
/// - `row_level_security_schema.table_id != TableId::SENTINEL`
/// - `row_level_security_schema.sql` must be unique.
///
/// Ensures:
///
/// - The row level security policy metadata is inserted into the system tables (and other data structures reflecting them).
/// - The returned `sql` is unique.
pub fn create_row_level_security(
&mut self,
ctx: &ExecutionContext,
row_level_security_schema: RowLevelSecuritySchema,
) -> Result<RawSql> {
if row_level_security_schema.table_id == TableId::SENTINEL {
return Err(anyhow::anyhow!(
"`table_id` must not be `TableId::SENTINEL` in `{:#?}`",
row_level_security_schema
)
.into());
}

log::trace!(
"ROW LEVEL SECURITY CREATING for table: {}",
row_level_security_schema.table_id
);

// Insert the row into st_row_level_security
// NOTE: Because st_row_level_security has a unique index on sql, this will
// fail if already exists.
let row = StRowLevelSecurityRow {
table_id: row_level_security_schema.table_id,
sql: row_level_security_schema.sql,
};

let row = self.insert(ST_ROW_LEVEL_SECURITY_ID, &mut ProductValue::from(row), ctx.database())?;
let row_level_security_sql = row.1.collapse().read_col(StRowLevelSecurityFields::Sql)?;
let existed = matches!(row.1, RowRefInsertion::Existed(_));

// Add the row level security to the transaction's insert table.
self.get_or_create_insert_table_mut(row_level_security_schema.table_id)?;

if existed {
log::trace!("ROW LEVEL SECURITY ALREADY EXISTS: {row_level_security_sql}");
} else {
log::trace!("ROW LEVEL SECURITY CREATED: {row_level_security_sql}");
}

Ok(row_level_security_sql)
}

pub fn row_level_security_for_table_id(
&self,
ctx: &ExecutionContext,
table_id: TableId,
) -> Result<Vec<RowLevelSecuritySchema>> {
Ok(self
.iter_by_col_eq(
ctx,
ST_ROW_LEVEL_SECURITY_ID,
StRowLevelSecurityFields::TableId,
&table_id.into(),
)?
.map(|row| {
let row = StRowLevelSecurityRow::try_from(row).unwrap();
row.into()
})
.collect())
}

pub fn drop_row_level_security(&mut self, ctx: &ExecutionContext, sql: RawSql) -> Result<()> {
let st_rls_ref = self
.iter_by_col_eq(
ctx,
ST_ROW_LEVEL_SECURITY_ID,
StRowLevelSecurityFields::Sql,
&sql.clone().into(),
)?
.next()
.ok_or_else(|| TableError::RawSqlNotFound(SystemTable::st_row_level_security, sql))?;
self.delete(ST_ROW_LEVEL_SECURITY_ID, st_rls_ref.pointer())?;

Ok(())
}

// TODO(perf, deep-integration):
// When all of [`Table::read_row`], [`RowRef::new`], [`CommittedState::get`]
// and [`TxState::get`] become unsafe,
Expand Down
Loading

2 comments on commit b1b58ac

@github-actions
Copy link

@github-actions github-actions bot commented on b1b58ac Oct 11, 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 💿 412.6±1.96ns 414.9±2.48ns - -
sqlite 🧠 404.1±2.46ns 410.3±2.45ns - -
stdb_raw 💿 622.8±2.95ns 625.5±0.83ns - -
stdb_raw 🧠 623.2±1.10ns 622.8±0.50ns - -

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 585.0±0.72µs 589.3±0.53µs 1709 tx/sec 1696 tx/sec
sqlite 💿 u32_u64_str unique_0 2048 256 152.2±7.86µs 152.4±0.36µs 6.4 Ktx/sec 6.4 Ktx/sec
sqlite 💿 u32_u64_u64 btree_each_column 2048 256 464.1±0.47µs 471.4±1.56µs 2.1 Ktx/sec 2.1 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 2048 256 134.1±0.61µs 134.8±0.99µs 7.3 Ktx/sec 7.2 Ktx/sec
sqlite 🧠 u32_u64_str btree_each_column 2048 256 450.1±0.74µs 453.4±0.62µs 2.2 Ktx/sec 2.2 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 2048 256 120.1±0.35µs 123.5±0.48µs 8.1 Ktx/sec 7.9 Ktx/sec
sqlite 🧠 u32_u64_u64 btree_each_column 2048 256 364.2±0.26µs 369.9±0.44µs 2.7 Ktx/sec 2.6 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 2048 256 101.1±0.25µs 104.3±0.33µs 9.7 Ktx/sec 9.4 Ktx/sec
stdb_raw 💿 u32_u64_str btree_each_column 2048 256 512.1±17.19µs 583.0±17.31µs 1952 tx/sec 1715 tx/sec
stdb_raw 💿 u32_u64_str unique_0 2048 256 493.1±25.97µs 506.9±18.18µs 2027 tx/sec 1972 tx/sec
stdb_raw 💿 u32_u64_u64 btree_each_column 2048 256 338.5±4.61µs 371.7±11.90µs 2.9 Ktx/sec 2.6 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 2048 256 315.6±4.78µs 352.3±5.47µs 3.1 Ktx/sec 2.8 Ktx/sec
stdb_raw 🧠 u32_u64_str btree_each_column 2048 256 308.4±0.27µs 305.3±0.21µs 3.2 Ktx/sec 3.2 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 2048 256 240.4±0.35µs 240.1±0.15µs 4.1 Ktx/sec 4.1 Ktx/sec
stdb_raw 🧠 u32_u64_u64 btree_each_column 2048 256 247.7±0.17µs 241.5±0.24µs 3.9 Ktx/sec 4.0 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 2048 256 222.2±0.11µs 218.5±0.25µs 4.4 Ktx/sec 4.5 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.15µs 23.5±0.17µs 41.5 Ktx/sec 41.6 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 21.9±0.04µs 21.7±0.12µs 44.5 Ktx/sec 45.1 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 20.5±0.18µs 20.8±0.08µs 47.6 Ktx/sec 47.0 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 19.2±0.05µs 19.1±0.13µs 50.8 Ktx/sec 51.2 Ktx/sec
stdb_raw 💿 u32_u64_str unique_0 4.8±0.00µs 4.7±0.00µs 205.2 Ktx/sec 206.0 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 4.6±0.00µs 4.6±0.00µs 210.3 Ktx/sec 210.6 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 4.8±0.00µs 4.7±0.00µs 205.6 Ktx/sec 205.6 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 4.6±0.00µs 4.6±0.00µs 210.4 Ktx/sec 210.6 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 68.5±0.23µs 68.2±0.41µs 14.2 Ktx/sec 14.3 Ktx/sec
sqlite 💿 u64 index 2048 256 66.3±0.17µs 64.3±0.12µs 14.7 Ktx/sec 15.2 Ktx/sec
sqlite 🧠 string index 2048 256 65.4±0.22µs 65.4±0.15µs 14.9 Ktx/sec 14.9 Ktx/sec
sqlite 🧠 u64 index 2048 256 60.0±0.17µs 59.5±0.42µs 16.3 Ktx/sec 16.4 Ktx/sec
stdb_raw 💿 string index 2048 256 4.9±0.00µs 4.9±0.00µs 198.8 Ktx/sec 198.9 Ktx/sec
stdb_raw 💿 u64 index 2048 256 4.8±0.00µs 4.8±0.00µs 204.9 Ktx/sec 204.0 Ktx/sec
stdb_raw 🧠 string index 2048 256 4.9±0.00µs 4.9±0.00µs 198.8 Ktx/sec 199.0 Ktx/sec
stdb_raw 🧠 u64 index 2048 256 4.8±0.00µs 4.8±0.00µs 204.8 Ktx/sec 203.8 Ktx/sec

serialize

schema format count new latency old latency new throughput old throughput
u32_u64_str bflatn_to_bsatn_fast_path 100 3.3±0.01µs 3.3±0.01µs 29.2 Mtx/sec 29.3 Mtx/sec
u32_u64_str bflatn_to_bsatn_slow_path 100 3.4±0.00µs 3.0±0.01µs 27.8 Mtx/sec 31.7 Mtx/sec
u32_u64_str bsatn 100 2.3±0.02µs 2.4±0.02µs 40.9 Mtx/sec 39.6 Mtx/sec
u32_u64_str bsatn 100 40.5±0.12ns 40.4±0.11ns 2.3 Gtx/sec 2.3 Gtx/sec
u32_u64_str json 100 4.9±0.02µs 4.8±0.03µs 19.6 Mtx/sec 19.8 Mtx/sec
u32_u64_str json 100 8.1±0.04µs 7.1±0.06µs 11.8 Mtx/sec 13.4 Mtx/sec
u32_u64_str product_value 100 1017.6±0.63ns 1016.4±7.74ns 93.7 Mtx/sec 93.8 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_fast_path 100 1113.9±10.69ns 1191.3±3.71ns 85.6 Mtx/sec 80.1 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_slow_path 100 2.8±0.00µs 2.4±0.00µs 34.3 Mtx/sec 39.2 Mtx/sec
u32_u64_u64 bsatn 100 1671.3±21.52ns 1779.1±53.76ns 57.1 Mtx/sec 53.6 Mtx/sec
u32_u64_u64 bsatn 100 39.3±0.08ns 39.4±0.11ns 2.4 Gtx/sec 2.4 Gtx/sec
u32_u64_u64 json 100 3.0±0.01µs 3.1±0.02µs 31.5 Mtx/sec 30.3 Mtx/sec
u32_u64_u64 json 100 5.1±0.09µs 4.9±0.06µs 18.8 Mtx/sec 19.3 Mtx/sec
u32_u64_u64 product_value 100 1013.4±1.07ns 1013.8±1.42ns 94.1 Mtx/sec 94.1 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_fast_path 100 887.8±3.66ns 926.3±10.93ns 107.4 Mtx/sec 103.0 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_slow_path 100 2.8±0.00µs 2.5±0.01µs 34.1 Mtx/sec 38.9 Mtx/sec
u64_u64_u32 bsatn 100 1092.7±8.37ns 723.8±0.70ns 87.3 Mtx/sec 131.8 Mtx/sec
u64_u64_u32 bsatn 100 1711.1±23.62ns 1690.0±31.99ns 55.7 Mtx/sec 56.4 Mtx/sec
u64_u64_u32 json 100 3.1±0.01µs 3.3±0.03µs 31.0 Mtx/sec 29.3 Mtx/sec
u64_u64_u32 json 100 5.1±0.49µs 4.9±0.01µs 18.6 Mtx/sec 19.6 Mtx/sec
u64_u64_u32 product_value 100 1015.7±0.55ns 1015.9±0.95ns 93.9 Mtx/sec 93.9 Mtx/sec

stdb_module_large_arguments

arg size new latency old latency new throughput old throughput
64KiB 108.9±7.36µs 115.5±6.34µs - -

stdb_module_print_bulk

line count new latency old latency new throughput old throughput
1 52.9±4.34µs 56.4±5.46µs - -
100 568.4±3.98µs 597.6±19.52µs - -
1000 5.1±0.12ms 4.5±0.96ms - -

remaining

name new latency old latency new throughput old throughput
special/db_game/circles/load=10 300.9±1.75µs 290.8±3.19µs - -
special/db_game/circles/load=100 297.6±2.95µs 291.7±3.50µ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 55.9±0.46µs 55.4±0.32µs 17.5 Ktx/sec 17.6 Ktx/sec
sqlite/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 46.5±0.24µs 47.8±0.10µs 21.0 Ktx/sec 20.4 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 40.2±0.35µs 41.7±0.25µs 24.3 Ktx/sec 23.4 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 36.0±0.11µs 36.4±0.23µs 27.1 Ktx/sec 26.8 Ktx/sec
stdb_module/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 1260.3±11.34µs 1279.3±9.18µs 793 tx/sec 781 tx/sec
stdb_module/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 972.5±65.67µs 1032.0±17.36µs 1028 tx/sec 968 tx/sec
stdb_raw/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 642.8±25.10µs 647.6±18.24µs 1555 tx/sec 1544 tx/sec
stdb_raw/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 468.5±25.48µs 497.1±8.13µs 2.1 Ktx/sec 2011 tx/sec
stdb_raw/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 380.8±0.41µs 380.0±0.40µs 2.6 Ktx/sec 2.6 Ktx/sec
stdb_raw/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 339.0±0.58µs 342.1±1.06µs 2.9 Ktx/sec 2.9 Ktx/sec

@github-actions
Copy link

@github-actions github-actions bot commented on b1b58ac Oct 11, 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 5385 5395 -0.19% 5423 5441 -0.33%
sqlite 5519 5509 0.18% 5995 5871 2.11%

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 75451 75388 0.08% 76099 75686 0.55%
stdb_raw u32_u64_str no_index 64 128 2 string 118951 117799 0.98% 119811 118253 1.32%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 24068 24061 0.03% 24752 24423 1.35%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 23036 23029 0.03% 23500 23323 0.76%
sqlite u32_u64_str no_index 64 128 2 string 144677 144677 0.00% 146253 146163 0.06%
sqlite u32_u64_str no_index 64 128 1 u64 124027 124027 0.00% 125375 125345 0.02%
sqlite u32_u64_str btree_each_column 64 128 1 u64 131344 131344 0.00% 132808 132730 0.06%
sqlite u32_u64_str btree_each_column 64 128 2 string 134476 134482 -0.00% 136156 136148 0.01%

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 902800 895990 0.76% 950984 954384 -0.36%
stdb_raw u32_u64_str btree_each_column 64 128 1052756 1051061 0.16% 1125742 1125773 -0.00%
sqlite u32_u64_str unique_0 64 128 398158 398158 0.00% 412960 416100 -0.75%
sqlite u32_u64_str btree_each_column 64 128 983475 983481 -0.00% 1018215 1022611 -0.43%

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 152705 152681 0.02% 152811 152731 0.05%
stdb_raw u32_u64_str unique_0 64 15730 15706 0.15% 15804 15772 0.20%
sqlite u32_u64_str unique_0 1024 1046663 1046653 0.00% 1050119 1050109 0.00%
sqlite u32_u64_str unique_0 64 74809 74799 0.01% 75987 75885 0.13%

callgrind: serialize_product_value

count format total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
64 json 47374 47374 0.00% 49996 49894 0.20%
64 bsatn 25716 25716 0.00% 27994 27960 0.12%
16 bsatn 8117 8117 0.00% 9477 9443 0.36%
16 json 12126 12126 0.00% 13996 13894 0.73%

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 20712769 20627797 0.41% 21402149 21475209 -0.34%
stdb_raw u32_u64_str unique_0 64 128 1307894 1300359 0.58% 1387462 1388851 -0.10%
sqlite u32_u64_str unique_0 1024 1024 1802091 1802091 0.00% 1811095 1811641 -0.03%
sqlite u32_u64_str unique_0 64 128 128437 128437 0.00% 131237 131281 -0.03%
On-disk benchmarks

callgrind: empty transaction

db total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw 5395 5405 -0.19% 5437 5451 -0.26%
sqlite 5557 5551 0.11% 6199 5977 3.71%

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 75461 75398 0.08% 76069 75696 0.49%
stdb_raw u32_u64_str no_index 64 128 2 string 117872 117809 0.05% 118652 118239 0.35%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 24094 24087 0.03% 24766 24453 1.28%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 23046 23039 0.03% 23506 23297 0.90%
sqlite u32_u64_str no_index 64 128 1 u64 125948 125948 0.00% 127516 127546 -0.02%
sqlite u32_u64_str no_index 64 128 2 string 146598 146598 0.00% 148426 148400 0.02%
sqlite u32_u64_str btree_each_column 64 128 2 string 136598 136598 0.00% 138736 138642 0.07%
sqlite u32_u64_str btree_each_column 64 128 1 u64 133440 133440 0.00% 135310 135272 0.03%

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 851115 845738 0.64% 898181 903546 -0.59%
stdb_raw u32_u64_str btree_each_column 64 128 1001836 997997 0.38% 1074054 1069307 0.44%
sqlite u32_u64_str unique_0 64 128 415695 415695 0.00% 429859 433003 -0.73%
sqlite u32_u64_str btree_each_column 64 128 1021736 1021736 0.00% 1056868 1059550 -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 152715 152691 0.02% 152785 152729 0.04%
stdb_raw u32_u64_str unique_0 64 15740 15716 0.15% 15810 15762 0.30%
sqlite u32_u64_str unique_0 1024 1049727 1049721 0.00% 1053525 1053443 0.01%
sqlite u32_u64_str unique_0 64 76571 76571 0.00% 77835 77881 -0.06%

callgrind: serialize_product_value

count format total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
64 json 47374 47374 0.00% 49996 49894 0.20%
64 bsatn 25716 25716 0.00% 27994 27960 0.12%
16 bsatn 8117 8117 0.00% 9477 9443 0.36%
16 json 12126 12126 0.00% 13996 13894 0.73%

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 19415679 19331872 0.43% 20230603 20247656 -0.08%
stdb_raw u32_u64_str unique_0 64 128 1260469 1254488 0.48% 1341805 1342118 -0.02%
sqlite u32_u64_str unique_0 1024 1024 1809652 1809652 0.00% 1818268 1818326 -0.00%
sqlite u32_u64_str unique_0 64 128 132563 132581 -0.01% 135499 135625 -0.09%

Please sign in to comment.