Skip to content

Commit

Permalink
Clean up auth related code with traits, and reuse existing key genera…
Browse files Browse the repository at this point in the history
…tion code. (#1988)
  • Loading branch information
jsdt authored Nov 12, 2024
1 parent 2976593 commit cccadd1
Show file tree
Hide file tree
Showing 11 changed files with 824 additions and 884 deletions.
1,086 changes: 512 additions & 574 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions crates/cli/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use base64::{
};
use reqwest::RequestBuilder;
use serde::Deserialize;
use spacetimedb::auth::identity::{IncomingClaims, SpacetimeIdentityClaims2};
use spacetimedb::auth::identity::{IncomingClaims, SpacetimeIdentityClaims};
use spacetimedb_client_api_messages::name::{DnsLookupResponse, RegisterTldResult, ReverseDNSResponse};
use spacetimedb_data_structures::map::HashMap;
use spacetimedb_lib::{AlgebraicType, Identity};
Expand Down Expand Up @@ -277,7 +277,7 @@ pub fn decode_identity(config: &Config) -> anyhow::Result<String> {
let decoded_string = String::from_utf8(decoded_bytes)?;

let claims_data: IncomingClaims = serde_json::from_str(decoded_string.as_str())?;
let claims_data: SpacetimeIdentityClaims2 = claims_data.try_into()?;
let claims_data: SpacetimeIdentityClaims = claims_data.try_into()?;

Ok(claims_data.identity.to_string())
}
2 changes: 0 additions & 2 deletions crates/client-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,8 @@ tokio-tungstenite.workspace = true
itoa.workspace = true
derive_more = "0.99.17"
uuid.workspace = true
blake3.workspace = true
jsonwebtoken.workspace = true
scopeguard.workspace = true

[dev-dependencies]
jsonwebkey = { version = "0.3.5", features = ["generate","jwt-convert"] }
jsonwebtoken.workspace = true
141 changes: 89 additions & 52 deletions crates/client-api/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ use axum::response::IntoResponse;
use axum_extra::typed_header::TypedHeader;
use headers::{authorization, HeaderMapExt};
use http::{request, HeaderValue, StatusCode};
use serde::Deserialize;
use spacetimedb::auth::identity::SpacetimeIdentityClaims2;
use spacetimedb::auth::identity::{
decode_token, encode_token, DecodingKey, EncodingKey, JwtError, JwtErrorKind, SpacetimeIdentityClaims,
use serde::{Deserialize, Serialize};
use spacetimedb::auth::identity::SpacetimeIdentityClaims;
use spacetimedb::auth::identity::{JwtError, JwtErrorKind};
use spacetimedb::auth::token_validation::{
new_validator, DefaultValidator, TokenSigner, TokenValidationError, TokenValidator,
};
use spacetimedb::auth::token_validation::{validate_token, TokenValidationError};
use spacetimedb::auth::JwtKeys;
use spacetimedb::energy::EnergyQuanta;
use spacetimedb::identity::Identity;
use uuid::Uuid;
Expand Down Expand Up @@ -57,18 +58,10 @@ impl SpacetimeCreds {
pub fn token(&self) -> &str {
&self.token
}
/// Decode this token into auth claims.
pub fn decode_token(&self, public_key: &DecodingKey) -> Result<SpacetimeIdentityClaims, JwtError> {
decode_token(public_key, self.token()).map(|x| x.claims)
}

pub fn from_signed_token(token: String) -> Self {
Self { token }
}
/// Mint a new credentials JWT for an identity.
pub fn encode_token(private_key: &EncodingKey, identity: Identity) -> Result<Self, JwtError> {
let token = encode_token(private_key, identity)?;
Ok(Self { token })
}

/// Extract credentials from the headers or else query string of a request.
fn from_request_parts(parts: &request::Parts) -> Result<Option<Self>, headers::Error> {
Expand Down Expand Up @@ -137,25 +130,24 @@ impl TokenClaims {

pub fn encode_and_sign_with_expiry(
&self,
private_key: &EncodingKey,
signer: &impl TokenSigner,
expiry: Option<Duration>,
) -> Result<String, JwtError> {
let iat = SystemTime::now();
let exp = expiry.map(|dur| iat + dur);
let claims = SpacetimeIdentityClaims2 {
let claims = SpacetimeIdentityClaims {
identity: self.id(),
subject: self.subject.clone(),
issuer: self.issuer.clone(),
audience: self.audience.clone(),
iat,
exp,
};
let header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::ES256);
jsonwebtoken::encode(&header, &claims, private_key)
signer.sign(&claims)
}

pub fn encode_and_sign(&self, private_key: &EncodingKey) -> Result<String, JwtError> {
self.encode_and_sign_with_expiry(private_key, None)
pub fn encode_and_sign(&self, signer: &impl TokenSigner) -> Result<String, JwtError> {
self.encode_and_sign_with_expiry(signer, None)
}
}

Expand All @@ -165,23 +157,23 @@ impl SpacetimeAuth {
// Generate claims with a random subject.
let subject = Uuid::new_v4().to_string();
let claims = TokenClaims {
issuer: ctx.local_issuer(),
issuer: ctx.jwt_auth_provider().local_issuer().to_owned(),
subject: subject.clone(),
// Placeholder audience.
audience: vec!["spacetimedb".to_string()],
};

let identity = claims.id();
let creds = {
let token = claims.encode_and_sign(ctx.private_key()).map_err(log_and_500)?;
let token = claims.encode_and_sign(ctx.jwt_auth_provider()).map_err(log_and_500)?;
SpacetimeCreds::from_signed_token(token)
};

Ok(Self {
creds,
identity,
subject,
issuer: ctx.local_issuer(),
issuer: ctx.jwt_auth_provider().local_issuer().to_string(),
})
}

Expand All @@ -196,52 +188,94 @@ impl SpacetimeAuth {
// Sign a new token with the same claims and a new expiry.
// Note that this will not change the issuer, so the private_key might not match.
// We do this to create short-lived tokens that we will be able to verify.
pub fn re_sign_with_expiry(&self, private_key: &EncodingKey, expiry: Duration) -> Result<String, JwtError> {
TokenClaims::from(self.clone()).encode_and_sign_with_expiry(private_key, Some(expiry))
pub fn re_sign_with_expiry(&self, signer: &impl TokenSigner, expiry: Duration) -> Result<String, JwtError> {
TokenClaims::from(self.clone()).encode_and_sign_with_expiry(signer, Some(expiry))
}
}

#[cfg(test)]
mod tests {
use crate::auth::TokenClaims;
use anyhow::Ok;
use jsonwebkey as jwk;
use jsonwebtoken::{DecodingKey, EncodingKey};
use spacetimedb::auth::identity;
// JwtAuthProvider is used for signing and verifying JWT tokens.
pub trait JwtAuthProvider: Sync + Send + TokenSigner {
type TV: TokenValidator + Send + Sync;
/// Used to validate incoming JWTs.
fn validator(&self) -> &Self::TV;

/// The issuer to use when signing JWTs.
fn local_issuer(&self) -> &str;

/// Return the public key used to verify JWTs, as the bytes of a PEM public key file.
///
/// The `/identity/public-key` route calls this method to return the public key to callers.
fn public_key_bytes(&self) -> &[u8];
}

pub struct JwtKeyAuthProvider<TV: TokenValidator + Send + Sync> {
keys: JwtKeys,
local_issuer: String,
validator: TV,
}

pub type DefaultJwtAuthProvider = JwtKeyAuthProvider<DefaultValidator>;

// Create a new AuthEnvironment using the default caching validator.
pub fn default_auth_environment(keys: JwtKeys, local_issuer: String) -> JwtKeyAuthProvider<DefaultValidator> {
let validator = new_validator(keys.public.clone(), local_issuer.clone());
JwtKeyAuthProvider::new(keys, local_issuer, validator)
}

impl<TV: TokenValidator + Send + Sync> JwtKeyAuthProvider<TV> {
fn new(keys: JwtKeys, local_issuer: String, validator: TV) -> Self {
Self {
keys,
local_issuer,
validator,
}
}
}

// TODO: this keypair stuff is duplicated. We should create a test-only crate with helpers.
struct KeyPair {
pub public_key: DecodingKey,
pub private_key: EncodingKey,
impl<TV: TokenValidator + Send + Sync> TokenSigner for JwtKeyAuthProvider<TV> {
fn sign<T: Serialize>(&self, claims: &T) -> Result<String, JwtError> {
let header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::ES256);
jsonwebtoken::encode(&header, &claims, &self.keys.private)
}
}

fn new_keypair() -> anyhow::Result<KeyPair> {
let mut my_jwk = jwk::JsonWebKey::new(jwk::Key::generate_p256());
impl<TV: TokenValidator + Send + Sync> JwtAuthProvider for JwtKeyAuthProvider<TV> {
type TV = TV;

my_jwk.set_algorithm(jwk::Algorithm::ES256).unwrap();
let public_key = jsonwebtoken::DecodingKey::from_ec_pem(my_jwk.key.to_public().unwrap().to_pem().as_bytes())?;
let private_key = jsonwebtoken::EncodingKey::from_ec_pem(my_jwk.key.try_to_pem()?.as_bytes())?;
Ok(KeyPair {
public_key,
private_key,
})
fn local_issuer(&self) -> &str {
&self.local_issuer
}

fn public_key_bytes(&self) -> &[u8] {
&self.keys.public_pem
}

fn validator(&self) -> &Self::TV {
&self.validator
}
}

#[cfg(test)]
mod tests {
use crate::auth::TokenClaims;
use anyhow::Ok;
use spacetimedb::auth::{token_validation::TokenValidator, JwtKeys};

// Make sure that when we encode TokenClaims, we can decode to get the expected identity.
#[test]
fn decode_encoded_token() -> Result<(), anyhow::Error> {
let kp = new_keypair()?;
#[tokio::test]
async fn decode_encoded_token() -> Result<(), anyhow::Error> {
let kp = JwtKeys::generate()?;

let claims = TokenClaims {
issuer: "localhost".to_string(),
subject: "test-subject".to_string(),
audience: vec!["spacetimedb".to_string()],
};
let id = claims.id();
let token = claims.encode_and_sign(&kp.private_key)?;
let token = claims.encode_and_sign(&kp.private)?;
let decoded = kp.public.validate_token(&token).await?;

let decoded = identity::decode_token(&kp.public_key, &token)?;
assert_eq!(decoded.claims.identity, id);
assert_eq!(decoded.identity, id);
Ok(())
}
}
Expand All @@ -258,7 +292,10 @@ impl<S: NodeDelegate + Send + Sync> axum::extract::FromRequestParts<S> for Space
return Ok(Self { auth: None });
};

let claims = validate_token(state.public_key().clone(), &state.local_issuer(), &creds.token)
let claims = state
.jwt_auth_provider()
.validator()
.validate_token(&creds.token)
.await
.map_err(AuthorizationRejection::Custom)?;

Expand Down
33 changes: 5 additions & 28 deletions crates/client-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use async_trait::async_trait;
use axum::response::ErrorResponse;
use http::StatusCode;

use spacetimedb::auth::identity::{DecodingKey, EncodingKey};
use spacetimedb::client::ClientActorIndex;
use spacetimedb::energy::{EnergyBalance, EnergyQuanta};
use spacetimedb::host::{HostController, UpdateDatabaseResult};
Expand All @@ -26,19 +25,8 @@ pub trait NodeDelegate: Send + Sync {
fn host_controller(&self) -> &HostController;
fn client_actor_index(&self) -> &ClientActorIndex;

/// Return a JWT decoding key for verifying credentials.
fn public_key(&self) -> &DecodingKey;

// The issuer to use when signing JWTs.
fn local_issuer(&self) -> String;

/// Return the public key used to verify JWTs, as the bytes of a PEM public key file.
///
/// The `/identity/public-key` route calls this method to return the public key to callers.
fn public_key_bytes(&self) -> &[u8];

/// Return a JWT encoding key for signing credentials.
fn private_key(&self) -> &EncodingKey;
type JwtAuthProviderT: auth::JwtAuthProvider;
fn jwt_auth_provider(&self) -> &Self::JwtAuthProviderT;
}

/// Parameters for publishing a database.
Expand Down Expand Up @@ -222,14 +210,11 @@ impl<T: ControlStateWriteAccess + ?Sized> ControlStateWriteAccess for Arc<T> {
}

impl<T: NodeDelegate + ?Sized> NodeDelegate for Arc<T> {
type JwtAuthProviderT = T::JwtAuthProviderT;
fn gather_metrics(&self) -> Vec<prometheus::proto::MetricFamily> {
(**self).gather_metrics()
}

fn local_issuer(&self) -> String {
(**self).local_issuer()
}

fn host_controller(&self) -> &HostController {
(**self).host_controller()
}
Expand All @@ -238,16 +223,8 @@ impl<T: NodeDelegate + ?Sized> NodeDelegate for Arc<T> {
(**self).client_actor_index()
}

fn public_key(&self) -> &DecodingKey {
(**self).public_key()
}

fn public_key_bytes(&self) -> &[u8] {
(**self).public_key_bytes()
}

fn private_key(&self) -> &EncodingKey {
(**self).private_key()
fn jwt_auth_provider(&self) -> &Self::JwtAuthProviderT {
(**self).jwt_auth_provider()
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/client-api/src/routes/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use spacetimedb_lib::de::serde::DeserializeWrapper;
use spacetimedb_lib::Identity;

use crate::auth::{SpacetimeAuth, SpacetimeAuthRequired};
use crate::auth::{JwtAuthProvider, SpacetimeAuth, SpacetimeAuthRequired};
use crate::{log_and_500, ControlStateDelegate, NodeDelegate};

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -104,7 +104,7 @@ pub async fn create_websocket_token<S: NodeDelegate>(
) -> axum::response::Result<impl IntoResponse> {
let expiry = Duration::from_secs(60);
let token = auth
.re_sign_with_expiry(ctx.private_key(), expiry)
.re_sign_with_expiry(ctx.jwt_auth_provider(), expiry)
.map_err(log_and_500)?;
// let token = encode_token_with_expiry(ctx.private_key(), auth.identity, Some(expiry)).map_err(log_and_500)?;
Ok(axum::Json(WebsocketTokenResponse { token }))
Expand All @@ -131,7 +131,7 @@ pub async fn validate_token(
pub async fn get_public_key<S: NodeDelegate>(State(ctx): State<S>) -> axum::response::Result<impl IntoResponse> {
Ok((
[(CONTENT_TYPE, "application/pem-certificate-chain")],
ctx.public_key_bytes().to_owned(),
ctx.jwt_auth_provider().public_key_bytes().to_owned(),
))
}

Expand Down
3 changes: 0 additions & 3 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ clap.workspace = true
crossbeam-channel.workspace = true
derive_more.workspace = true
dirs.workspace = true
email_address.workspace = true
enum-as-inner.workspace = true
enum-map.workspace = true
flate2.workspace = true
Expand Down Expand Up @@ -127,7 +126,5 @@ proptest-derive.workspace = true
rand.workspace = true
env_logger.workspace = true
pretty_assertions.workspace = true
jsonwebkey = { version = "0.3.5", features = ["generate", "jwt-convert"] }
jsonwebtoken.workspace = true
axum-test = "16.2.0"
axum.workspace = true
Loading

2 comments on commit cccadd1

@github-actions
Copy link

@github-actions github-actions bot commented on cccadd1 Nov 12, 2024

Choose a reason for hiding this comment

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

Criterion benchmark results

Criterion benchmark report

YOU SHOULD PROBABLY IGNORE THESE RESULTS.

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

Go look at the callgrind report instead.

empty

db on disk new latency old latency new throughput old throughput
sqlite 💿 424.5±2.44ns 407.4±1.29ns - -
sqlite 🧠 417.2±2.26ns 403.2±2.17ns - -
stdb_raw 💿 778.2±1.12ns 774.7±4.03ns - -
stdb_raw 🧠 773.5±2.56ns 774.8±0.81ns - -

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.63µs 581.0±0.66µs 1709 tx/sec 1721 tx/sec
sqlite 💿 u32_u64_str unique_0 2048 256 151.3±0.50µs 148.8±0.48µs 6.5 Ktx/sec 6.6 Ktx/sec
sqlite 💿 u32_u64_u64 btree_each_column 2048 256 467.2±0.55µs 460.7±0.44µs 2.1 Ktx/sec 2.1 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 2048 256 150.8±32.69µs 141.3±15.39µs 6.5 Ktx/sec 6.9 Ktx/sec
sqlite 🧠 u32_u64_str btree_each_column 2048 256 448.7±0.84µs 442.3±0.59µs 2.2 Ktx/sec 2.2 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 2048 256 124.8±0.71µs 118.6±0.31µs 7.8 Ktx/sec 8.2 Ktx/sec
sqlite 🧠 u32_u64_u64 btree_each_column 2048 256 367.1±1.01µs 361.2±0.46µs 2.7 Ktx/sec 2.7 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 2048 256 108.5±1.21µs 103.3±0.34µs 9.0 Ktx/sec 9.5 Ktx/sec
stdb_raw 💿 u32_u64_str btree_each_column 2048 256 601.1±23.14µs 499.2±60.86µs 1663 tx/sec 2003 tx/sec
stdb_raw 💿 u32_u64_str unique_0 2048 256 478.3±18.87µs 486.2±31.81µs 2.0 Ktx/sec 2.0 Ktx/sec
stdb_raw 💿 u32_u64_u64 btree_each_column 2048 256 365.8±9.22µs 372.4±7.61µs 2.7 Ktx/sec 2.6 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 2048 256 346.3±5.92µs 349.1±5.65µs 2.8 Ktx/sec 2.8 Ktx/sec
stdb_raw 🧠 u32_u64_str btree_each_column 2048 256 293.5±0.31µs 298.8±0.29µs 3.3 Ktx/sec 3.3 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 2048 256 228.1±0.94µs 231.4±0.73µs 4.3 Ktx/sec 4.2 Ktx/sec
stdb_raw 🧠 u32_u64_u64 btree_each_column 2048 256 232.0±0.17µs 235.9±0.20µs 4.2 Ktx/sec 4.1 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 2048 256 210.0±0.29µs 209.0±0.73µs 4.7 Ktx/sec 4.7 Ktx/sec

iterate

db on disk schema indices new latency old latency new throughput old throughput
sqlite 💿 u32_u64_str unique_0 23.6±0.35µs 23.1±0.36µs 41.3 Ktx/sec 42.2 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 21.6±0.07µs 21.2±0.18µs 45.2 Ktx/sec 46.0 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 21.3±0.06µs 20.6±0.16µs 45.9 Ktx/sec 47.4 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 19.0±0.07µs 18.7±0.15µs 51.5 Ktx/sec 52.1 Ktx/sec
stdb_raw 💿 u32_u64_str unique_0 4.9±0.00µs 4.9±0.00µs 199.5 Ktx/sec 199.7 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 4.8±0.00µs 4.8±0.00µs 203.8 Ktx/sec 204.5 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 4.9±0.00µs 4.9±0.00µs 199.7 Ktx/sec 200.3 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 4.8±0.00µs 4.8±0.00µs 203.9 Ktx/sec 204.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.0±0.20µs 70.4±0.29µs 14.1 Ktx/sec 13.9 Ktx/sec
sqlite 💿 u64 index 2048 256 64.6±0.13µs 67.4±0.29µs 15.1 Ktx/sec 14.5 Ktx/sec
sqlite 🧠 string index 2048 256 65.2±0.15µs 66.4±0.22µs 15.0 Ktx/sec 14.7 Ktx/sec
sqlite 🧠 u64 index 2048 256 57.7±0.11µs 60.9±0.23µs 16.9 Ktx/sec 16.0 Ktx/sec
stdb_raw 💿 string index 2048 256 4.9±0.00µs 4.9±0.00µs 198.5 Ktx/sec 198.1 Ktx/sec
stdb_raw 💿 u64 index 2048 256 4.8±0.00µs 4.9±0.00µs 201.4 Ktx/sec 201.3 Ktx/sec
stdb_raw 🧠 string index 2048 256 4.9±0.00µs 4.9±0.00µs 198.5 Ktx/sec 198.2 Ktx/sec
stdb_raw 🧠 u64 index 2048 256 4.8±0.00µs 4.8±0.00µs 201.6 Ktx/sec 201.5 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.6±0.01µs 26.6 Mtx/sec 26.5 Mtx/sec
u32_u64_str bflatn_to_bsatn_slow_path 100 2.9±0.00µs 3.1±0.01µs 33.0 Mtx/sec 30.3 Mtx/sec
u32_u64_str bsatn 100 15.3±0.05ns 15.3±0.01ns 6.1 Gtx/sec 6.1 Gtx/sec
u32_u64_str bsatn 100 2.3±0.01µs 2.2±0.01µs 41.0 Mtx/sec 44.2 Mtx/sec
u32_u64_str json 100 5.2±0.03µs 5.1±0.03µs 18.4 Mtx/sec 18.8 Mtx/sec
u32_u64_str json 100 8.9±0.03µs 8.8±0.15µs 10.7 Mtx/sec 10.8 Mtx/sec
u32_u64_str product_value 100 1018.6±3.16ns 1021.7±0.65ns 93.6 Mtx/sec 93.3 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_fast_path 100 949.9±2.18ns 947.1±2.88ns 100.4 Mtx/sec 100.7 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_slow_path 100 2.4±0.00µs 2.4±0.00µs 39.7 Mtx/sec 39.5 Mtx/sec
u32_u64_u64 bsatn 100 1816.0±27.18ns 1596.1±23.30ns 52.5 Mtx/sec 59.7 Mtx/sec
u32_u64_u64 bsatn 100 6.8±0.02ns 6.8±0.03ns 13.8 Gtx/sec 13.7 Gtx/sec
u32_u64_u64 json 100 3.7±0.00µs 3.2±0.07µs 25.8 Mtx/sec 30.1 Mtx/sec
u32_u64_u64 json 100 5.9±0.03µs 6.0±0.04µs 16.1 Mtx/sec 15.8 Mtx/sec
u32_u64_u64 product_value 100 1016.7±0.59ns 1016.2±0.82ns 93.8 Mtx/sec 93.8 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_fast_path 100 715.5±9.36ns 721.4±1.53ns 133.3 Mtx/sec 132.2 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_slow_path 100 2.4±0.01µs 2.4±0.01µs 39.6 Mtx/sec 39.5 Mtx/sec
u64_u64_u32 bsatn 100 1786.5±50.92ns 1589.9±22.68ns 53.4 Mtx/sec 60.0 Mtx/sec
u64_u64_u32 bsatn 100 644.9±2.08ns 642.4±1.54ns 147.9 Mtx/sec 148.4 Mtx/sec
u64_u64_u32 json 100 3.7±0.04µs 3.3±0.10µs 25.9 Mtx/sec 28.8 Mtx/sec
u64_u64_u32 json 100 6.2±0.23µs 6.1±0.03µs 15.4 Mtx/sec 15.7 Mtx/sec
u64_u64_u32 product_value 100 1015.8±0.64ns 1015.6±0.99ns 93.9 Mtx/sec 93.9 Mtx/sec

stdb_module_large_arguments

arg size new latency old latency new throughput old throughput
64KiB 105.6±7.10µs 108.2±9.46µs - -

stdb_module_print_bulk

line count new latency old latency new throughput old throughput
1 54.2±7.85µs 56.1±4.74µs - -
100 595.5±4.65µs 602.9±11.35µs - -
1000 3.9±0.81ms 4.9±0.87ms - -

remaining

name new latency old latency new throughput old throughput
special/db_game/circles/load=10 47.0±3.98ms 50.7±4.71ms - -
special/db_game/circles/load=100 50.6±7.48ms 51.8±4.76ms - -
special/db_game/ia_loop/load=500 143.1±2.01ms 142.2±2.46ms - -
special/db_game/ia_loop/load=5000 5.3±0.03s 5.4±0.03s - -
sqlite/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 57.9±0.56µs 54.1±0.19µs 16.9 Ktx/sec 18.0 Ktx/sec
sqlite/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 48.8±0.14µs 45.4±0.08µs 20.0 Ktx/sec 21.5 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 42.6±0.56µs 39.4±0.16µs 22.9 Ktx/sec 24.8 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 37.4±0.20µs 34.5±0.13µs 26.1 Ktx/sec 28.3 Ktx/sec
stdb_module/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 1225.8±18.57µs 1280.4±11.41µs 815 tx/sec 780 tx/sec
stdb_module/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 958.8±7.33µs 1015.5±10.26µs 1043 tx/sec 984 tx/sec
stdb_raw/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 632.5±10.25µs 641.7±13.24µs 1580 tx/sec 1558 tx/sec
stdb_raw/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 419.9±7.34µs 456.3±31.70µs 2.3 Ktx/sec 2.1 Ktx/sec
stdb_raw/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 365.6±0.64µs 374.8±0.57µs 2.7 Ktx/sec 2.6 Ktx/sec
stdb_raw/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 331.2±0.47µs 341.3±0.94µs 2.9 Ktx/sec 2.9 Ktx/sec

@github-actions
Copy link

@github-actions github-actions bot commented on cccadd1 Nov 12, 2024

Choose a reason for hiding this comment

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

Callgrind benchmark results

Callgrind Benchmark Report

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

Measurement changes larger than five percent are in bold.

In-memory benchmarks

callgrind: empty transaction

db total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw 6397 6397 0.00% 6497 6493 0.06%
sqlite 5589 5579 0.18% 6007 5993 0.23%

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 76592 76591 0.00% 77116 77063 0.07%
stdb_raw u32_u64_str no_index 64 128 2 string 118834 119089 -0.21% 119632 119711 -0.07%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 25081 25082 -0.00% 25831 25632 0.78%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 24049 24049 0.00% 24613 24489 0.51%
sqlite u32_u64_str no_index 64 128 2 string 144695 144695 0.00% 146219 146111 0.07%
sqlite u32_u64_str no_index 64 128 1 u64 124044 124044 0.00% 125334 125258 0.06%
sqlite u32_u64_str btree_each_column 64 128 1 u64 131361 131361 0.00% 132919 132791 0.10%
sqlite u32_u64_str btree_each_column 64 128 2 string 134494 134494 0.00% 136190 136062 0.09%

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 872325 873590 -0.14% 889545 889708 -0.02%
stdb_raw u32_u64_str btree_each_column 64 128 1020180 1022673 -0.24% 1052658 1048073 0.44%
sqlite u32_u64_str unique_0 64 128 398320 398320 0.00% 415254 415516 -0.06%
sqlite u32_u64_str btree_each_column 64 128 983637 983643 -0.00% 1019611 1022689 -0.30%

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 153721 153724 -0.00% 153833 153840 -0.00%
stdb_raw u32_u64_str unique_0 64 16746 16749 -0.02% 16846 16845 0.01%
sqlite u32_u64_str unique_0 1024 1067255 1067255 0.00% 1070627 1070547 0.01%
sqlite u32_u64_str unique_0 64 76201 76201 0.00% 77199 77195 0.01%

callgrind: serialize_product_value

count format total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
64 json 47528 47528 0.00% 50214 50286 -0.14%
64 bsatn 25509 25509 0.00% 27821 27787 0.12%
16 bsatn 8200 8200 0.00% 9628 9594 0.35%
16 json 12188 12188 0.00% 14126 14194 -0.48%

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 19981315 20011708 -0.15% 20486849 20486134 0.00%
stdb_raw u32_u64_str unique_0 64 128 1278415 1280941 -0.20% 1315457 1316171 -0.05%
sqlite u32_u64_str unique_0 1024 1024 1802182 1802150 0.00% 1811446 1811422 0.00%
sqlite u32_u64_str unique_0 64 128 128528 128496 0.02% 131352 131432 -0.06%
On-disk benchmarks

callgrind: empty transaction

db total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
stdb_raw 6402 6402 0.00% 6498 6518 -0.31%
sqlite 5621 5621 0.00% 6061 6103 -0.69%

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 76597 76596 0.00% 77085 77044 0.05%
stdb_raw u32_u64_str no_index 64 128 2 string 118839 119094 -0.21% 119677 119732 -0.05%
stdb_raw u32_u64_str btree_each_column 64 128 2 string 25086 25088 -0.01% 25800 25622 0.69%
stdb_raw u32_u64_str btree_each_column 64 128 1 u64 24054 24054 0.00% 24594 24462 0.54%
sqlite u32_u64_str no_index 64 128 1 u64 125965 125965 0.00% 127487 127523 -0.03%
sqlite u32_u64_str no_index 64 128 2 string 146616 146616 0.00% 148444 148380 0.04%
sqlite u32_u64_str btree_each_column 64 128 2 string 136616 136616 0.00% 138722 138690 0.02%
sqlite u32_u64_str btree_each_column 64 128 1 u64 133457 133457 0.00% 135385 135293 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 820924 822629 -0.21% 836612 837943 -0.16%
stdb_raw u32_u64_str btree_each_column 64 128 967924 971436 -0.36% 998342 1028118 -2.90%
sqlite u32_u64_str unique_0 64 128 415857 415857 0.00% 432023 432363 -0.08%
sqlite u32_u64_str btree_each_column 64 128 1021898 1021898 0.00% 1058142 1059252 -0.10%

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 153726 153729 -0.00% 153822 153825 -0.00%
stdb_raw u32_u64_str unique_0 64 16751 16754 -0.02% 16847 16846 0.01%
sqlite u32_u64_str unique_0 1024 1070323 1070323 0.00% 1074145 1074061 0.01%
sqlite u32_u64_str unique_0 64 77973 77973 0.00% 79267 79167 0.13%

callgrind: serialize_product_value

count format total reads + writes old total reads + writes Δrw estimated cycles old estimated cycles Δcycles
64 json 47528 47528 0.00% 50214 50286 -0.14%
64 bsatn 25509 25509 0.00% 27821 27787 0.12%
16 bsatn 8200 8200 0.00% 9628 9594 0.35%
16 json 12188 12188 0.00% 14126 14194 -0.48%

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 18901673 18939889 -0.20% 19473275 19506595 -0.17%
stdb_raw u32_u64_str unique_0 64 128 1231044 1233586 -0.21% 1296962 1300386 -0.26%
sqlite u32_u64_str unique_0 1024 1024 1809743 1809711 0.00% 1818411 1818439 -0.00%
sqlite u32_u64_str unique_0 64 128 132654 132622 0.02% 135694 135634 0.04%

Please sign in to comment.