Skip to content

Commit

Permalink
Add gzip + none compression algos and let SDK pick compression (#1802)
Browse files Browse the repository at this point in the history
  • Loading branch information
Centril authored Oct 9, 2024
1 parent 4db4c9a commit 1e35fe6
Show file tree
Hide file tree
Showing 16 changed files with 173 additions and 63 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.

12 changes: 10 additions & 2 deletions crates/bench/benches/subscription.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use spacetimedb::db::relational_db::RelationalDB;
use spacetimedb::error::DBError;
use spacetimedb::execution_context::ExecutionContext;
use spacetimedb::host::module_host::DatabaseTableUpdate;
use spacetimedb::identity::AuthCtx;
use spacetimedb::messages::websocket::BsatnFormat;
use spacetimedb::subscription::query::compile_read_only_query;
use spacetimedb::subscription::subscription::ExecutionSet;
use spacetimedb::{db::relational_db::RelationalDB, messages::websocket::Compression};
use spacetimedb_bench::database::BenchDatabase as _;
use spacetimedb_bench::spacetime_raw::SpacetimeRaw;
use spacetimedb_primitives::{col_list, TableId};
Expand Down Expand Up @@ -104,7 +104,15 @@ fn eval(c: &mut Criterion) {
let query = compile_read_only_query(&raw.db, &AuthCtx::for_testing(), &tx, sql).unwrap();
let query: ExecutionSet = query.into();
let ctx = &ExecutionContext::subscribe(raw.db.address());
b.iter(|| drop(black_box(query.eval::<BsatnFormat>(ctx, &raw.db, &tx, None))))
b.iter(|| {
drop(black_box(query.eval::<BsatnFormat>(
ctx,
&raw.db,
&tx,
None,
Compression::Brotli,
)))
})
});
};

Expand Down
1 change: 1 addition & 0 deletions crates/client-api-messages/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ bytestring.workspace = true
brotli.workspace = true
chrono = { workspace = true, features = ["serde"] }
enum-as-inner.workspace = true
flate2.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
smallvec.workspace = true
Expand Down
79 changes: 60 additions & 19 deletions crates/client-api-messages/src/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use spacetimedb_sats::{
SpacetimeType,
};
use std::{
io::{self, Read as _},
io::{self, Read as _, Write as _},
sync::Arc,
};

Expand Down Expand Up @@ -74,7 +74,7 @@ pub trait WebsocketFormat: Sized {

/// Convert a `QueryUpdate` into `Self::QueryUpdate`.
/// This allows some formats to e.g., compress the update.
fn into_query_update(qu: QueryUpdate<Self>) -> Self::QueryUpdate;
fn into_query_update(qu: QueryUpdate<Self>, compression: Compression) -> Self::QueryUpdate;
}

/// Messages sent from the client to the server.
Expand Down Expand Up @@ -165,12 +165,15 @@ pub struct OneOffQuery {
pub query_string: Box<str>,
}

/// The tag recognized by ghe host and SDKs to mean no compression of a [`ServerMessage`].
/// The tag recognized by the host and SDKs to mean no compression of a [`ServerMessage`].
pub const SERVER_MSG_COMPRESSION_TAG_NONE: u8 = 0;

/// The tag recognized by the host and SDKs to mean brotli compression of a [`ServerMessage`].
pub const SERVER_MSG_COMPRESSION_TAG_BROTLI: u8 = 1;

/// The tag recognized by the host and SDKs to mean brotli compression of a [`ServerMessage`].
pub const SERVER_MSG_COMPRESSION_TAG_GZIP: u8 = 2;

