Skip to content

Commit bcbbb55

Browse files
committed
Make Table::clone_structure cheaper by:
- Arcing `TableSchema`, and this has benefits elsewhere too. - Arc<[_]>ing the visitor program instructions. The data behind the Arcs very rarely change, which is the perfect case for an Arc.
1 parent 4cd17d7 commit bcbbb55

File tree

20 files changed

+272
-249
lines changed

20 files changed

+272
-249
lines changed

crates/bench/benches/special.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ fn serialize_benchmarks<T: BenchTable + RandomTable>(c: &mut Criterion) {
8989
});
9090

9191
let mut table = spacetimedb_table::table::Table::new(
92-
TableDef::from_product(name, T::product_type().clone()).into_schema(TableId(0)),
92+
TableDef::from_product(name, T::product_type().clone())
93+
.into_schema(TableId(0))
94+
.into(),
9395
spacetimedb_table::indexes::SquashedOffset::COMMITTED_STATE,
9496
);
9597
let mut blob_store = spacetimedb_table::blob_store::HashMapBlobStore::default();

crates/core/src/db/datastore/locking_tx_datastore/committed_state.rs

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use spacetimedb_table::{
3838
indexes::{RowPointer, SquashedOffset},
3939
table::{IndexScanIter, InsertError, RowRef, Table},
4040
};
41+
use std::sync::Arc;
4142
use std::{
4243
collections::{BTreeMap, BTreeSet},
4344
ops::RangeBounds,
@@ -51,7 +52,7 @@ pub(crate) struct CommittedState {
5152
}
5253

5354
impl StateView for CommittedState {
54-
fn get_schema(&self, table_id: &TableId) -> Option<&TableSchema> {
55+
fn get_schema(&self, table_id: &TableId) -> Option<&Arc<TableSchema>> {
5556
self.tables.get(table_id).map(|table| table.get_schema())
5657
}
5758
fn iter<'a>(&'a self, ctx: &'a ExecutionContext, table_id: &TableId) -> Result<Iter<'a>> {
@@ -61,11 +62,7 @@ impl StateView for CommittedState {
6162
Err(TableError::IdNotFound(SystemTable::st_table, table_id.0).into())
6263
}
6364
fn table_exists(&self, table_id: &TableId) -> Option<&str> {
64-
if let Some(table) = self.tables.get(table_id) {
65-
Some(&table.schema.table_name)
66-
} else {
67-
None
68-
}
65+
self.tables.get(table_id).map(|t| &*t.schema.table_name)
6966
}
7067
/// Returns an iterator,
7168
/// yielding every row in the table identified by `table_id`,
@@ -117,7 +114,7 @@ impl CommittedState {
117114
};
118115

119116
// Insert the table row into st_tables, creating st_tables if it's missing
120-
let (st_tables, blob_store) = self.get_table_and_blob_store_or_create(ST_TABLES_ID, st_table_schema());
117+
let (st_tables, blob_store) = self.get_table_and_blob_store_or_create(ST_TABLES_ID, st_table_schema().into());
121118
// Insert the table row into `st_tables` for all system tables
122119
for schema in system_tables() {
123120
let table_id = schema.table_id;
@@ -139,7 +136,8 @@ impl CommittedState {
139136
}
140137

141138
// Insert the columns into `st_columns`
142-
let (st_columns, blob_store) = self.get_table_and_blob_store_or_create(ST_COLUMNS_ID, st_columns_schema());
139+
let (st_columns, blob_store) =
140+
self.get_table_and_blob_store_or_create(ST_COLUMNS_ID, st_columns_schema().into());
143141
for col in system_tables().into_iter().flat_map(|x| x.into_columns()) {
144142
let row = StColumnRow {
145143
table_id: col.table_id,
@@ -159,7 +157,7 @@ impl CommittedState {
159157

160158
// Insert constraints into `st_constraints`
161159
let (st_constraints, blob_store) =
162-
self.get_table_and_blob_store_or_create(ST_CONSTRAINTS_ID, st_constraints_schema());
160+
self.get_table_and_blob_store_or_create(ST_CONSTRAINTS_ID, st_constraints_schema().into());
163161
for (i, constraint) in system_tables()
164162
.into_iter()
165163
.flat_map(|x| x.constraints)
@@ -184,7 +182,8 @@ impl CommittedState {
184182
}
185183

186184
// Insert the indexes into `st_indexes`
187-
let (st_indexes, blob_store) = self.get_table_and_blob_store_or_create(ST_INDEXES_ID, st_indexes_schema());
185+
let (st_indexes, blob_store) =
186+
self.get_table_and_blob_store_or_create(ST_INDEXES_ID, st_indexes_schema().into());
188187
for (i, index) in system_tables()
189188
.into_iter()
190189
.flat_map(|x| x.indexes)
@@ -215,7 +214,7 @@ impl CommittedState {
215214

216215
// Insert the sequences into `st_sequences`
217216
let (st_sequences, blob_store) =
218-
self.get_table_and_blob_store_or_create(ST_SEQUENCES_ID, st_sequences_schema());
217+
self.get_table_and_blob_store_or_create(ST_SEQUENCES_ID, st_sequences_schema().into());
219218
// We create sequences last to get right the starting number
220219
// so, we don't sort here
221220
for (i, col) in system_tables().into_iter().flat_map(|x| x.sequences).enumerate() {
@@ -244,8 +243,8 @@ impl CommittedState {
244243
// Re-read the schema with the correct ids...
245244
let ctx = ExecutionContext::internal(database_address);
246245
for schema in system_tables() {
247-
*self.tables.get_mut(&schema.table_id).unwrap().schema =
248-
self.schema_for_table_raw(&ctx, schema.table_id)?;
246+
self.tables.get_mut(&schema.table_id).unwrap().schema =
247+
Arc::new(self.schema_for_table_raw(&ctx, schema.table_id)?);
249248
}
250249

251250
Ok(())
@@ -265,7 +264,7 @@ impl CommittedState {
265264
Ok(())
266265
}
267266

268-
pub fn replay_insert(&mut self, table_id: TableId, schema: &TableSchema, row: &ProductValue) -> Result<()> {
267+
pub fn replay_insert(&mut self, table_id: TableId, schema: &Arc<TableSchema>, row: &ProductValue) -> Result<()> {
269268
let (table, blob_store) = self.get_table_and_blob_store_or_create_ref_schema(table_id, schema);
270269
table.insert_internal(blob_store, row).map_err(TableError::Insert)?;
271270
Ok(())
@@ -331,9 +330,7 @@ impl CommittedState {
331330

332331
// Construct their schemas and insert tables for them.
333332
for table_id in table_ids {
334-
let schema = self
335-
.schema_for_table(&ExecutionContext::default(), table_id)?
336-
.into_owned();
333+
let schema = self.schema_for_table(&ExecutionContext::default(), table_id)?;
337334
self.tables
338335
.insert(table_id, Table::new(schema, SquashedOffset::COMMITTED_STATE));
339336
}
@@ -449,11 +446,8 @@ impl CommittedState {
449446
// and the fullness of the page.
450447

451448
for (table_id, mut tx_table) in insert_tables {
452-
let (commit_table, commit_blob_store) = self.get_table_and_blob_store_or_create(
453-
table_id,
454-
// TODO(perf): avoid cloning here.
455-
*tx_table.schema.clone(),
456-
);
449+
let (commit_table, commit_blob_store) =
450+
self.get_table_and_blob_store_or_create(table_id, tx_table.schema.clone());
457451

458452
// NOTE: if there is a schema change the table id will not change
459453
// and that is what is important here so it doesn't matter if we
@@ -517,13 +511,13 @@ impl CommittedState {
517511

518512
fn create_table(&mut self, table_id: TableId, schema: TableSchema) {
519513
self.tables
520-
.insert(table_id, Table::new(schema, SquashedOffset::COMMITTED_STATE));
514+
.insert(table_id, Table::new(Arc::new(schema), SquashedOffset::COMMITTED_STATE));
521515
}
522516

523517
pub fn get_table_and_blob_store_or_create_ref_schema<'this>(
524518
&'this mut self,
525519
table_id: TableId,
526-
schema: &'_ TableSchema,
520+
schema: &Arc<TableSchema>,
527521
) -> (&'this mut Table, &'this mut dyn BlobStore) {
528522
let table = self
529523
.tables
@@ -536,7 +530,7 @@ impl CommittedState {
536530
pub fn get_table_and_blob_store_or_create(
537531
&mut self,
538532
table_id: TableId,
539-
schema: TableSchema,
533+
schema: Arc<TableSchema>,
540534
) -> (&mut Table, &mut dyn BlobStore) {
541535
let table = self
542536
.tables

crates/core/src/db/datastore/locking_tx_datastore/datastore.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use crate::{
1414
Epoch, StModuleFields, StModuleRow, StTableFields, ST_MODULE_ID, ST_TABLES_ID, WASM_MODULE,
1515
},
1616
traits::{
17-
DataRow, IsolationLevel, MutProgrammable, MutTx, MutTxDatastore, Programmable, Tx, TxData, TxDatastore,
17+
DataRow, IsolationLevel, MutProgrammable, MutTx, MutTxDatastore, Programmable, RowTypeForTable, Tx,
18+
TxData, TxDatastore,
1819
},
1920
},
2021
db_metrics::{DB_METRICS, MAX_TX_CPU_TIME},
@@ -26,7 +27,7 @@ use anyhow::{anyhow, Context};
2627
use parking_lot::{Mutex, RwLock};
2728
use spacetimedb_primitives::{ColList, ConstraintId, IndexId, SequenceId, TableId};
2829
use spacetimedb_sats::db::def::{IndexDef, SequenceDef, TableDef, TableSchema};
29-
use spacetimedb_sats::{bsatn, buffer::BufReader, hash::Hash, AlgebraicValue, ProductType, ProductValue};
30+
use spacetimedb_sats::{bsatn, buffer::BufReader, hash::Hash, AlgebraicValue, ProductValue};
3031
use spacetimedb_table::{indexes::RowPointer, table::RowRef};
3132
use std::ops::RangeBounds;
3233
use std::sync::Arc;
@@ -225,11 +226,11 @@ impl TxDatastore for Locking {
225226
Ok(tx.table_exists(&table_id).map(Cow::Borrowed))
226227
}
227228

228-
fn schema_for_table_tx<'tx>(&self, tx: &'tx Self::Tx, table_id: TableId) -> Result<Cow<'tx, TableSchema>> {
229+
fn schema_for_table_tx(&self, tx: &Self::Tx, table_id: TableId) -> Result<Arc<TableSchema>> {
229230
tx.schema_for_table(&ExecutionContext::internal(self.database_address), table_id)
230231
}
231232

232-
fn get_all_tables_tx<'tx>(&self, ctx: &ExecutionContext, tx: &'tx Self::Tx) -> Result<Vec<Cow<'tx, TableSchema>>> {
233+
fn get_all_tables_tx(&self, ctx: &ExecutionContext, tx: &Self::Tx) -> Result<Vec<Arc<TableSchema>>> {
233234
self.iter_tx(ctx, tx, ST_TABLES_ID)?
234235
.map(|row_ref| {
235236
let table_id = row_ref.read_col(StTableFields::TableId)?;
@@ -257,15 +258,15 @@ impl MutTxDatastore for Locking {
257258
/// reflect the schema of the table.q
258259
///
259260
/// This function is known to be called quite frequently.
260-
fn row_type_for_table_mut_tx<'tx>(&self, tx: &'tx Self::MutTx, table_id: TableId) -> Result<Cow<'tx, ProductType>> {
261+
fn row_type_for_table_mut_tx<'tx>(&self, tx: &'tx Self::MutTx, table_id: TableId) -> Result<RowTypeForTable<'tx>> {
261262
tx.row_type_for_table(table_id, self.database_address)
262263
}
263264

264265
/// IMPORTANT! This function is relatively expensive, and much more
265266
/// expensive than `row_type_for_table_mut_tx`. Prefer
266267
/// `row_type_for_table_mut_tx` if you only need to access the `ProductType`
267268
/// of the table.
268-
fn schema_for_table_mut_tx<'tx>(&self, tx: &'tx Self::MutTx, table_id: TableId) -> Result<Cow<'tx, TableSchema>> {
269+
fn schema_for_table_mut_tx(&self, tx: &Self::MutTx, table_id: TableId) -> Result<Arc<TableSchema>> {
269270
tx.schema_for_table(&ExecutionContext::internal(self.database_address), table_id)
270271
}
271272

@@ -621,8 +622,7 @@ impl spacetimedb_commitlog::payload::txdata::Visitor for ReplayVisitor<'_> {
621622
) -> std::result::Result<Self::Row, Self::Error> {
622623
let schema = self
623624
.committed_state
624-
.schema_for_table(&ExecutionContext::default(), table_id)?
625-
.into_owned();
625+
.schema_for_table(&ExecutionContext::default(), table_id)?;
626626
let row = ProductValue::decode(schema.get_row_type(), reader)?;
627627

628628
self.committed_state
@@ -1276,7 +1276,7 @@ mod tests {
12761276
datastore.commit_mut_tx_for_test(tx)?;
12771277

12781278
let mut tx = datastore.begin_mut_tx(IsolationLevel::Serializable);
1279-
let schema = datastore.schema_for_table_mut_tx(&tx, table_id)?.into_owned();
1279+
let schema = datastore.schema_for_table_mut_tx(&tx, table_id)?;
12801280

12811281
for index in &*schema.indexes {
12821282
datastore.drop_index_mut_tx(&mut tx, index.index_id)?;

0 commit comments

Comments
 (0)