/// Messages sent from the server to the client.
#[derive(SpacetimeType, derive_more::From)]
#[sats(crate = spacetimedb_lib)]
Expand Down Expand Up @@ -357,13 +360,21 @@ impl<F: WebsocketFormat> TableUpdate<F> {
pub enum CompressableQueryUpdate<F: WebsocketFormat> {
Uncompressed(QueryUpdate<F>),
Brotli(Bytes),
Gzip(Bytes),
}

impl CompressableQueryUpdate<BsatnFormat> {
pub fn maybe_decompress(self) -> QueryUpdate<BsatnFormat> {
match self {
Self::Uncompressed(qu) => qu,
Self::Brotli(bytes) => brotli_decompress_qu(&bytes),
Self::Brotli(bytes) => {
let bytes = brotli_decompress(&bytes).unwrap();
bsatn::from_slice(&bytes).unwrap()
}
Self::Gzip(bytes) => {
let bytes = gzip_decompress(&bytes).unwrap();
bsatn::from_slice(&bytes).unwrap()
}
}
}
}
Expand Down Expand Up @@ -456,7 +467,7 @@ impl WebsocketFormat for JsonFormat {

type QueryUpdate = QueryUpdate<Self>;

fn into_query_update(qu: QueryUpdate<Self>) -> Self::QueryUpdate {
fn into_query_update(qu: QueryUpdate<Self>, _: Compression) -> Self::QueryUpdate {
qu
}
}
Expand Down Expand Up @@ -499,27 +510,50 @@ impl WebsocketFormat for BsatnFormat {

type QueryUpdate = CompressableQueryUpdate<Self>;

fn into_query_update(qu: QueryUpdate<Self>) -> Self::QueryUpdate {
fn into_query_update(qu: QueryUpdate<Self>, compression: Compression) -> Self::QueryUpdate {
let qu_len_would_have_been = bsatn::to_len(&qu).unwrap();

if should_compress(qu_len_would_have_been) {
let bytes = bsatn::to_vec(&qu).unwrap();
let mut out = Vec::new();
brotli_compress(&bytes, &mut out);
CompressableQueryUpdate::Brotli(out.into())
} else {
CompressableQueryUpdate::Uncompressed(qu)
match decide_compression(qu_len_would_have_been, compression) {
Compression::None => CompressableQueryUpdate::Uncompressed(qu),
Compression::Brotli => {
let bytes = bsatn::to_vec(&qu).unwrap();
let mut out = Vec::new();
brotli_compress(&bytes, &mut out);
CompressableQueryUpdate::Brotli(out.into())
}
Compression::Gzip => {
let bytes = bsatn::to_vec(&qu).unwrap();
let mut out = Vec::new();
gzip_compress(&bytes, &mut out);
CompressableQueryUpdate::Gzip(out.into())
}
}
}
}

pub fn should_compress(len: usize) -> bool {
/// The threshold at which we start to compress messages.
/// A specification of either a desired or decided compression algorithm.
#[derive(serde::Deserialize, Default, PartialEq, Eq, Clone, Copy, Hash, Debug)]
pub enum Compression {
/// No compression ever.
None,
/// Compress using brotli if a certain size threshold was met.
#[default]
Brotli,
/// Compress using gzip if a certain size threshold was met.
Gzip,
}

pub fn decide_compression(len: usize, compression: Compression) -> Compression {
/// The threshold beyond which we start to compress messages.
/// 1KiB was chosen without measurement.
/// TODO(perf): measure!
const COMPRESS_THRESHOLD: usize = 1024;

len <= COMPRESS_THRESHOLD
if len > COMPRESS_THRESHOLD {
compression
} else {
Compression::None
}
}

pub fn brotli_compress(bytes: &[u8], out: &mut Vec<u8>) {
Expand Down Expand Up @@ -560,9 +594,16 @@ pub fn brotli_decompress(bytes: &[u8]) -> Result<Vec<u8>, io::Error> {
Ok(decompressed)
}

pub fn brotli_decompress_qu(bytes: &[u8]) -> QueryUpdate<BsatnFormat> {
let bytes = brotli_decompress(bytes).unwrap();
bsatn::from_slice(&bytes).unwrap()
pub fn gzip_compress(bytes: &[u8], out: &mut Vec<u8>) {
let mut encoder = flate2::write::GzEncoder::new(out, flate2::Compression::fast());
encoder.write_all(bytes).unwrap();
encoder.finish().expect("Failed to gzip compress `bytes`");
}

pub fn gzip_decompress(bytes: &[u8]) -> Result<Vec<u8>, io::Error> {
let mut decompressed = Vec::new();
let _ = flate2::read::GzDecoder::new(bytes).read(&mut decompressed)?;
Ok(decompressed)
}

type RowSize = u16;
Expand Down
15 changes: 11 additions & 4 deletions crates/client-api/src/routes/subscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use spacetimedb::client::{ClientActorId, ClientConnection, DataMessage, MessageH
use spacetimedb::host::NoSuchModule;
use spacetimedb::util::also_poll;
use spacetimedb::worker_metrics::WORKER_METRICS;
use spacetimedb_client_api_messages::websocket::Compression;
use spacetimedb_lib::address::AddressForUrl;
use spacetimedb_lib::Address;
use std::time::Instant;
Expand All @@ -42,6 +43,7 @@ pub struct SubscribeParams {
#[derive(Deserialize)]
pub struct SubscribeQueryParams {
pub client_address: Option<AddressForUrl>,
pub compression: Option<Compression>,
}

// TODO: is this a reasonable way to generate client addresses?
Expand All @@ -55,7 +57,10 @@ pub fn generate_random_address() -> Address {
pub async fn handle_websocket<S>(
State(ctx): State<S>,
Path(SubscribeParams { name_or_address }): Path<SubscribeParams>,
Query(SubscribeQueryParams { client_address }): Query<SubscribeQueryParams>,
Query(SubscribeQueryParams {
client_address,
compression,
}): Query<SubscribeQueryParams>,
forwarded_for: Option<TypedHeader<XForwardedFor>>,
Extension(auth): Extension<SpacetimeAuth>,
ws: WebSocketUpgrade,
Expand All @@ -80,6 +85,7 @@ where
ws.select_protocol([(BIN_PROTOCOL, Protocol::Binary), (TEXT_PROTOCOL, Protocol::Text)]);

let protocol = protocol.ok_or((StatusCode::BAD_REQUEST, "no valid protocol selected"))?;
let compression = compression.unwrap_or_default();

// TODO: Should also maybe refactor the code and the protocol to allow a single websocket
// to connect to multiple modules
Expand Down Expand Up @@ -131,7 +137,8 @@ where
}

let actor = |client, sendrx| ws_client_actor(client, ws, sendrx);
let client = match ClientConnection::spawn(client_id, protocol, replica_id, module_rx, actor).await {
let client = match ClientConnection::spawn(client_id, protocol, compression, replica_id, module_rx, actor).await
{
Ok(s) => s,
Err(e) => {
log::warn!("ModuleHost died while we were connecting: {e:#}");
Expand Down Expand Up @@ -259,7 +266,7 @@ async fn ws_client_actor_inner(
let workload = msg.workload();
let num_rows = msg.num_rows();

let msg = datamsg_to_wsmsg(serialize(msg, client.protocol));
let msg = datamsg_to_wsmsg(serialize(msg, client.protocol, client.compression));

// These metrics should be updated together,
// or not at all.
Expand Down Expand Up @@ -347,7 +354,7 @@ async fn ws_client_actor_inner(
if let Err(e) = res {
if let MessageHandleError::Execution(err) = e {
log::error!("{err:#}");
let msg = serialize(err, client.protocol);
let msg = serialize(err, client.protocol, client.compression);
if let Err(error) = ws.send(datamsg_to_wsmsg(msg)).await {
log::warn!("Websocket send error: {error}")
}
Expand Down
6 changes: 5 additions & 1 deletion crates/core/src/client/client_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::util::prometheus_handle::IntGaugeExt;
use crate::worker_metrics::WORKER_METRICS;
use derive_more::From;
use futures::prelude::*;
use spacetimedb_client_api_messages::websocket::FormatSwitch;
use spacetimedb_client_api_messages::websocket::{Compression, FormatSwitch};
use spacetimedb_lib::identity::RequestId;
use tokio::sync::{mpsc, oneshot, watch};
use tokio::task::AbortHandle;
Expand All @@ -36,6 +36,7 @@ impl Protocol {
pub struct ClientConnectionSender {
pub id: ClientActorId,
pub protocol: Protocol,
pub compression: Compression,
sendtx: mpsc::Sender<SerializableMessage>,
abort_handle: AbortHandle,
cancelled: AtomicBool,
Expand All @@ -61,6 +62,7 @@ impl ClientConnectionSender {
Self {
id,
protocol,
compression: Compression::Brotli,
sendtx,
abort_handle,
cancelled: AtomicBool::new(false),
Expand Down Expand Up @@ -143,6 +145,7 @@ impl ClientConnection {
pub async fn spawn<F, Fut>(
id: ClientActorId,
protocol: Protocol,
compression: Compression,
replica_id: u64,
mut module_rx: watch::Receiver<ModuleHost>,
actor: F,
Expand Down Expand Up @@ -178,6 +181,7 @@ impl ClientConnection {
let sender = Arc::new(ClientConnectionSender {
id,
protocol,
compression,
sendtx,
abort_handle,
cancelled: AtomicBool::new(false),
Expand Down
32 changes: 22 additions & 10 deletions crates/core/src/client/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::host::ArgsTuple;
use crate::messages::websocket as ws;
use derive_more::From;
use spacetimedb_client_api_messages::websocket::{
BsatnFormat, FormatSwitch, JsonFormat, WebsocketFormat, SERVER_MSG_COMPRESSION_TAG_BROTLI,
SERVER_MSG_COMPRESSION_TAG_NONE,
BsatnFormat, Compression, FormatSwitch, JsonFormat, WebsocketFormat, SERVER_MSG_COMPRESSION_TAG_BROTLI,
SERVER_MSG_COMPRESSION_TAG_GZIP, SERVER_MSG_COMPRESSION_TAG_NONE,
};
use spacetimedb_lib::identity::RequestId;
use spacetimedb_lib::ser::serde::SerializeWrapper;
Expand All @@ -28,8 +28,13 @@ pub(super) type SwitchedServerMessage = FormatSwitch<ws::ServerMessage<BsatnForm

/// Serialize `msg` into a [`DataMessage`] containing a [`ws::ServerMessage`].
///
/// If `protocol` is [`Protocol::Binary`], the message will be compressed by this method.
pub fn serialize(msg: impl ToProtocol<Encoded = SwitchedServerMessage>, protocol: Protocol) -> DataMessage {
/// If `protocol` is [`Protocol::Binary`],
/// the message will be conditionally compressed by this method according to `compression`.
pub fn serialize(
msg: impl ToProtocol<Encoded = SwitchedServerMessage>,
protocol: Protocol,
compression: Compression,
) -> DataMessage {
// TODO(centril, perf): here we are allocating buffers only to throw them away eventually.
// Consider pooling these allocations so that we reuse them.
match msg.to_protocol(protocol) {
Expand All @@ -40,12 +45,19 @@ pub fn serialize(msg: impl ToProtocol<Encoded = SwitchedServerMessage>, protocol
bsatn::to_writer(&mut msg_bytes, &msg).unwrap();

// Conditionally compress the message.
let msg_bytes = if ws::should_compress(msg_bytes[1..].len()) {
let mut out = vec![SERVER_MSG_COMPRESSION_TAG_BROTLI];
ws::brotli_compress(&msg_bytes[1..], &mut out);
out
} else {
msg_bytes
let srv_msg = &msg_bytes[1..];
let msg_bytes = match ws::decide_compression(srv_msg.len(), compression) {
Compression::None => msg_bytes,
Compression::Brotli => {
let mut out = vec![SERVER_MSG_COMPRESSION_TAG_BROTLI];
ws::brotli_compress(srv_msg, &mut out);
out
}
Compression::Gzip => {
let mut out = vec![SERVER_MSG_COMPRESSION_TAG_GZIP];
ws::gzip_compress(srv_msg, &mut out);
out
}
};
msg_bytes.into()
}
Expand Down
6 changes: 3 additions & 3 deletions crates/core/src/host/module_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use indexmap::IndexSet;
use itertools::Itertools;
use smallvec::SmallVec;
use spacetimedb_client_api_messages::timestamp::Timestamp;
use spacetimedb_client_api_messages::websocket::{QueryUpdate, WebsocketFormat};
use spacetimedb_client_api_messages::websocket::{Compression, QueryUpdate, WebsocketFormat};
use spacetimedb_data_structures::error_stream::ErrorStream;
use spacetimedb_data_structures::map::{HashCollectionExt as _, IntMap};
use spacetimedb_lib::identity::{AuthCtx, RequestId};
Expand Down Expand Up @@ -124,12 +124,12 @@ impl UpdatesRelValue<'_> {
!(self.deletes.is_empty() && self.inserts.is_empty())
}

pub fn encode<F: WebsocketFormat>(&self) -> (F::QueryUpdate, u64) {
pub fn encode<F: WebsocketFormat>(&self, compression: Compression) -> (F::QueryUpdate, u64) {
let (deletes, nr_del) = F::encode_list(self.deletes.iter());
let (inserts, nr_ins) = F::encode_list(self.inserts.iter());
let num_rows = nr_del + nr_ins;
let qu = QueryUpdate { deletes, inserts };
let cqu = F::into_query_update(qu);
let cqu = F::into_query_update(qu, compression);
(cqu, num_rows)
}
}
Expand Down
Loading

2 comments on commit 1e35fe6

@github-actions
Copy link

@github-actions github-actions bot commented on 1e35fe6 Oct 9, 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 💿 404.8±0.96ns 426.5±3.01ns - -
sqlite 🧠 400.9±2.04ns 416.7±3.82ns - -
stdb_raw 💿 625.6±3.10ns 631.2±0.90ns - -
stdb_raw 🧠 625.5±1.07ns 631.8±1.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 589.2±0.81µs 588.6±0.53µs 1697 tx/sec 1698 tx/sec
sqlite 💿 u32_u64_str unique_0 2048 256 151.3±0.89µs 153.0±0.94µs 6.5 Ktx/sec 6.4 Ktx/sec
sqlite 💿 u32_u64_u64 btree_each_column 2048 256 469.3±0.94µs 471.3±0.45µs 2.1 Ktx/sec 2.1 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 2048 256 137.7±0.48µs 138.4±0.63µs 7.1 Ktx/sec 7.1 Ktx/sec
sqlite 🧠 u32_u64_str btree_each_column 2048 256 452.9±1.24µs 451.3±0.82µs 2.2 Ktx/sec 2.2 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 2048 256 122.1±0.41µs 123.7±0.69µs 8.0 Ktx/sec 7.9 Ktx/sec
sqlite 🧠 u32_u64_u64 btree_each_column 2048 256 367.4±0.59µs 369.8±0.53µs 2.7 Ktx/sec 2.6 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 2048 256 104.3±0.43µs 108.2±1.46µs 9.4 Ktx/sec 9.0 Ktx/sec
stdb_raw 💿 u32_u64_str btree_each_column 2048 256 575.2±40.61µs 591.0±19.65µs 1738 tx/sec 1692 tx/sec
stdb_raw 💿 u32_u64_str unique_0 2048 256 491.4±20.72µs 454.3±41.76µs 2035 tx/sec 2.1 Ktx/sec
stdb_raw 💿 u32_u64_u64 btree_each_column 2048 256 389.0±7.36µs 378.0±10.52µs 2.5 Ktx/sec 2.6 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 2048 256 350.7±9.43µs 349.2±16.46µs 2.8 Ktx/sec 2.8 Ktx/sec
stdb_raw 🧠 u32_u64_str btree_each_column 2048 256 306.9±0.24µs 306.1±0.21µs 3.2 Ktx/sec 3.2 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 2048 256 236.1±0.30µs 239.9±0.19µs 4.1 Ktx/sec 4.1 Ktx/sec
stdb_raw 🧠 u32_u64_u64 btree_each_column 2048 256 245.7±0.19µs 241.8±0.14µs 4.0 Ktx/sec 4.0 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 2048 256 217.9±0.11µs 221.1±0.18µs 4.5 Ktx/sec 4.4 Ktx/sec

iterate

db on disk schema indices new latency old latency new throughput old throughput
sqlite 💿 u32_u64_str unique_0 21.8±0.08µs 23.6±0.16µs 44.7 Ktx/sec 41.3 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 20.7±0.34µs 21.2±0.06µs 47.1 Ktx/sec 46.1 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 19.3±0.21µs 21.2±0.09µs 50.7 Ktx/sec 46.2 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 18.0±0.23µs 18.6±0.07µs 54.3 Ktx/sec 52.5 Ktx/sec
stdb_raw 💿 u32_u64_str unique_0 4.8±0.00µs 4.7±0.00µs 204.8 Ktx/sec 206.3 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 4.6±0.00µs 4.6±0.00µs 210.8 Ktx/sec 211.3 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 4.8±0.00µs 4.7±0.00µs 204.7 Ktx/sec 206.4 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 4.6±0.00µs 4.6±0.00µs 210.6 Ktx/sec 211.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 69.2±0.35µs 68.8±0.21µs 14.1 Ktx/sec 14.2 Ktx/sec
sqlite 💿 u64 index 2048 256 65.6±0.11µs 65.7±0.24µs 14.9 Ktx/sec 14.9 Ktx/sec
sqlite 🧠 string index 2048 256 65.6±0.26µs 66.4±0.20µs 14.9 Ktx/sec 14.7 Ktx/sec
sqlite 🧠 u64 index 2048 256 60.1±0.26µs 59.7±0.14µs 16.2 Ktx/sec 16.4 Ktx/sec
stdb_raw 💿 string index 2048 256 4.9±0.00µs 4.8±0.00µs 199.0 Ktx/sec 202.0 Ktx/sec
stdb_raw 💿 u64 index 2048 256 4.7±0.00µs 4.8±0.00µs 208.3 Ktx/sec 201.8 Ktx/sec
stdb_raw 🧠 string index 2048 256 4.9±0.00µs 4.8±0.00µs 198.8 Ktx/sec 201.9 Ktx/sec
stdb_raw 🧠 u64 index 2048 256 4.7±0.00µs 4.8±0.00µs 208.2 Ktx/sec 202.1 Ktx/sec

serialize

schema format count new latency old latency new throughput old throughput
u32_u64_str bflatn_to_bsatn_fast_path 100 3.2±0.00µs 3.3±0.09µs 29.4 Mtx/sec 28.6 Mtx/sec
u32_u64_str bflatn_to_bsatn_slow_path 100 3.1±0.01µs 3.1±0.00µs 31.0 Mtx/sec 31.2 Mtx/sec
u32_u64_str bsatn 100 2.4±0.02µs 2.4±0.01µs 39.8 Mtx/sec 40.4 Mtx/sec
u32_u64_str bsatn 100 40.5±0.11ns 40.5±0.12ns 2.3 Gtx/sec 2.3 Gtx/sec
u32_u64_str json 100 5.5±0.06µs 5.0±0.13µs 17.4 Mtx/sec 19.1 Mtx/sec
u32_u64_str json 100 7.1±0.05µs 7.9±0.03µs 13.4 Mtx/sec 12.1 Mtx/sec
u32_u64_str product_value 100 1014.9±1.17ns 1016.8±0.42ns 94.0 Mtx/sec 93.8 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_fast_path 100 1112.9±7.61ns 1131.1±37.14ns 85.7 Mtx/sec 84.3 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_slow_path 100 2.4±0.00µs 2.4±0.00µs 39.2 Mtx/sec 38.9 Mtx/sec
u32_u64_u64 bsatn 100 1699.7±37.89ns 1706.5±24.56ns 56.1 Mtx/sec 55.9 Mtx/sec
u32_u64_u64 bsatn 100 39.4±0.09ns 28.8±0.11ns 2.4 Gtx/sec 3.2 Gtx/sec
u32_u64_u64 json 100 3.1±0.01µs 3.3±0.04µs 31.0 Mtx/sec 29.0 Mtx/sec
u32_u64_u64 json 100 5.1±0.11µs 6.0±0.02µs 18.7 Mtx/sec 15.8 Mtx/sec
u32_u64_u64 product_value 100 1015.3±1.61ns 1013.4±1.10ns 93.9 Mtx/sec 94.1 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_fast_path 100 897.5±2.97ns 903.7±2.99ns 106.3 Mtx/sec 105.5 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_slow_path 100 2.4±0.00µs 2.4±0.00µs 39.1 Mtx/sec 39.0 Mtx/sec
u64_u64_u32 bsatn 100 1714.8±33.64ns 1711.7±32.42ns 55.6 Mtx/sec 55.7 Mtx/sec
u64_u64_u32 bsatn 100 750.9±3.75ns 708.4±0.43ns 127.0 Mtx/sec 134.6 Mtx/sec
u64_u64_u32 json 100 3.3±0.23µs 3.3±0.06µs 28.7 Mtx/sec 28.6 Mtx/sec
u64_u64_u32 json 100 5.0±0.04µs 5.1±0.00µs 18.9 Mtx/sec 18.7 Mtx/sec
u64_u64_u32 product_value 100 1015.5±0.65ns 1014.5±0.48ns 93.9 Mtx/sec 94.0 Mtx/sec

stdb_module_large_arguments

arg size new latency old latency new throughput old throughput
64KiB 117.8±10.67µs 110.2±6.31µs - -

stdb_module_print_bulk

line count new latency old latency new throughput old throughput
1 51.0±4.85µs 58.7±5.20µs - -
100 594.9±5.30µs 605.0±4.21µs - -
1000 3.3±0.10ms 5.1±0.63ms - -

remaining

name new latency old latency new throughput old throughput
special/db_game/circles/load=10 307.1±2.64µs 291.6±2.58µs - -
special/db_game/circles/load=100 306.9±2.42µs 291.5±3.04µ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 53.9±0.22µs 57.3±0.08µs 18.1 Ktx/sec 17.0 Ktx/sec
sqlite/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 46.3±0.17µs 49.2±0.18µs 21.1 Ktx/sec 19.9 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 39.8±0.13µs 42.1±0.14µs 24.6 Ktx/sec 23.2 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 35.4±0.12µs 37.8±0.29µs 27.6 Ktx/sec 25.8 Ktx/sec
stdb_module/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 1269.4±16.63µs 1287.7±14.62µs 787 tx/sec 776 tx/sec
stdb_module/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 1035.3±4.27µs 1031.3±5.87µs 965 tx/sec 969 tx/sec
stdb_raw/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 530.4±18.23µs 544.2±15.21µs 1885 tx/sec 1837 tx/sec
stdb_raw/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 451.3±11.91µs 489.8±9.41µs 2.2 Ktx/sec 2041 tx/sec
stdb_raw/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 369.8±0.84µs 376.5±0.41µs 2.6 Ktx/sec 2.6 Ktx/sec
stdb_raw/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 335.3±0.33µs 341.2±0.38µs 2.9 Ktx/sec 2.9 Ktx/sec

@github-actions
Copy link

@github-actions github-actions bot commented on 1e35fe6 Oct 9, 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 5395 5395 0.00% 5441 5445 -0.07%
sqlite 5509 5509 0.00% 5863 5975 -1.87%

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 75388 75388 0.00% 75714 75680 0.04%
stdb_raw u32_u64_str no_index 64 128 2 string 117799 117820 -0.02% 118293 118282 0.01%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 24061 24061 0.00% 24423 24499 -0.31%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 23029 23029 0.00% 23323 23373 -0.21%
sqlite u32_u64_str no_index 64 128 2 string 144677 144677 0.00% 146163 146097 0.05%
sqlite u32_u64_str no_index 64 128 1 u64 124027 124027 0.00% 125353 125153 0.16%
sqlite u32_u64_str btree_each_column 64 128 1 u64 131344 131344 0.00% 132886 132606 0.21%
sqlite u32_u64_str btree_each_column 64 128 2 string 134476 134494 -0.01% 136190 136094 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 897762 897311 0.05% 951922 947225 0.50%
stdb_raw u32_u64_str btree_each_column 64 128 1046621 1049649 -0.29% 1110905 1120653 -0.87%
sqlite u32_u64_str unique_0 64 128 398158 398158 0.00% 414884 418028 -0.75%
sqlite u32_u64_str btree_each_column 64 128 983475 983475 0.00% 1026985 1023279 0.36%

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 152681 152681 0.00% 152731 152763 -0.02%
stdb_raw u32_u64_str unique_0 64 15706 15706 0.00% 15752 15780 -0.18%
sqlite u32_u64_str unique_0 1024 1046653 1046653 0.00% 1049949 1050081 -0.01%
sqlite u32_u64_str unique_0 64 74799 74817 -0.02% 75797 75915 -0.16%

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% 49898 49928 -0.06%
64 bsatn 25716 25716 0.00% 27960 27960 0.00%
16 bsatn 8117 8117 0.00% 9443 9477 -0.36%
16 json 12126 12126 0.00% 13894 13928 -0.24%

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 20624457 20626033 -0.01% 21377041 21249111 0.60%
stdb_raw u32_u64_str unique_0 64 128 1301685 1300174 0.12% 1353877 1375564 -1.58%
sqlite u32_u64_str unique_0 1024 1024 1802109 1802091 0.00% 1811167 1811505 -0.02%
sqlite u32_u64_str unique_0 64 128 128437 128437 0.00% 131277 131321 -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 5405 5405 0.00% 5451 5455 -0.07%
sqlite 5551 5551 0.00% 5973 6089 -1.91%

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 75398 75398 0.00% 75684 75622 0.08%
stdb_raw u32_u64_str no_index 64 128 2 string 117809 117809 0.00% 118315 118315 0.00%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 24071 24072 -0.00% 24413 24526 -0.46%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 23039 23039 0.00% 23317 23331 -0.06%
sqlite u32_u64_str no_index 64 128 1 u64 125948 125948 0.00% 127578 127422 0.12%
sqlite u32_u64_str no_index 64 128 2 string 146598 146598 0.00% 148400 148314 0.06%
sqlite u32_u64_str btree_each_column 64 128 2 string 136598 136598 0.00% 138710 138652 0.04%
sqlite u32_u64_str btree_each_column 64 128 1 u64 133440 133440 0.00% 135340 135192 0.11%

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 845162 845698 -0.06% 900152 894650 0.61%
stdb_raw u32_u64_str btree_each_column 64 128 998513 995878 0.26% 1062357 1066706 -0.41%
sqlite u32_u64_str unique_0 64 128 415695 415695 0.00% 431963 435019 -0.70%
sqlite u32_u64_str btree_each_column 64 128 1021736 1021736 0.00% 1064070 1060710 0.32%

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 152691 152691 0.00% 152733 152761 -0.02%
stdb_raw u32_u64_str unique_0 64 15716 15716 0.00% 15754 15786 -0.20%
sqlite u32_u64_str unique_0 1024 1049721 1049721 0.00% 1053395 1053575 -0.02%
sqlite u32_u64_str unique_0 64 76577 76571 0.01% 77831 77865 -0.04%

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% 49898 49928 -0.06%
64 bsatn 25716 25716 0.00% 27960 27960 0.00%
16 bsatn 8117 8117 0.00% 9443 9477 -0.36%
16 json 12126 12126 0.00% 13894 13928 -0.24%

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 19328837 19335605 -0.04% 20174509 20066883 0.54%
stdb_raw u32_u64_str unique_0 64 128 1254173 1255078 -0.07% 1338183 1329600 0.65%
sqlite u32_u64_str unique_0 1024 1024 1809652 1809652 0.00% 1818306 1818266 0.00%
sqlite u32_u64_str unique_0 64 128 132581 132563 0.01% 135505 135575 -0.05%

Please sign in to comment.