diff --git a/Cargo.lock b/Cargo.lock index 4a1c238f49d..5debdc2594b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -842,6 +842,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chain-call-repro" +version = "0.0.0" +dependencies = [ + "spacetimedb 2.1.0", +] + [[package]] name = "check-license-symlinks" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 2e8daf523d7..7d82ec900e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ members = [ "modules/keynote-benchmarks", "modules/perf-test", "modules/module-test", + "modules/chain-call-repro", "templates/basic-rs/spacetimedb", "templates/chat-console-rs/spacetimedb", "templates/keynote-2/spacetimedb-rust-client", @@ -262,7 +263,7 @@ rand_distr = "0.5.1" rayon = "1.8" rayon-core = "1.11.0" regex = "1" -reqwest = { version = "0.12", features = ["stream", "json"] } +reqwest = { version = "0.12", features = ["stream", "json", "blocking"] } rolldown = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" } rolldown_common = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" } rolldown_error = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" } diff --git a/crates/bindings-sys/src/lib.rs b/crates/bindings-sys/src/lib.rs index 927c444a38d..630428ab653 100644 --- a/crates/bindings-sys/src/lib.rs +++ b/crates/bindings-sys/src/lib.rs @@ -1504,10 +1504,10 @@ pub fn call_reducer_on_db( identity: [u8; 32], reducer_name: &str, args: &[u8], -) -> Result<(u16, raw::BytesSource), raw::BytesSource> { +) -> Result<(u16, raw::BytesSource), (Errno, raw::BytesSource)> { let mut out = raw::BytesSource::INVALID; let status = unsafe { - raw::call_reducer_on_db( + raw::call_reducer_on_db_2pc( identity.as_ptr(), reducer_name.as_ptr(), reducer_name.len() as u32, @@ -1520,10 +1520,14 @@ pub fn call_reducer_on_db( // on transport failure. Unlike other ABI functions, a non-zero return value here // does NOT indicate a generic errno — it's the HTTP status code. Only HTTP_ERROR // specifically signals a transport-level failure. - if status == Errno::HTTP_ERROR.code() { - Err(out) - } else { + if (100..=599).contains(&status) { Ok((status, out)) + } else { + match Errno::from_code(status) { + Some(errno @ (Errno::HTTP_ERROR | Errno::WOUNDED_TRANSACTION)) => Err((errno, out)), + Some(errno) => panic!("{errno:?}"), + None => Ok((status, out)), + } } } @@ -1539,7 +1543,7 @@ pub fn call_reducer_on_db_2pc( identity: [u8; 32], reducer_name: &str, args: &[u8], -) -> Result<(u16, raw::BytesSource), raw::BytesSource> { +) -> Result<(u16, raw::BytesSource), (Errno, raw::BytesSource)> { let mut out = raw::BytesSource::INVALID; let status = unsafe { raw::call_reducer_on_db_2pc( @@ -1551,10 +1555,14 @@ pub fn call_reducer_on_db_2pc( &mut out, ) }; - if status == Errno::HTTP_ERROR.code() { - Err(out) - } else { + if (100..=599).contains(&status) { Ok((status, out)) + } else { + match Errno::from_code(status) { + Some(errno @ (Errno::HTTP_ERROR | Errno::WOUNDED_TRANSACTION)) => Err((errno, out)), + Some(errno) => panic!("{errno:?}"), + None => Ok((status, out)), + } } } diff --git a/crates/bindings/src/remote_reducer.rs b/crates/bindings/src/remote_reducer.rs index 701282c66f6..8ec58b6b786 100644 --- a/crates/bindings/src/remote_reducer.rs +++ b/crates/bindings/src/remote_reducer.rs @@ -19,6 +19,7 @@ //! Err(remote_reducer::RemoteCallError::Failed(msg)) => log::error!("reducer failed: {msg}"), //! Err(remote_reducer::RemoteCallError::NotFound(msg)) => log::error!("not found: {msg}"), //! Err(remote_reducer::RemoteCallError::Unreachable(msg)) => log::error!("unreachable: {msg}"), +//! Err(remote_reducer::RemoteCallError::Wounded(msg)) => log::warn!("wounded: {msg}"), //! } //! } //! ``` @@ -34,6 +35,8 @@ pub enum RemoteCallError { NotFound(String), /// The call could not be delivered (connection refused, timeout, network error, etc.). Unreachable(String), + /// The distributed transaction was wounded by an older transaction. + Wounded(String), } impl core::fmt::Display for RemoteCallError { @@ -42,10 +45,18 @@ impl core::fmt::Display for RemoteCallError { RemoteCallError::Failed(msg) => write!(f, "remote reducer failed: {msg}"), RemoteCallError::NotFound(msg) => write!(f, "remote database or reducer not found: {msg}"), RemoteCallError::Unreachable(msg) => write!(f, "remote database unreachable: {msg}"), + RemoteCallError::Wounded(msg) => write!(f, "{msg}"), } } } +pub fn into_reducer_error_message(error: RemoteCallError) -> String { + match error { + RemoteCallError::Wounded(msg) => crate::rt::encode_wounded_error_message(msg), + other => other.to_string(), + } +} + /// Call a reducer on a remote database. /// /// - `database_identity`: the target database. @@ -56,6 +67,7 @@ impl core::fmt::Display for RemoteCallError { /// Returns `Err(RemoteCallError::Failed(msg))` when the reducer ran but returned an error. /// Returns `Err(RemoteCallError::NotFound(msg))` when the database or reducer does not exist. /// Returns `Err(RemoteCallError::Unreachable(msg))` on transport failure (connection refused, timeout, …). +/// Returns `Err(RemoteCallError::Wounded(msg))` if the surrounding distributed transaction was wounded. pub fn call_reducer_on_db( database_identity: Identity, reducer_name: &str, @@ -66,7 +78,9 @@ pub fn call_reducer_on_db( Ok((status, body_source)) => { if status < 300 { let mut out = Vec::new(); - read_bytes_source_into(body_source, &mut out); + if body_source != spacetimedb_bindings_sys::raw::BytesSource::INVALID { + read_bytes_source_into(body_source, &mut out); + } return Ok(out); } // Decode the response body as the error message. @@ -83,10 +97,14 @@ pub fn call_reducer_on_db( Err(RemoteCallError::Failed(msg)) } } - Err(err_source) => { + Err((errno, err_source)) => { use crate::rt::read_bytes_source_as; let msg = read_bytes_source_as::(err_source); - Err(RemoteCallError::Unreachable(msg)) + Err(if errno == spacetimedb_bindings_sys::Errno::WOUNDED_TRANSACTION { + RemoteCallError::Wounded(msg) + } else { + RemoteCallError::Unreachable(msg) + }) } } } @@ -103,12 +121,16 @@ pub fn call_reducer_on_db_2pc( database_identity: Identity, reducer_name: &str, args: &[u8], -) -> Result<(), RemoteCallError> { +) -> Result, RemoteCallError> { let identity_bytes = database_identity.to_byte_array(); match spacetimedb_bindings_sys::call_reducer_on_db_2pc(identity_bytes, reducer_name, args) { Ok((status, body_source)) => { if status < 300 { - return Ok(()); + let mut out = Vec::new(); + if body_source != spacetimedb_bindings_sys::raw::BytesSource::INVALID { + read_bytes_source_into(body_source, &mut out); + } + return Ok(out); } let msg = if body_source == spacetimedb_bindings_sys::raw::BytesSource::INVALID { String::new() @@ -123,10 +145,14 @@ pub fn call_reducer_on_db_2pc( Err(RemoteCallError::Failed(msg)) } } - Err(err_source) => { + Err((errno, err_source)) => { use crate::rt::read_bytes_source_as; let msg = read_bytes_source_as::(err_source); - Err(RemoteCallError::Unreachable(msg)) + Err(if errno == spacetimedb_bindings_sys::Errno::WOUNDED_TRANSACTION { + RemoteCallError::Wounded(msg) + } else { + RemoteCallError::Unreachable(msg) + }) } } } diff --git a/crates/bindings/src/rt.rs b/crates/bindings/src/rt.rs index 780e514e7dd..24a7e2aff3f 100644 --- a/crates/bindings/src/rt.rs +++ b/crates/bindings/src/rt.rs @@ -919,6 +919,12 @@ static DESCRIBERS: Mutex>> = Mutex::new(Vec::new()); /// A reducer function takes in `(ReducerContext, Args)` /// and returns a result with a possible error message. pub type ReducerFn = fn(&ReducerContext, &[u8]) -> ReducerResult; + +const WOUNDED_ERROR_PREFIX: &str = "__STDB_WOUNDED__:"; + +pub fn encode_wounded_error_message(message: impl Into) -> String { + format!("{WOUNDED_ERROR_PREFIX}{}", message.into()) +} static REDUCERS: OnceLock> = OnceLock::new(); #[cfg(feature = "unstable")] diff --git a/crates/client-api/src/routes/database.rs b/crates/client-api/src/routes/database.rs index 8f72fe867ec..2f1d0d16bff 100644 --- a/crates/client-api/src/routes/database.rs +++ b/crates/client-api/src/routes/database.rs @@ -21,12 +21,14 @@ use axum::routing::MethodRouter; use axum::Extension; use axum_extra::TypedHeader; use futures::TryStreamExt; +use http::HeaderMap; use http::StatusCode; use log::{info, warn}; use serde::Deserialize; use spacetimedb::database_logger::DatabaseLogger; use spacetimedb::host::module_host::ClientConnectedError; use spacetimedb::host::{CallResult, UpdateDatabaseResult}; +use spacetimedb::worker_metrics::WORKER_METRICS; use spacetimedb::host::{FunctionArgs, MigratePlanResult}; use spacetimedb::host::{ModuleHost, ReducerOutcome}; use spacetimedb::host::{ProcedureCallError, ReducerCallError}; @@ -41,10 +43,11 @@ use spacetimedb_lib::bsatn; use spacetimedb_lib::db::raw_def::v10::RawModuleDefV10; use spacetimedb_lib::db::raw_def::v9::RawModuleDefV9; use spacetimedb_lib::de::DeserializeSeed; -use spacetimedb_lib::{sats, AlgebraicValue, Hash, ProductValue, Timestamp}; +use spacetimedb_lib::{sats, AlgebraicValue, GlobalTxId, Hash, ProductValue, Timestamp, TX_ID_HEADER}; use spacetimedb_schema::auto_migrate::{ MigrationPolicy as SchemaMigrationPolicy, MigrationToken, PrettyPrintStyle as AutoMigratePrettyPrintStyle, }; +use tokio::time::sleep; use super::subscribe::{handle_websocket, HasWebSocketOptions}; @@ -88,6 +91,7 @@ pub struct CallParams { pub const NO_SUCH_DATABASE: (StatusCode, &str) = (StatusCode::NOT_FOUND, "No such database."); const MISDIRECTED: (StatusCode, &str) = (StatusCode::NOT_FOUND, "Database is not scheduled on this host"); +const PREPARE_ID_HEADER: &str = "X-Prepare-Id"; fn map_reducer_error(e: ReducerCallError, reducer: &str) -> (StatusCode, String) { let status_code = match e { @@ -95,6 +99,7 @@ fn map_reducer_error(e: ReducerCallError, reducer: &str) -> (StatusCode, String) log::debug!("Attempt to call reducer {reducer} with invalid arguments"); StatusCode::BAD_REQUEST } + ReducerCallError::InvalidPrepareId(_) => StatusCode::BAD_REQUEST, ReducerCallError::NoSuchModule(_) | ReducerCallError::ScheduleReducerNotFound => StatusCode::NOT_FOUND, ReducerCallError::NoSuchReducer => { log::debug!("Attempt to call non-existent reducer {reducer}"); @@ -133,6 +138,7 @@ fn map_procedure_error(e: ProcedureCallError, procedure: &str) -> (StatusCode, S pub async fn call( State(worker_ctx): State, Extension(auth): Extension, + headers: HeaderMap, Path(CallParams { name_or_identity, reducer, @@ -141,6 +147,10 @@ pub async fn call( body: Bytes, ) -> axum::response::Result { let caller_identity = auth.claims.identity; + let requested_tx_id = headers + .get(TX_ID_HEADER) + .and_then(|v| v.to_str().ok()) + .and_then(|s| s.parse::().ok()); let (args, want_bsatn) = parse_call_args(content_type, body)?; @@ -149,6 +159,9 @@ pub async fn call( let connection_id = generate_random_connection_id(); let (module, Database { owner_identity, .. }) = find_module_and_database(&worker_ctx, name_or_identity).await?; + let tx_id = requested_tx_id.unwrap_or_else(|| module.mint_global_tx_id(Timestamp::now())); + const MAX_WOUNDED_RETRIES: usize = 10; + const MAX_WOUNDED_BACKOFF: Duration = Duration::from_millis(1000); // Call the database's `client_connected` reducer, if any. // If it fails or rejects the connection, bail. @@ -158,34 +171,56 @@ pub async fn call( .map_err(client_connected_error_to_response)?; let mut reducer_return_value: Option = None; - let result = match module - .call_reducer_with_return( - caller_identity, - Some(connection_id), - None, - None, - None, - &reducer, - args.clone(), - ) - .await - { - Ok((rcr, return_value)) => { - reducer_return_value = return_value; - Ok(CallResult::Reducer(rcr)) - } - Err(ReducerCallError::NoSuchReducer | ReducerCallError::ScheduleReducerNotFound) => { - // Not a reducer — try procedure instead - match module - .call_procedure(caller_identity, Some(connection_id), None, &reducer, args) - .await - .result - { - Ok(res) => Ok(CallResult::Procedure(res)), - Err(e) => Err(map_procedure_error(e, &reducer)), + let mut tx_id = tx_id; + let mut wound_backoff = Duration::from_millis(10); + let result = loop { + match module + .call_reducer_with_return( + caller_identity, + Some(connection_id), + Some(tx_id), + None, + None, + None, + &reducer, + args.clone(), + ) + .await + { + Ok((rcr, return_value)) => { + if matches!(rcr.outcome, ReducerOutcome::Wounded(_)) { + if tx_id.attempt >= MAX_WOUNDED_RETRIES as u32 { + log::warn!("HTTP reducer call was wounded on final attempt. Returning error to caller."); + reducer_return_value = return_value; + break Ok(CallResult::Reducer(rcr)); + } + log::info!( + "HTTP reducer call was wounded on attempt {}, retrying after {:?} with new transaction ID {}", + tx_id.attempt, + wound_backoff, + tx_id + ); + sleep(wound_backoff).await; + wound_backoff = wound_backoff.mul_f32(2.0).min(MAX_WOUNDED_BACKOFF); + tx_id = tx_id.next_attempt(); + continue; + } + reducer_return_value = return_value; + break Ok(CallResult::Reducer(rcr)); + } + Err(ReducerCallError::NoSuchReducer | ReducerCallError::ScheduleReducerNotFound) => { + // Not a reducer — try procedure instead + break match module + .call_procedure(caller_identity, Some(connection_id), None, &reducer, args) + .await + .result + { + Ok(res) => Ok(CallResult::Procedure(res)), + Err(e) => Err(map_procedure_error(e, &reducer)), + }; } + Err(e) => break Err(map_reducer_error(e, &reducer)), } - Err(e) => Err(map_reducer_error(e, &reducer)), }; module @@ -269,6 +304,10 @@ pub async fn prepare( ) -> axum::response::Result { let args = parse_call_args(content_type, body)?; let caller_identity = auth.claims.identity; + let tx_id = headers + .get(TX_ID_HEADER) + .and_then(|v| v.to_str().ok()) + .and_then(|s| s.parse::().ok()); // The coordinator sends its actual database identity in `X-Coordinator-Identity`. // Without this, `anon_auth_middleware` gives the HTTP caller an ephemeral random @@ -277,14 +316,45 @@ pub async fn prepare( .get("X-Coordinator-Identity") .and_then(|v| v.to_str().ok()) .and_then(|s| spacetimedb_lib::Identity::from_hex(s).ok()); + let supplied_prepare_id = headers + .get(PREPARE_ID_HEADER) + .and_then(|v| v.to_str().ok()) + .map(str::to_owned); + if tx_id.is_some() && supplied_prepare_id.is_none() { + return Err(( + StatusCode::BAD_REQUEST, + format!("missing required {PREPARE_ID_HEADER} header for 2PC prepare request"), + ) + .into()); + } - let (module, Database { owner_identity, .. }) = find_module_and_database(&worker_ctx, name_or_identity).await?; + let ( + module, + Database { + owner_identity, + database_identity, + .. + }, + ) = find_module_and_database(&worker_ctx, name_or_identity).await?; + + WORKER_METRICS + .two_pc_prepare_calls_received_total + .with_label_values(&database_identity) + .inc(); // 2PC prepare is a server-to-server call; no client lifecycle management needed. // call_identity_connected/disconnected submit jobs to the module's executor, which // will be blocked holding the 2PC write lock after prepare_reducer returns — deadlock. let result = module - .prepare_reducer(caller_identity, None, &reducer, args.0, coordinator_identity) + .prepare_reducer( + caller_identity, + None, + tx_id, + &reducer, + args.0, + coordinator_identity, + supplied_prepare_id, + ) .await; match result { @@ -315,6 +385,12 @@ pub struct TwoPcParams { prepare_id: String, } +#[derive(Deserialize)] +pub struct GlobalTxParams { + name_or_identity: NameOrIdentity, + global_tx_id: String, +} + /// 2PC commit endpoint: finalize a prepared transaction. /// /// `POST /v1/database/:name_or_identity/2pc/commit/:prepare_id` @@ -373,7 +449,7 @@ pub async fn status_2pc( ) -> axum::response::Result { let (module, _database) = find_module_and_database(&worker_ctx, name_or_identity).await?; - let decision = if module.has_2pc_coordinator_commit(&prepare_id) { + let decision = if module.has_2pc_coordinator_commit(&prepare_id).await { "commit" } else { "abort" @@ -406,6 +482,35 @@ pub async fn ack_commit_2pc( Ok(StatusCode::OK) } +/// 2PC wound endpoint. +/// +/// `POST /v1/database/:name_or_identity/2pc/wound/:global_tx_id` +pub async fn wound_2pc( + State(worker_ctx): State, + Extension(_auth): Extension, + Path(GlobalTxParams { + name_or_identity, + global_tx_id, + }): Path, +) -> axum::response::Result { + let tx_id = global_tx_id + .parse::() + .map_err(|e| (StatusCode::BAD_REQUEST, e).into_response())?; + let (module, database) = find_module_and_database(&worker_ctx, name_or_identity).await?; + + log::info!( + "received 2PC wound request for transaction {tx_id} on database {}", + database.database_identity + ); + + module.wound_global_tx(tx_id).await.map_err(|e| { + log::warn!("2PC wound failed for {tx_id}: {e}"); + (StatusCode::NOT_FOUND, e).into_response() + })?; + + Ok(StatusCode::OK) +} + /// Encode a reducer return value as an HTTP response. /// /// If the outcome is an error, return a raw string with `application/text`. Ignore `want_bsatn` in this case. @@ -456,6 +561,7 @@ fn reducer_outcome_response( // TODO: different status code? this is what cloudflare uses, sorta Ok((StatusCode::from_u16(530).unwrap(), (*errmsg).into_response())) } + ReducerOutcome::Wounded(errmsg) => Ok((StatusCode::CONFLICT, (*errmsg).into_response())), ReducerOutcome::BudgetExceeded => { log::warn!("Node's energy budget exceeded for identity: {owner_identity} while executing {reducer}"); Ok(( @@ -1441,6 +1547,8 @@ pub struct DatabaseRoutes { pub commit_2pc_post: MethodRouter, /// POST: /database/:name_or_identity/2pc/abort/:prepare_id pub abort_2pc_post: MethodRouter, + /// POST: /database/:name_or_identity/2pc/wound/:global_tx_id + pub wound_2pc_post: MethodRouter, /// GET: /database/:name_or_identity/2pc/status/:prepare_id pub status_2pc_get: MethodRouter, /// POST: /database/:name_or_identity/2pc/ack-commit/:prepare_id @@ -1473,6 +1581,7 @@ where prepare_post: post(prepare::), commit_2pc_post: post(commit_2pc::), abort_2pc_post: post(abort_2pc::), + wound_2pc_post: post(wound_2pc::), status_2pc_get: get(status_2pc::), ack_commit_2pc_post: post(ack_commit_2pc::), } @@ -1503,6 +1612,7 @@ where .route("/prepare/:reducer", self.prepare_post) .route("/2pc/commit/:prepare_id", self.commit_2pc_post) .route("/2pc/abort/:prepare_id", self.abort_2pc_post) + .route("/2pc/wound/:global_tx_id", self.wound_2pc_post) .route("/2pc/status/:prepare_id", self.status_2pc_get) .route("/2pc/ack-commit/:prepare_id", self.ack_commit_2pc_post); diff --git a/crates/core/DISTRIBUTED-WOUND-WAIT.md b/crates/core/DISTRIBUTED-WOUND-WAIT.md new file mode 100644 index 00000000000..750c36a3dee --- /dev/null +++ b/crates/core/DISTRIBUTED-WOUND-WAIT.md @@ -0,0 +1,143 @@ +# Distributed Wound-Wait for 2PC Deadlock Breaking + +## Problem + +Distributed reducers can deadlock when one distributed transaction holds a participant lock on one database and waits on another database locked by a younger distributed transaction. Existing 2PC ensures atomic commit/abort, but it does not resolve distributed lock cycles. + +## Chosen Model + +- Use wound-wait. +- Transaction identity is `GlobalTxId`. +- `GlobalTxId.creator_db` is the authoritative coordinator database. +- Participant and coordinator runtime state are keyed by `GlobalTxId`. +- `prepare_id` remains a participant-local 2PC phase handle only. +- Older transactions wound younger transactions. +- Younger transactions wait behind older transactions. +- Lock acquisition is managed by an owner-aware scheduler that tracks running and pending `GlobalTxId`s. +- A wound RPC is required because a younger lock holder may belong to a distributed transaction coordinated on a different database. + +## Runtime Model + +- Add a distributed session registry keyed by `GlobalTxId`. +- Session tracks role, state, local `prepare_id`, coordinator identity, participants, and a local wounded/abort signal. +- Add a per-database async lock scheduler for distributed reducer write acquisition. +- Scheduler state includes current running owner, pending queue/set, and wounded marker for the current owner. +- Requesters await scheduler admission before reducer execution starts a local mutable transaction. + +## Wound Protocol + +Participant detecting conflict compares requester and owner by `GlobalTxId` ordering. + +- If requester is older: + - mark local owner as wounded + - send wound RPC to coordinator `GlobalTxId.creator_db` if needed + - keep requester pending until local owner releases +- If requester is younger: + - requester stays pending + +Wound RPC is idempotent and targets the distributed session at the coordinator. + +Coordinator receiving wound: + +- transitions session to `Aborting` +- sets wounded flag +- aborts local execution cooperatively +- fans out abort to known prepared participants + +## Safe Points + +- Before remote reducer calls +- Before PREPARE / COMMIT path work +- After reducer body returns, before expensive post-processing + +On safe-point wound detection: + +- rollback local tx +- unregister scheduler ownership +- wake waiters +- surface retryable `wounded` error + +## Compatibility + +- Keep existing `/2pc/commit`, `/2pc/abort`, `/2pc/status`, and ack-commit flows. +- Add a new wound endpoint. +- `/prepare` must propagate `GlobalTxId` the same way `/call` already does. +- No durable format change unless recovery work later proves it necessary. + +## Implementation Sequence + +### 1. Propagate `GlobalTxId` through 2PC prepare path + +- Update outgoing 2PC prepare requests to send `X-Spacetime-Tx-Id`. +- Update incoming `/prepare/:reducer` to parse `X-Spacetime-Tx-Id`. +- Thread `GlobalTxId` through `prepare_reducer` and any participant execution params. +- Ensure recovered/replayed participant work can recover or reconstruct the same session identity. + +### 2. Replace minimal prepared registry with `GlobalTxId` session registry + +- Extend the current prepared transaction registry into a session manager keyed by `GlobalTxId`. +- Track: + - role + - state + - local `prepare_id` + - participants + - coordinator identity + - wounded/abort signal +- Provide lookup by both `GlobalTxId` and `prepare_id`. + +### 3. Add distributed lock scheduler + +- Add an async scheduler in `core`, adjacent to reducer tx startup, not inside raw datastore locking. +- Track running owner and pending `GlobalTxId`s. +- Require distributed reducer write acquisition to await scheduler admission before blocking datastore acquisition. +- Implement wound-wait ordering and wakeup behavior there. + +### 4. Add wound RPC endpoint and coordinator handler + +- Add `POST /v1/database/:name_or_identity/2pc/wound/:global_tx_id`. +- Parse and route by `GlobalTxId.creator_db`. +- Coordinator session handler must: + - mark session `Aborting` + - set wounded flag + - begin participant abort fanout + - behave idempotently + +### 5. Add cooperative abort checks in reducer execution + +- Add wound checks at required safe points. +- On wound: + - rollback + - unregister running owner + - notify scheduler waiters + - surface retryable wounded error + +### 6. Integrate scheduler + wound with 2PC transitions + +- Ensure PREPARE, COMMIT, ABORT, and recovery all keep scheduler and session registry consistent. +- Make local owner release happen on all terminal paths. +- Keep participant recovery compatible with session state. + +### 7. Add tests + +- Scheduler ordering and wakeup tests +- Local same-database wound tests +- Distributed deadlock cycle tests +- Wound RPC idempotency tests +- Recovery tests for wounded prepared transactions +- Regression tests for existing 2PC success/failure flows + +## Acceptance Criteria + +- Distributed deadlock cycles are broken deterministically by wound-wait. +- Older distributed transactions eventually proceed without manual intervention. +- Younger distributed transactions abort globally, not just locally. +- `/prepare` and `/call` both carry `GlobalTxId`. +- Existing 2PC happy paths continue to pass. +- Repeated wound or abort requests are safe and idempotent. + +## Assumptions + +- `GlobalTxId.creator_db` is always the coordinator database. +- `GlobalTxId` ordering is the authoritative age/tie-break rule. +- Cooperative abort at safe points is sufficient for v1; no preemptive interruption is required. +- Lock scheduler state is in-memory runtime state, not durable state. diff --git a/crates/core/src/client/client_connection.rs b/crates/core/src/client/client_connection.rs index 6fb8d8e1623..ebae782e949 100644 --- a/crates/core/src/client/client_connection.rs +++ b/crates/core/src/client/client_connection.rs @@ -14,7 +14,7 @@ use crate::error::DBError; use crate::host::module_host::ClientConnectedError; use crate::host::{ CallProcedureReturn, FunctionArgs, ModuleHost, NoSuchModule, ProcedureCallResult, ReducerCallError, - ReducerCallResult, + ReducerCallResult, ReducerOutcome, }; use crate::subscription::module_subscription_manager::BroadcastError; use crate::subscription::row_list_builder_pool::JsonRowListBuilderFakePool; @@ -32,9 +32,11 @@ use spacetimedb_durability::{DurableOffset, TxOffset}; use spacetimedb_lib::identity::{AuthCtx, RequestId}; use spacetimedb_lib::metrics::ExecutionMetrics; use spacetimedb_lib::{bsatn, Identity, TimeDuration, Timestamp}; +use std::time::Duration; use tokio::sync::mpsc::error::{SendError, TrySendError}; use tokio::sync::{mpsc, oneshot, watch}; use tokio::task::AbortHandle; +use tokio::time::sleep; use tracing::{trace, warn}; #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] @@ -852,6 +854,7 @@ impl ClientConnection { .call_reducer( self.id.identity, Some(self.id.connection_id), + None, caller, Some(request_id), Some(timer), @@ -869,17 +872,42 @@ impl ClientConnection { timer: Instant, _flags: ws_v2::CallReducerFlags, ) -> Result { - self.module() - .call_reducer( - self.id.identity, - Some(self.id.connection_id), - Some(self.sender()), - Some(request_id), - Some(timer), - reducer, - FunctionArgs::Bsatn(args), - ) - .await + const MAX_WOUNDED_RETRIES: usize = 10; + const MAX_BACKOFF: Duration = Duration::from_millis(100); + + let module = self.module(); + let mut tx_id = module.replica_ctx().mint_global_tx_id(Timestamp::now()); + let mut wound_backoff = Duration::from_millis(10); + + for attempt in 0..=MAX_WOUNDED_RETRIES { + let result = module + .call_reducer( + self.id.identity, + Some(self.id.connection_id), + Some(tx_id), + Some(self.sender()), + Some(request_id), + Some(timer), + reducer, + FunctionArgs::Bsatn(args.clone()), + ) + .await?; + + if !matches!(result.outcome, ReducerOutcome::Wounded(_)) || attempt == MAX_WOUNDED_RETRIES { + if attempt == MAX_WOUNDED_RETRIES && matches!(result.outcome, ReducerOutcome::Wounded(_)) { + log::warn!("Reducer call was wounded on final attempt. Returning error to client."); + } + return Ok(result); + } + + log::info!("Reducer call was wounded on attempt {attempt}, retrying after {wound_backoff:?} with new transaction ID {tx_id}"); + sleep(wound_backoff).await; + wound_backoff = wound_backoff.mul_f32(2.0).min(MAX_BACKOFF); + + tx_id = tx_id.next_attempt(); + } + + unreachable!("retry loop should return before exhausting attempts") } pub async fn call_procedure( diff --git a/crates/core/src/client/messages.rs b/crates/core/src/client/messages.rs index ed65e092d0e..f85eaac2410 100644 --- a/crates/core/src/client/messages.rs +++ b/crates/core/src/client/messages.rs @@ -379,9 +379,9 @@ impl ToProtocol for TransactionUpdateMessage { let status = match &event.status { EventStatus::Committed(_) => ws_v1::UpdateStatus::Committed(update), - EventStatus::FailedUser(errmsg) | EventStatus::FailedInternal(errmsg) => { - ws_v1::UpdateStatus::Failed(errmsg.clone().into()) - } + EventStatus::FailedUser(errmsg) + | EventStatus::FailedInternal(errmsg) + | EventStatus::Wounded(errmsg) => ws_v1::UpdateStatus::Failed(errmsg.clone().into()), EventStatus::OutOfEnergy => ws_v1::UpdateStatus::OutOfEnergy, }; diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index 5eec2eaf4e3..0d57890fc49 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -136,6 +136,8 @@ pub struct ConfigFile { pub logs: LogConfig, #[serde(default)] pub v8_heap_policy: V8HeapPolicyConfig, + #[serde(default)] + pub global_tx: GlobalTxConfig, } impl ConfigFile { @@ -189,6 +191,21 @@ pub struct V8HeapPolicyConfig { pub heap_limit_bytes: Option, } +#[derive(Clone, Copy, Debug, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct GlobalTxConfig { + #[serde(default = "default_wound_grace_period", deserialize_with = "de_duration")] + pub wound_grace_period: Duration, +} + +impl Default for GlobalTxConfig { + fn default() -> Self { + Self { + wound_grace_period: default_wound_grace_period(), + } + } +} + impl Default for V8HeapPolicyConfig { fn default() -> Self { Self { @@ -237,6 +254,10 @@ fn def_retire() -> f64 { 0.75 } +fn default_wound_grace_period() -> Duration { + Duration::from_millis(10) +} + fn de_nz_u64<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, @@ -264,6 +285,23 @@ where Ok((!duration.is_zero()).then_some(duration)) } +fn de_duration<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + #[derive(serde::Deserialize)] + #[serde(untagged)] + enum DurationValue { + String(String), + Seconds(u64), + } + + match DurationValue::deserialize(deserializer)? { + DurationValue::String(value) => humantime::parse_duration(&value).map_err(serde::de::Error::custom), + DurationValue::Seconds(value) => Ok(Duration::from_secs(value)), + } +} + fn de_fraction<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -445,4 +483,21 @@ mod tests { assert_eq!(config.v8_heap_policy.heap_retire_fraction, 0.8); assert_eq!(config.v8_heap_policy.heap_limit_bytes, Some(256 * 1024 * 1024)); } + + #[test] + fn global_tx_defaults_when_omitted() { + let config: ConfigFile = toml::from_str("").unwrap(); + assert_eq!(config.global_tx.wound_grace_period, Duration::from_millis(10)); + } + + #[test] + fn global_tx_parses_from_toml() { + let toml = r#" + [global-tx] + wound-grace-period = "25ms" + "#; + + let config: ConfigFile = toml::from_str(toml).unwrap(); + assert_eq!(config.global_tx.wound_grace_period, Duration::from_millis(25)); + } } diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 226c2700d08..4b0f85bf70d 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -275,6 +275,8 @@ pub enum NodesError { BadIndexType(u8), #[error("Failed to scheduled timer: {0}")] ScheduleError(#[source] ScheduleError), + #[error("Distributed transaction wounded: {0}")] + Wounded(String), #[error("HTTP request failed: {0}")] HttpError(String), } diff --git a/crates/core/src/host/global_tx.rs b/crates/core/src/host/global_tx.rs new file mode 100644 index 00000000000..3605d8c45fe --- /dev/null +++ b/crates/core/src/host/global_tx.rs @@ -0,0 +1,930 @@ +use crate::identity::Identity; +use crate::worker_metrics::WORKER_METRICS; +use spacetimedb_lib::GlobalTxId; +use std::cmp::Ordering as CmpOrdering; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::future::Future; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use tokio::sync::{watch, Notify}; + +const DEFAULT_WOUND_GRACE_PERIOD: Duration = Duration::from_millis(10); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GlobalTxRole { + Coordinator, + Participant, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GlobalTxState { + Running, + Preparing, + Prepared, + Aborting, + Aborted, + Committing, + Committed, +} + +#[derive(Debug)] +pub struct GlobalTxSession { + pub tx_id: GlobalTxId, + pub role: GlobalTxRole, + pub coordinator_identity: Identity, + wounded: AtomicBool, + wounded_tx: watch::Sender, + state: Mutex, + prepare_id: Mutex>, + participants: Mutex>, +} + +impl GlobalTxSession { + fn new(tx_id: GlobalTxId, role: GlobalTxRole, coordinator_identity: Identity) -> Self { + let (wounded_tx, _) = watch::channel(false); + Self { + tx_id, + role, + coordinator_identity, + wounded: AtomicBool::new(false), + wounded_tx, + state: Mutex::new(GlobalTxState::Running), + prepare_id: Mutex::new(None), + participants: Mutex::new(Vec::new()), + } + } + + pub fn is_wounded(&self) -> bool { + self.wounded.load(Ordering::SeqCst) + } + + pub fn wound(&self) -> bool { + let was_fresh = !self.wounded.swap(true, Ordering::SeqCst); + if was_fresh { + let _ = self.wounded_tx.send(true); + } + was_fresh + } + + pub fn subscribe_wounded(&self) -> watch::Receiver { + self.wounded_tx.subscribe() + } + + pub fn state(&self) -> GlobalTxState { + *self.state.lock().unwrap() + } + + pub fn set_state(&self, state: GlobalTxState) { + *self.state.lock().unwrap() = state; + } + + pub fn set_prepare_id(&self, prepare_id: Option) { + *self.prepare_id.lock().unwrap() = prepare_id; + } + + pub fn prepare_id(&self) -> Option { + self.prepare_id.lock().unwrap().clone() + } + + pub fn add_participant(&self, db_identity: Identity, prepare_id: String) { + self.participants.lock().unwrap().push((db_identity, prepare_id)); + } + + pub fn participants(&self) -> Vec<(Identity, String)> { + self.participants.lock().unwrap().clone() + } +} + +struct LockState { + owner: Option, + // An set of waiters ordered by tx_id with the oldest first. + waiting: BTreeSet, + // A map from wait_id to the corresponding wait entry, which contains the notify object to wake up the waiter when its turn comes. + wait_entries: HashMap, + waiter_ids_by_tx: HashMap, + wounded_owners: HashSet, + next_wait_id: u64, +} + +impl Default for LockState { + fn default() -> Self { + Self { + owner: None, + waiting: BTreeSet::new(), + wait_entries: HashMap::new(), + waiter_ids_by_tx: HashMap::new(), + wounded_owners: HashSet::new(), + next_wait_id: 1, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct WaitKey { + tx_id: GlobalTxId, + wait_id: u64, +} + +impl Ord for WaitKey { + fn cmp(&self, other: &Self) -> CmpOrdering { + self.tx_id + .cmp(&other.tx_id) + .then_with(|| self.wait_id.cmp(&other.wait_id)) + } +} + +impl PartialOrd for WaitKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Debug)] +struct WaitEntry { + tx_id: GlobalTxId, + notify: Arc, +} + +pub enum AcquireDisposition { + Acquired(GlobalTxLockGuard), + Cancelled, +} + +pub struct GlobalTxLockGuard { + manager: Arc, + tx_id: Option, +} + +struct WaitRegistration<'a> { + manager: &'a GlobalTxManager, + wait_id: Option, +} + +impl<'a> WaitRegistration<'a> { + fn new(manager: &'a GlobalTxManager, wait_id: u64) -> Self { + Self { + manager, + wait_id: Some(wait_id), + } + } + + fn wait_id(&self) -> u64 { + self.wait_id.expect("registered waiter must still have a wait id") + } + + fn disarm(mut self, ls: &mut std::sync::MutexGuard<'_, LockState>) { + self.remove_waiter(ls); + } + + fn remove_waiter(&mut self, ls: &mut LockState) { + if let Some(wait_id) = self.wait_id.take() { + self.manager.remove_waiter_by_id(ls, wait_id); + } + } +} + +impl Drop for WaitRegistration<'_> { + fn drop(&mut self) { + if self.wait_id.is_none() { + return; + } + let mut ls = self.manager.lock_state.lock().unwrap(); + self.remove_waiter(&mut ls); + } +} + +impl GlobalTxLockGuard { + fn new(manager: Arc, tx_id: GlobalTxId) -> Self { + Self { + manager, + tx_id: Some(tx_id), + } + } + + pub fn tx_id(&self) -> GlobalTxId { + self.tx_id.expect("lock guard must always have a tx_id before drop") + } +} + +impl Drop for GlobalTxLockGuard { + fn drop(&mut self) { + if let Some(tx_id) = self.tx_id.take() { + self.manager.release(&tx_id); + } + } +} + +pub struct GlobalTxManager { + sessions: Mutex>>, + prepare_to_tx: Mutex>, + lock_state: Mutex, + wound_grace_period: Duration, +} + +impl Default for GlobalTxManager { + fn default() -> Self { + Self::new(DEFAULT_WOUND_GRACE_PERIOD) + } +} + +impl GlobalTxManager { + fn session_metric_labels(&self, tx_id: &GlobalTxId) -> Option<(Identity, &'static str)> { + let session = self.get_session(tx_id)?; + let role = match session.role { + GlobalTxRole::Coordinator => "coordinator", + GlobalTxRole::Participant => "participant", + }; + Some((session.coordinator_identity, role)) + } + + pub fn new(wound_grace_period: Duration) -> Self { + Self { + sessions: Mutex::default(), + prepare_to_tx: Mutex::default(), + lock_state: Mutex::default(), + wound_grace_period, + } + } + + pub fn wound_grace_period(&self) -> Duration { + self.wound_grace_period + } + + pub fn ensure_session( + &self, + tx_id: GlobalTxId, + role: GlobalTxRole, + coordinator_identity: Identity, + ) -> Arc { + let mut sessions = self.sessions.lock().unwrap(); + sessions + .entry(tx_id) + .or_insert_with(|| Arc::new(GlobalTxSession::new(tx_id, role, coordinator_identity))) + .clone() + } + + pub fn get_session(&self, tx_id: &GlobalTxId) -> Option> { + self.sessions.lock().unwrap().get(tx_id).cloned() + } + + pub fn remove_session(&self, tx_id: &GlobalTxId) { + self.sessions.lock().unwrap().remove(tx_id); + } + + pub fn tx_for_prepare(&self, prepare_id: &str) -> Option { + self.prepare_to_tx.lock().unwrap().get(prepare_id).copied() + } + + pub fn set_prepare_mapping(&self, tx_id: GlobalTxId, prepare_id: String) { + self.prepare_to_tx.lock().unwrap().insert(prepare_id.clone(), tx_id); + if let Some(session) = self.get_session(&tx_id) { + session.set_prepare_id(Some(prepare_id)); + } + } + + pub fn remove_prepare_mapping(&self, prepare_id: &str) -> Option { + let tx_id = self.prepare_to_tx.lock().unwrap().remove(prepare_id); + if let Some(tx_id) = tx_id + && let Some(session) = self.get_session(&tx_id) + { + session.set_prepare_id(None); + } + tx_id + } + + pub fn add_participant(&self, tx_id: GlobalTxId, db_identity: Identity, prepare_id: String) { + if let Some(session) = self.get_session(&tx_id) { + session.add_participant(db_identity, prepare_id); + } + } + + pub fn mark_state(&self, tx_id: &GlobalTxId, state: GlobalTxState) { + if let Some(session) = self.get_session(tx_id) { + session.set_state(state); + } + } + + pub fn is_wounded(&self, tx_id: &GlobalTxId) -> bool { + self.get_session(tx_id).map(|s| s.is_wounded()).unwrap_or(false) + } + + pub fn subscribe_wounded(&self, tx_id: &GlobalTxId) -> Option> { + self.get_session(tx_id).map(|s| s.subscribe_wounded()) + } + + // This should only be called by the coordinator. + // Arguably we should have a separate state for wounded and aborted, in case we wound a remote tx before we send write the prepare. + pub fn wound(&self, tx_id: &GlobalTxId) -> Option> { + let session = self.get_session(tx_id)?; + let was_fresh = session.wound(); + if !matches!(session.state(), GlobalTxState::Committed | GlobalTxState::Aborted) { + session.set_state(GlobalTxState::Aborting); + } + if was_fresh { + let role = match session.role { + GlobalTxRole::Coordinator => "coordinator", + GlobalTxRole::Participant => "participant", + }; + log::info!( + "global transaction {tx_id} marked wounded; role={:?} coordinator={}", + session.role, + session.coordinator_identity + ); + WORKER_METRICS + .transactions_wounded_total + .with_label_values(&session.coordinator_identity, &role) + .inc(); + } + Some(session) + } + + pub async fn acquire(self: &Arc, tx_id: GlobalTxId, mut on_wound: F) -> AcquireDisposition + where + F: FnMut(GlobalTxId) -> Fut, + Fut: Future + Send + 'static, + { + let mut wounded_rx = match self.subscribe_wounded(&tx_id) { + Some(rx) => rx, + None => return AcquireDisposition::Cancelled, + }; + let mut registration: Option> = None; + loop { + if *wounded_rx.borrow() { + return AcquireDisposition::Cancelled; + } + if self.is_terminalish(&tx_id) { + return AcquireDisposition::Cancelled; + } + + let (notify, owner_to_wound, new_registration, cancelled): ( + Arc, + Option, + Option>, + bool, + ) = { + let mut state = self.lock_state.lock().unwrap(); + if state.owner.is_none() { + self.prune_stale_head_waiters_locked(&mut state); + } + match state.owner { + None if self.is_next_waiter_locked(&state, tx_id) => { + log::info!("setting owner to {tx_id}"); + state.owner = Some(tx_id); + self.remove_waiter_locked(&mut state, &tx_id); + if let Some(registration) = registration.take() { + registration.disarm(&mut state); + } + log::info!("global transaction {tx_id} acquired the lock"); + return AcquireDisposition::Acquired(GlobalTxLockGuard::new(self.clone(), tx_id)); + } + None => { + let waiter = match registration.as_ref() { + Some(registration) => match self.registered_waiter_locked(&state, tx_id, registration) { + Ok(registered_waiter) => Some(registered_waiter), + Err(()) => None, + }, + None => Some(self.ensure_waiter_locked(&mut state, tx_id)), + }; + let Some((wait_id, notify)) = waiter else { + return AcquireDisposition::Cancelled; + }; + let head_waiter = state.waiting.first().map(|wait_key| wait_key.tx_id); + if let Some(head_waiter) = head_waiter + && head_waiter != tx_id + { + log::info!( + "global transaction {tx_id} observed ownerless lock while queued behind head waiter {head_waiter}; nudging head waiter" + ); + self.notify_next_waiter_locked(&state); + } + + log::info!( + "global transaction {tx_id} is waiting for the lock; no current owner; head waiter: {:?}", + head_waiter + ); + let new_registration = registration.is_none().then(|| WaitRegistration::new(self, wait_id)); + (notify, None, new_registration, false) + } + Some(owner) if owner == tx_id => { + log::warn!("global transaction {tx_id} is trying to acquire the lock it already holds. This should not happen and may indicate a bug in the caller's logic, but we'll allow it to proceed without deadlocking on itself."); + self.remove_waiter_locked(&mut state, &tx_id); + if let Some(registration) = registration.take() { + registration.disarm(&mut state); + } + return AcquireDisposition::Acquired(GlobalTxLockGuard::new(self.clone(), tx_id)); + } + Some(owner) => { + let waiter = match registration.as_ref() { + Some(registration) => match self.registered_waiter_locked(&state, tx_id, registration) { + Ok(registered_waiter) => Some(registered_waiter), + Err(()) => None, + }, + None => Some(self.ensure_waiter_locked(&mut state, tx_id)), + }; + let Some((wait_id, notify)) = waiter else { + return AcquireDisposition::Cancelled; + }; + let owner_to_wound = (tx_id < owner && state.wounded_owners.insert(owner)).then_some(owner); + let new_registration = registration.is_none().then(|| WaitRegistration::new(self, wait_id)); + (notify, owner_to_wound, new_registration, false) + } + } + }; + if cancelled { + return AcquireDisposition::Cancelled; + } + if let Some(new_registration) = new_registration { + registration = Some(new_registration); + } + + if let Some(owner) = owner_to_wound { + if let Some((coordinator_identity, role)) = self.session_metric_labels(&tx_id) { + WORKER_METRICS + .global_tx_waiting_on_younger_owner_total + .with_label_values(&coordinator_identity, &role) + .inc(); + } + let wound_grace_period = self.wound_grace_period; + log::info!( + "global transaction {tx_id} is waiting behind younger owner {owner}; giving it {:?} to finish before wound flow", + wound_grace_period + ); + let owner_finished = tokio::select! { + changed = wounded_rx.changed(), if !*wounded_rx.borrow() => { + if changed.is_ok() && *wounded_rx.borrow() { + return AcquireDisposition::Cancelled; + } + false + } + _ = notify.notified() => true, + _ = tokio::time::sleep(wound_grace_period) => false, + }; + if owner_finished { + if let Some((coordinator_identity, role)) = self.session_metric_labels(&tx_id) { + WORKER_METRICS + .global_tx_younger_owner_finished_within_grace_period_total + .with_label_values(&coordinator_identity, &role) + .inc(); + } + log::info!("global transaction {tx_id} observed owner {owner} finish within grace period; not triggering wound",); + continue; + } + + let should_trigger_wound = { + let state = self.lock_state.lock().unwrap(); + state.owner == Some(owner) + }; + if should_trigger_wound { + log::info!( + "global transaction {tx_id} is still waiting behind younger owner {owner} after {:?}; triggering wound flow", + wound_grace_period + ); + if self.should_wound_locally(&owner) { + let _ = self.wound(&owner); + } else { + log::info!( + "global transaction {tx_id} observed prepared participant owner {owner}; notifying coordinator without local wound" + ); + } + tokio::spawn(on_wound(owner)); + } + } + + tokio::select! { + changed = wounded_rx.changed(), if !*wounded_rx.borrow() => { + if changed.is_ok() && *wounded_rx.borrow() { + return AcquireDisposition::Cancelled; + } + } + _ = notify.notified() => { + log::info!( + "global transaction {tx_id} was notified of a potential lock availability change; re-checking lock state" + ); + } + } + } + } + + pub fn release(&self, tx_id: &GlobalTxId) { + let mut state = self.lock_state.lock().unwrap(); + if state.owner.as_ref() == Some(tx_id) { + log::info!("Releasing lock for tx_id {}", tx_id); + state.owner = None; + state.wounded_owners.remove(tx_id); + self.notify_next_waiter_locked(&state); + } else { + log::warn!("Release a lock that isn't actually held. This should not happen"); + } + self.remove_waiter_locked(&mut state, tx_id); + } + + fn ensure_waiter_locked(&self, state: &mut LockState, tx_id: GlobalTxId) -> (u64, Arc) { + if let Some(wait_id) = state.waiter_ids_by_tx.get(&tx_id).copied() { + let notify = state + .wait_entries + .get(&wait_id) + .expect("wait entry must exist for registered waiter") + .notify + .clone(); + return (wait_id, notify); + } + + let wait_id = state.next_wait_id; + state.next_wait_id += 1; + let notify = Arc::new(Notify::new()); + state.wait_entries.insert( + wait_id, + WaitEntry { + tx_id, + notify: notify.clone(), + }, + ); + state.waiter_ids_by_tx.insert(tx_id, wait_id); + state.waiting.insert(WaitKey { tx_id, wait_id }); + (wait_id, notify) + } + + fn registered_waiter_locked( + &self, + state: &LockState, + tx_id: GlobalTxId, + registration: &WaitRegistration<'_>, + ) -> Result<(u64, Arc), ()> { + let wait_id = registration.wait_id(); + if let Some(wait_entry) = state.wait_entries.get(&wait_id) { + return Ok((wait_id, wait_entry.notify.clone())); + } + + log::warn!( + "global transaction {tx_id} lost its waiter registration while still waiting; treating acquire as cancelled" + ); + Err(()) + } + + fn is_next_waiter_locked(&self, state: &LockState, tx_id: GlobalTxId) -> bool { + match state.waiting.first() { + None => true, + Some(wait_key) => wait_key.tx_id == tx_id, + } + } + + fn notify_next_waiter_locked(&self, state: &LockState) { + if let Some(wait_key) = state.waiting.first() + && let Some(wait_entry) = state.wait_entries.get(&wait_key.wait_id) + { + log::info!("Notifying next waiter for tx_id {}", wait_entry.tx_id); + wait_entry.notify.notify_one(); + } + } + + fn remove_waiter_locked(&self, state: &mut LockState, tx_id: &GlobalTxId) { + if let Some(wait_id) = state.waiter_ids_by_tx.remove(tx_id) { + state.wait_entries.remove(&wait_id); + state.waiting.remove(&WaitKey { tx_id: *tx_id, wait_id }); + } + } + + fn remove_waiter_by_id(&self, state: &mut LockState, wait_id: u64) { + log::info!("Removing waiter with wait_id {}", wait_id); + let was_head = state.waiting.first().map(|w| w.wait_id) == Some(wait_id); + if let Some(wait_entry) = state.wait_entries.remove(&wait_id) { + log::info!("Removing waiter with wait_id {}, tx_id {}", wait_id, wait_entry.tx_id); + state.waiter_ids_by_tx.remove(&wait_entry.tx_id); + state.waiting.remove(&WaitKey { + tx_id: wait_entry.tx_id, + wait_id, + }); + if was_head && state.owner.is_none() { + self.notify_next_waiter_locked(state); + } + } else { + log::warn!("Trying to remove non-existent waiter with wait_id {}, current_owner: {:?}", wait_id, state.owner); + } + } + + fn should_wound_locally(&self, tx_id: &GlobalTxId) -> bool { + self.get_session(tx_id) + .map(|session| !(session.role == GlobalTxRole::Participant && session.state() == GlobalTxState::Prepared)) + .unwrap_or(true) + } + + fn is_terminalish(&self, tx_id: &GlobalTxId) -> bool { + let Some(session) = self.get_session(tx_id) else { + return true; + }; + session.is_wounded() + || matches!( + session.state(), + GlobalTxState::Committed | GlobalTxState::Aborted | GlobalTxState::Aborting + ) + } + + fn prune_stale_head_waiters_locked(&self, state: &mut LockState) { + while let Some(wait_key) = state.waiting.first().copied() { + let tx_id = wait_key.tx_id; + if self.is_terminalish(&tx_id) { + let session_state = self.get_session(&tx_id).map(|session| session.state()); + let wounded = self.is_wounded(&tx_id); + log::warn!( + "pruning stale head waiter {tx_id}: state={session_state:?} wounded={wounded} while owner is None" + ); + self.remove_waiter_by_id(state, wait_key.wait_id); + continue; + } + break; + } + } +} + +#[cfg(test)] +mod tests { + use super::{AcquireDisposition, GlobalTxManager}; + use crate::identity::Identity; + use spacetimedb_lib::{GlobalTxId, Timestamp}; + use std::sync::atomic::Ordering; + use std::sync::Arc; + use std::time::Duration; + use tokio::runtime::Runtime; + + fn tx_id(ts: i64, db_byte: u8, nonce: u32) -> GlobalTxId { + GlobalTxId::new( + Timestamp::from_micros_since_unix_epoch(ts), + Identity::from_byte_array([db_byte; 32]), + nonce, + 0, + ) + } + + #[test] + fn manager_uses_configured_wound_grace_period() { + let manager = GlobalTxManager::new(Duration::from_millis(42)); + assert_eq!(manager.wound_grace_period(), Duration::from_millis(42)); + } + + #[test] + fn older_requester_wounds_younger_owner() { + let manager = Arc::new(GlobalTxManager::default()); + let younger = tx_id(20, 2, 0); + let older = tx_id(10, 1, 0); + manager.ensure_session(younger, super::GlobalTxRole::Participant, younger.creator_db); + manager.ensure_session(older, super::GlobalTxRole::Participant, older.creator_db); + + let rt = Runtime::new().unwrap(); + let younger_guard = match rt.block_on(manager.acquire(younger, |_| async {})) { + AcquireDisposition::Acquired(guard) => guard, + AcquireDisposition::Cancelled => panic!("younger tx should acquire immediately"), + }; + + let manager_for_task = manager.clone(); + let older_task = rt.spawn(async move { + match manager_for_task.acquire(older, |_| async {}).await { + AcquireDisposition::Acquired(_guard) => true, + AcquireDisposition::Cancelled => false, + } + }); + std::thread::sleep(Duration::from_millis(25)); + assert!(manager.is_wounded(&younger)); + drop(younger_guard); + assert!(matches!(rt.block_on(older_task).expect("task should complete"), true)); + } + + #[test] + fn younger_owner_finishing_within_grace_period_is_not_wounded() { + let manager = Arc::new(GlobalTxManager::default()); + let younger = tx_id(20, 2, 0); + let older = tx_id(10, 1, 0); + manager.ensure_session(younger, super::GlobalTxRole::Participant, younger.creator_db); + manager.ensure_session(older, super::GlobalTxRole::Participant, older.creator_db); + + let rt = Runtime::new().unwrap(); + let younger_guard = match rt.block_on(manager.acquire(younger, |_| async {})) { + AcquireDisposition::Acquired(guard) => guard, + AcquireDisposition::Cancelled => panic!("younger tx should acquire immediately"), + }; + + let manager_for_task = manager.clone(); + let older_task = rt.spawn(async move { + match manager_for_task.acquire(older, |_| async {}).await { + AcquireDisposition::Acquired(_guard) => true, + AcquireDisposition::Cancelled => false, + } + }); + + std::thread::sleep(Duration::from_millis(5)); + drop(younger_guard); + + assert!(matches!(rt.block_on(older_task).expect("task should complete"), true)); + assert!(!manager.is_wounded(&younger)); + } + + #[test] + fn younger_requester_waits_behind_older_owner() { + let manager = Arc::new(GlobalTxManager::default()); + let older = tx_id(10, 1, 0); + let younger = tx_id(20, 2, 0); + manager.ensure_session(older, super::GlobalTxRole::Participant, older.creator_db); + manager.ensure_session(younger, super::GlobalTxRole::Participant, younger.creator_db); + let rt = Runtime::new().unwrap(); + + let older_guard = match rt.block_on(manager.acquire(older, |_| async {})) { + AcquireDisposition::Acquired(guard) => guard, + AcquireDisposition::Cancelled => panic!("older tx should acquire immediately"), + }; + let wait = rt.block_on(async { + tokio::time::timeout(Duration::from_millis(25), manager.acquire(younger, |_| async {})).await + }); + assert!(wait.is_err()); + drop(older_guard); + } + + #[test] + fn waiter_acquires_after_release() { + let manager = Arc::new(GlobalTxManager::default()); + let owner = tx_id(10, 1, 0); + let waiter = tx_id(20, 2, 0); + manager.ensure_session(owner, super::GlobalTxRole::Participant, owner.creator_db); + manager.ensure_session(waiter, super::GlobalTxRole::Participant, waiter.creator_db); + let rt = Runtime::new().unwrap(); + + let owner_guard = match rt.block_on(manager.acquire(owner, |_| async {})) { + AcquireDisposition::Acquired(guard) => guard, + AcquireDisposition::Cancelled => panic!("owner should acquire immediately"), + }; + + let manager_for_thread = manager.clone(); + let handle = std::thread::spawn(move || { + let rt = Runtime::new().unwrap(); + match rt.block_on(manager_for_thread.acquire(waiter, |_| async {})) { + AcquireDisposition::Acquired(_guard) => {} + AcquireDisposition::Cancelled => panic!("waiter should acquire after release"), + } + }); + + std::thread::sleep(Duration::from_millis(25)); + drop(owner_guard); + handle.join().unwrap(); + } + + #[test] + fn wound_is_idempotent() { + let manager = Arc::new(GlobalTxManager::default()); + let tx_id = tx_id(10, 1, 0); + let session = manager.ensure_session(tx_id, super::GlobalTxRole::Coordinator, tx_id.creator_db); + + assert!(!session.is_wounded()); + assert!(manager.wound(&tx_id).is_some()); + assert!(session.is_wounded()); + assert!(manager.wound(&tx_id).is_some()); + assert!(session.is_wounded()); + } + + #[test] + fn wound_subscription_notifies_waiter() { + let manager = Arc::new(GlobalTxManager::default()); + let tx_id = tx_id(10, 1, 0); + let _session = manager.ensure_session(tx_id, super::GlobalTxRole::Coordinator, tx_id.creator_db); + let mut wounded_rx = manager.subscribe_wounded(&tx_id).expect("session should exist"); + + let rt = Runtime::new().unwrap(); + rt.block_on(async { + let notifier = async { + if !*wounded_rx.borrow() { + wounded_rx.changed().await.expect("sender should still exist"); + } + *wounded_rx.borrow() + }; + + let trigger = async { + tokio::time::sleep(Duration::from_millis(10)).await; + manager.wound(&tx_id).expect("session should still exist"); + }; + + let (wounded, ()) = tokio::join!(notifier, trigger); + assert!(wounded); + }); + } + + #[test] + fn prepared_participant_only_signals_coordinator() { + let manager = Arc::new(GlobalTxManager::default()); + let owner = tx_id(20, 2, 0); + let older = tx_id(10, 1, 0); + let owner_session = manager.ensure_session(owner, super::GlobalTxRole::Participant, owner.creator_db); + owner_session.set_state(super::GlobalTxState::Prepared); + manager.ensure_session(older, super::GlobalTxRole::Participant, older.creator_db); + + let rt = Runtime::new().unwrap(); + let owner_guard = match rt.block_on(manager.acquire(owner, |_| async {})) { + AcquireDisposition::Acquired(guard) => guard, + AcquireDisposition::Cancelled => panic!("owner should acquire immediately"), + }; + + let coordinator_wounded = Arc::new(std::sync::atomic::AtomicBool::new(false)); + let (wound_tx, wound_rx) = std::sync::mpsc::channel(); + let flag = coordinator_wounded.clone(); + let manager_for_task = manager.clone(); + let older_task = rt.spawn(async move { + match manager_for_task + .acquire(older, move |_| { + let flag = flag.clone(); + let wound_tx = wound_tx.clone(); + async move { + flag.store(true, Ordering::SeqCst); + let _ = wound_tx.send(()); + } + }) + .await + { + AcquireDisposition::Acquired(_guard) => true, + AcquireDisposition::Cancelled => false, + } + }); + + wound_rx + .recv_timeout(Duration::from_millis(50)) + .expect("coordinator should be notified"); + assert!(coordinator_wounded.load(Ordering::SeqCst)); + assert!(!manager.is_wounded(&owner)); + drop(owner_guard); + assert!(rt.block_on(older_task).expect("task should complete")); + } + + #[test] + fn wounded_waiter_is_cancelled() { + let manager = Arc::new(GlobalTxManager::default()); + let owner = tx_id(10, 1, 0); + let waiter = tx_id(20, 2, 0); + manager.ensure_session(owner, super::GlobalTxRole::Participant, owner.creator_db); + manager.ensure_session(waiter, super::GlobalTxRole::Participant, waiter.creator_db); + + let rt = Runtime::new().unwrap(); + let owner_guard = match rt.block_on(manager.acquire(owner, |_| async {})) { + AcquireDisposition::Acquired(guard) => guard, + AcquireDisposition::Cancelled => panic!("owner should acquire immediately"), + }; + + let manager_for_task = manager.clone(); + let waiter_task = rt.spawn(async move { + matches!( + manager_for_task.acquire(waiter, |_| async {}).await, + AcquireDisposition::Cancelled + ) + }); + std::thread::sleep(Duration::from_millis(10)); + manager.wound(&waiter).expect("waiter session should exist"); + drop(owner_guard); + + assert!(matches!(rt.block_on(waiter_task).expect("task should complete"), true)); + } + + #[test] + fn pruned_waiter_is_cancelled_instead_of_panicking() { + let manager = Arc::new(GlobalTxManager::default()); + let owner = tx_id(20, 2, 0); + let waiter = tx_id(10, 1, 0); + manager.ensure_session(owner, super::GlobalTxRole::Participant, owner.creator_db); + manager.ensure_session(waiter, super::GlobalTxRole::Participant, waiter.creator_db); + + let rt = Runtime::new().unwrap(); + let owner_guard = match rt.block_on(manager.acquire(owner, |_| async {})) { + AcquireDisposition::Acquired(guard) => guard, + AcquireDisposition::Cancelled => panic!("owner should acquire immediately"), + }; + + let manager_for_task = manager.clone(); + let waiter_task = rt.spawn(async move { + matches!( + manager_for_task.acquire(waiter, |_| async {}).await, + AcquireDisposition::Cancelled + ) + }); + + let deadline = std::time::Instant::now() + Duration::from_millis(100); + while std::time::Instant::now() < deadline { + if manager + .lock_state + .lock() + .unwrap() + .waiter_ids_by_tx + .contains_key(&waiter) + { + break; + } + std::thread::sleep(Duration::from_millis(1)); + } + assert!( + manager + .lock_state + .lock() + .unwrap() + .waiter_ids_by_tx + .contains_key(&waiter), + "waiter should be registered before pruning it", + ); + manager.mark_state(&waiter, super::GlobalTxState::Aborting); + drop(owner_guard); + + assert!(rt.block_on(waiter_task).expect("task should complete")); + } +} diff --git a/crates/core/src/host/host_controller.rs b/crates/core/src/host/host_controller.rs index 8ab53513314..61dd71fb304 100644 --- a/crates/core/src/host/host_controller.rs +++ b/crates/core/src/host/host_controller.rs @@ -3,7 +3,7 @@ use super::scheduler::SchedulerStarter; use super::wasmtime::WasmtimeRuntime; use super::{Scheduler, UpdateDatabaseResult}; use crate::client::{ClientActorId, ClientName}; -use crate::config::V8HeapPolicyConfig; +use crate::config::{GlobalTxConfig, V8HeapPolicyConfig}; use crate::database_logger::DatabaseLogger; use crate::db::persistence::PersistenceProvider; use crate::db::relational_db::{self, spawn_view_cleanup_loop, DiskSizeFn, RelationalDB, Txdata}; @@ -123,6 +123,8 @@ pub struct HostController { /// /// All per-replica clones share the same underlying connection pool. pub call_reducer_client: reqwest::Client, + /// Blocking variant of [`call_reducer_client`] for the WASM executor thread. + pub call_reducer_blocking_client: reqwest::blocking::Client, /// Router that resolves the HTTP base URL of the leader node for a given database. /// /// Set to [`LocalReducerRouter`] by default; replaced with `ClusterReducerRouter` @@ -138,6 +140,8 @@ pub struct HostController { /// /// `None` in test/embedded contexts where no JWT signer is configured. pub call_reducer_auth_token: Option, + /// Global distributed transaction tuning. + pub global_tx_config: GlobalTxConfig, } pub(crate) struct HostRuntimes { @@ -180,6 +184,7 @@ impl From for Result<(), anyhow::Error> { pub enum ReducerOutcome { Committed, Failed(Box>), + Wounded(Box>), BudgetExceeded, } @@ -188,6 +193,7 @@ impl ReducerOutcome { match self { Self::Committed => Ok(()), Self::Failed(e) => Err(anyhow::anyhow!(e)), + Self::Wounded(e) => Err(anyhow::anyhow!(e)), Self::BudgetExceeded => Err(anyhow::anyhow!("reducer ran out of energy")), } } @@ -204,6 +210,7 @@ impl From<&EventStatus> for ReducerOutcome { EventStatus::FailedUser(e) | EventStatus::FailedInternal(e) => { ReducerOutcome::Failed(Box::new((&**e).into())) } + EventStatus::Wounded(e) => ReducerOutcome::Wounded(Box::new((&**e).into())), EventStatus::OutOfEnergy => ReducerOutcome::BudgetExceeded, } } @@ -233,6 +240,7 @@ impl HostController { data_dir: Arc, default_config: db::Config, v8_heap_policy: V8HeapPolicyConfig, + global_tx_config: GlobalTxConfig, program_storage: ProgramStorage, energy_monitor: Arc, persistence: Arc, @@ -250,8 +258,12 @@ impl HostController { bsatn_rlb_pool: BsatnRowListBuilderPool::new(), db_cores, call_reducer_client: ReplicaContext::new_call_reducer_client(&CallReducerOnDbConfig::default()), + call_reducer_blocking_client: ReplicaContext::new_call_reducer_blocking_client( + &CallReducerOnDbConfig::default(), + ), call_reducer_router: Arc::new(LocalReducerRouter::new("http://127.0.0.1:3000")), call_reducer_auth_token: None, + global_tx_config, } } @@ -690,8 +702,10 @@ async fn make_replica_ctx( relational_db: Arc, bsatn_rlb_pool: BsatnRowListBuilderPool, call_reducer_client: reqwest::Client, + call_reducer_blocking_client: reqwest::blocking::Client, call_reducer_router: Arc, call_reducer_auth_token: Option, + global_tx_config: GlobalTxConfig, ) -> anyhow::Result { let logger = match module_logs { Some(path) => asyncify(move || Arc::new(DatabaseLogger::open_today(path))).await, @@ -725,8 +739,13 @@ async fn make_replica_ctx( logger, subscriptions, call_reducer_client, + call_reducer_blocking_client, call_reducer_router, call_reducer_auth_token, + tx_id_nonce: Arc::default(), + global_tx_manager: Arc::new(crate::host::global_tx::GlobalTxManager::new( + global_tx_config.wound_grace_period, + )), }) } @@ -803,8 +822,10 @@ struct ModuleLauncher { core: AllocatedJobCore, bsatn_rlb_pool: BsatnRowListBuilderPool, call_reducer_client: reqwest::Client, + call_reducer_blocking_client: reqwest::blocking::Client, call_reducer_router: Arc, call_reducer_auth_token: Option, + global_tx_config: GlobalTxConfig, } impl ModuleLauncher { @@ -825,8 +846,10 @@ impl ModuleLauncher { self.relational_db, self.bsatn_rlb_pool, self.call_reducer_client, + self.call_reducer_blocking_client, self.call_reducer_router, self.call_reducer_auth_token, + self.global_tx_config, ) .await .map(Arc::new)?; @@ -1029,8 +1052,10 @@ impl Host { core: host_controller.db_cores.take(), bsatn_rlb_pool: bsatn_rlb_pool.clone(), call_reducer_client: host_controller.call_reducer_client.clone(), + call_reducer_blocking_client: host_controller.call_reducer_blocking_client.clone(), call_reducer_router: host_controller.call_reducer_router.clone(), call_reducer_auth_token: host_controller.call_reducer_auth_token.clone(), + global_tx_config: host_controller.global_tx_config, } .launch_module() .await? @@ -1061,8 +1086,10 @@ impl Host { core: host_controller.db_cores.take(), bsatn_rlb_pool: bsatn_rlb_pool.clone(), call_reducer_client: host_controller.call_reducer_client.clone(), + call_reducer_blocking_client: host_controller.call_reducer_blocking_client.clone(), call_reducer_router: host_controller.call_reducer_router.clone(), call_reducer_auth_token: host_controller.call_reducer_auth_token.clone(), + global_tx_config: host_controller.global_tx_config, } .launch_module() .await; @@ -1087,8 +1114,10 @@ impl Host { core: host_controller.db_cores.take(), bsatn_rlb_pool: bsatn_rlb_pool.clone(), call_reducer_client: host_controller.call_reducer_client.clone(), + call_reducer_blocking_client: host_controller.call_reducer_blocking_client.clone(), call_reducer_router: host_controller.call_reducer_router.clone(), call_reducer_auth_token: host_controller.call_reducer_auth_token.clone(), + global_tx_config: host_controller.global_tx_config, } .launch_module() .await; @@ -1203,8 +1232,12 @@ impl Host { bsatn_rlb_pool, // Transient validation-only module; build its own client and router with defaults. call_reducer_client: ReplicaContext::new_call_reducer_client(&CallReducerOnDbConfig::default()), + call_reducer_blocking_client: ReplicaContext::new_call_reducer_blocking_client( + &CallReducerOnDbConfig::default(), + ), call_reducer_router: Arc::new(LocalReducerRouter::new("http://127.0.0.1:3000")), call_reducer_auth_token: None, + global_tx_config: GlobalTxConfig::default(), } .launch_module() .await diff --git a/crates/core/src/host/instance_env.rs b/crates/core/src/host/instance_env.rs index 46af8def3cc..6be7b9f1a91 100644 --- a/crates/core/src/host/instance_env.rs +++ b/crates/core/src/host/instance_env.rs @@ -2,9 +2,10 @@ use super::scheduler::{get_schedule_from_row, ScheduleError, Scheduler}; use crate::database_logger::{BacktraceFrame, BacktraceProvider, LogLevel, ModuleBacktrace, Record}; use crate::db::relational_db::{MutTx, RelationalDB}; use crate::error::{DBError, DatastoreError, IndexError, NodesError}; +use crate::host::global_tx::{GlobalTxRole, GlobalTxState}; use crate::host::module_host::{DatabaseUpdate, EventStatus, ModuleEvent, ModuleFunctionCall}; use crate::host::wasm_common::TimingSpan; -use crate::replica_context::ReplicaContext; +use crate::replica_context::{execute_blocking_http, execute_blocking_http_cancellable, HttpOutcome, ReplicaContext}; use crate::subscription::module_subscription_actor::{commit_and_broadcast_event, ModuleSubscriptions}; use crate::subscription::module_subscription_manager::{from_tx_offset, TransactionOffset}; use crate::util::prometheus_handle::IntGaugeExt; @@ -20,7 +21,7 @@ use spacetimedb_datastore::execution_context::Workload; use spacetimedb_datastore::locking_tx_datastore::state_view::StateView; use spacetimedb_datastore::locking_tx_datastore::{FuncCallType, IndexScanPointOrRange, MutTxId}; use spacetimedb_datastore::traits::IsolationLevel; -use spacetimedb_lib::{http as st_http, ConnectionId, Identity, Timestamp}; +use spacetimedb_lib::{http as st_http, ConnectionId, GlobalTxId, Identity, Timestamp, TX_ID_HEADER}; use spacetimedb_primitives::{ColId, ColList, IndexId, TableId}; use spacetimedb_sats::{ bsatn::{self, ToBsatn}, @@ -38,6 +39,8 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use std::vec::IntoIter; +const PREPARE_ID_HEADER: &str = "X-Prepare-Id"; + pub struct InstanceEnv { pub replica_ctx: Arc, pub scheduler: Scheduler, @@ -50,15 +53,19 @@ pub struct InstanceEnv { pub func_type: FuncCallType, /// The name of the last, including current, function to be executed by this environment. pub func_name: Option, + /// Distributed transaction id for the current reducer invocation. + current_tx_id: Option, /// Are we in an anonymous tx context? in_anon_tx: bool, /// A procedure's last known transaction offset. procedure_last_tx_offset: Option, - /// 2PC: prepared participants from `call_reducer_on_db_2pc` calls. + /// 2PC: participant prepare targets from `call_reducer_on_db_2pc` calls. /// Each entry is (database_identity, prepare_id). /// After the coordinator's reducer commits, these are committed; - /// on failure, they are aborted. - pub prepared_participants: Vec<(Identity, String)>, + /// on failure, they are aborted. Entries are registered before the + /// HTTP prepare request is sent so wound-driven abort fanout can target + /// in-flight prepares using coordinator-assigned ids. + pub contacted_participants: Vec<(Identity, String)>, } /// `InstanceEnv` needs to be `Send` because it is created on the host thread @@ -241,9 +248,10 @@ impl InstanceEnv { // run a function func_type: FuncCallType::Reducer, func_name: None, + current_tx_id: None, in_anon_tx: false, procedure_last_tx_offset: None, - prepared_participants: Vec::new(), + contacted_participants: Vec::new(), } } @@ -253,11 +261,37 @@ impl InstanceEnv { } /// Signal to this `InstanceEnv` that a function call is beginning. - pub fn start_funcall(&mut self, name: Identifier, ts: Timestamp, func_type: FuncCallType) { + pub fn start_funcall( + &mut self, + name: Identifier, + ts: Timestamp, + func_type: FuncCallType, + tx_id: Option, + ) { + let is_reducer = matches!(func_type, FuncCallType::Reducer); self.start_time = ts; self.start_instant = Instant::now(); self.func_type = func_type; self.func_name = Some(name); + self.current_tx_id = if is_reducer { + Some(tx_id.unwrap_or_else(|| self.mint_tx_id(ts))) + } else { + None + }; + } + + pub fn current_tx_id(&self) -> Option { + self.current_tx_id + } + + fn wounded_tx_error(&self, tx_id: GlobalTxId) -> NodesError { + NodesError::Wounded(format!( + "distributed transaction {tx_id} was wounded by an older transaction" + )) + } + + fn mint_tx_id(&self, start_ts: Timestamp) -> GlobalTxId { + self.replica_ctx.mint_global_tx_id(start_ts) } /// Returns the name of the most recent reducer to be run in this environment, @@ -996,9 +1030,8 @@ impl InstanceEnv { /// Unlike [`Self::http_request`], this is explicitly allowed while a transaction is open — /// the caller is responsible for understanding the consistency implications. /// - /// Uses [`ReplicaContext::call_reducer_router`] to resolve the leader node for - /// `database_identity`, then sends the request via the warmed HTTP client in - /// [`ReplicaContext::call_reducer_client`]. + /// Blocks the calling thread (the `SingleCoreExecutor` OS thread) for the duration of the + /// HTTP round-trip using `reqwest::blocking::Client`. No tokio involvement on this path. /// /// Returns `(http_status, response_body)` on transport success, /// or [`NodesError::HttpError`] if the connection itself fails. @@ -1007,130 +1040,158 @@ impl InstanceEnv { database_identity: Identity, reducer_name: &str, args: bytes::Bytes, - ) -> impl Future> + use<> { - let client = self.replica_ctx.call_reducer_client.clone(); - let router = self.replica_ctx.call_reducer_router.clone(); - let reducer_name = reducer_name.to_owned(); - // Node-level auth token: a single token minted at startup and shared by all replicas - // on this node. Passed as a Bearer token so `anon_auth_middleware` on the target node - // accepts the request without generating a fresh ephemeral identity per call. - let auth_token = self.replica_ctx.call_reducer_auth_token.clone(); - let caller_identity = self.replica_ctx.database.database_identity; + ) -> Result<(u16, bytes::Bytes), NodesError> { + let tx_id = self.current_tx_id(); + if let Some(tx_id) = tx_id { + if self.replica_ctx.global_tx_manager.is_wounded(&tx_id) { + return Err(self.wounded_tx_error(tx_id)); + } + } - async move { - let start = Instant::now(); + let start = Instant::now(); + let caller_identity = self.replica_ctx.database.database_identity; - let base_url = router - .resolve_base_url(database_identity) - .await - .map_err(|e| NodesError::HttpError(e.to_string()))?; - let url = format!( - "{}/v1/database/{}/call/{}", - base_url, - database_identity.to_hex(), - reducer_name, - ); - let mut req = client - .post(&url) - .header(http::header::CONTENT_TYPE, "application/octet-stream") - .body(args); - if let Some(token) = auth_token { - req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); - } - let result = async { - let response = req.send().await.map_err(|e| NodesError::HttpError(e.to_string()))?; - let status = response.status().as_u16(); - let body = response - .bytes() - .await - .map_err(|e| NodesError::HttpError(e.to_string()))?; - Ok::<_, NodesError>((status, body)) - } - .await; - - WORKER_METRICS - .cross_db_reducer_calls_total - .with_label_values(&caller_identity) - .inc(); - WORKER_METRICS - .cross_db_reducer_duration_seconds - .with_label_values(&caller_identity) - .observe(start.elapsed().as_secs_f64()); - - result + let base_url = self + .replica_ctx + .call_reducer_router + .resolve_base_url_blocking(database_identity) + .map_err(|e| NodesError::HttpError(e.to_string()))?; + let url = format!( + "{}/v1/database/{}/call/{}", + base_url, + database_identity.to_hex(), + reducer_name, + ); + let mut req = self + .replica_ctx + .call_reducer_blocking_client + .post(&url) + .header(http::header::CONTENT_TYPE, "application/octet-stream") + .body(args.to_vec()); + if let Some(ref token) = self.replica_ctx.call_reducer_auth_token { + req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); } + if let Some(tx_id) = tx_id { + req = req.header(TX_ID_HEADER, tx_id.to_string()); + } + let request = req.build().map_err(|e| NodesError::HttpError(e.to_string()))?; + let result = execute_blocking_http(&self.replica_ctx.call_reducer_blocking_client, request, |resp| { + let status = resp.status().as_u16(); + let body = resp.bytes()?; + Ok((status, body)) + }) + .map_err(|e| NodesError::HttpError(e.to_string())); + + WORKER_METRICS + .cross_db_reducer_calls_total + .with_label_values(&caller_identity) + .inc(); + WORKER_METRICS + .cross_db_reducer_duration_seconds + .with_label_values(&caller_identity) + .observe(start.elapsed().as_secs_f64()); + + result } /// Call a reducer on a remote database using the 2PC prepare protocol. /// /// Like [`Self::call_reducer_on_db`], but POSTs to `/prepare/{reducer}` instead of - /// `/call/{reducer}`. On success, parses the `X-Prepare-Id` response header and stores - /// `(database_identity, prepare_id)` in [`Self::prepared_participants`]. + /// `/call/{reducer}`. The coordinator generates the `prepare_id` up front, sends it + /// in the request, and stores `(database_identity, prepare_id)` in + /// [`Self::contacted_participants`] before the HTTP round-trip begins. /// - /// Returns `(http_status, response_body)` on transport success. - /// The caller (coordinator reducer) is responsible for checking the status; - /// if the coordinator's reducer commits, the runtime will commit all participants, - /// and if it fails, the runtime will abort them. + /// Blocks the calling thread for the duration of the HTTP round-trip. + /// + /// Returns `(http_status, response_body, prepare_id)` on transport success. pub fn call_reducer_on_db_2pc( &mut self, database_identity: Identity, reducer_name: &str, args: bytes::Bytes, - ) -> impl Future), NodesError>> + use<> { - let client = self.replica_ctx.call_reducer_client.clone(); - let router = self.replica_ctx.call_reducer_router.clone(); - let reducer_name = reducer_name.to_owned(); - let auth_token = self.replica_ctx.call_reducer_auth_token.clone(); + ) -> Result<(u16, bytes::Bytes, String), NodesError> { let caller_identity = self.replica_ctx.database.database_identity; + let tx_id = self.current_tx_id().ok_or_else(|| { + NodesError::HttpError("2PC remote reducer call requires an active distributed transaction id".to_owned()) + })?; - async move { - let start = Instant::now(); + if self.replica_ctx.global_tx_manager.is_wounded(&tx_id) { + return Err(self.wounded_tx_error(tx_id)); + } - let base_url = router - .resolve_base_url(database_identity) - .await - .map_err(|e| NodesError::HttpError(e.to_string()))?; - let url = format!( - "{}/v1/database/{}/prepare/{}", - base_url, - database_identity.to_hex(), - reducer_name, - ); - let mut req = client - .post(&url) - .header(http::header::CONTENT_TYPE, "application/octet-stream") - .header("X-Coordinator-Identity", caller_identity.to_hex().to_string()) - .body(args); - if let Some(token) = auth_token { - req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); - } - let result = async { - let response = req.send().await.map_err(|e| NodesError::HttpError(e.to_string()))?; - let status = response.status().as_u16(); - let prepare_id = response - .headers() - .get("X-Prepare-Id") - .and_then(|v| v.to_str().ok()) - .map(|s| s.to_owned()); - let body = response - .bytes() - .await - .map_err(|e| NodesError::HttpError(e.to_string()))?; - Ok((status, body, prepare_id)) - } - .await; - - WORKER_METRICS - .cross_db_reducer_calls_total - .with_label_values(&caller_identity) - .inc(); - WORKER_METRICS - .cross_db_reducer_duration_seconds - .with_label_values(&caller_identity) - .observe(start.elapsed().as_secs_f64()); - - result + let session = + self.replica_ctx + .global_tx_manager + .ensure_session(tx_id, GlobalTxRole::Coordinator, tx_id.creator_db); + session.set_state(GlobalTxState::Preparing); + let prepare_id = super::module_host::generate_prepare_id(tx_id, caller_identity); + session.add_participant(database_identity, prepare_id.clone()); + self.contacted_participants + .push((database_identity, prepare_id.clone())); + + let start = Instant::now(); + + let base_url = self + .replica_ctx + .call_reducer_router + .resolve_base_url_blocking(database_identity) + .map_err(|e| NodesError::HttpError(e.to_string()))?; + let url = format!( + "{}/v1/database/{}/prepare/{}", + base_url, + database_identity.to_hex(), + reducer_name, + ); + let mut req = self + .replica_ctx + .call_reducer_blocking_client + .post(&url) + .header(http::header::CONTENT_TYPE, "application/octet-stream") + .header("X-Coordinator-Identity", caller_identity.to_hex().to_string()) + .header(PREPARE_ID_HEADER, prepare_id.clone()) + .header(TX_ID_HEADER, tx_id.to_string()) + .body(args.to_vec()); + if let Some(ref token) = self.replica_ctx.call_reducer_auth_token { + req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); } + + // Check for wound signal one more time before blocking, then keep checking + // every 50 ms while the HTTP round-trip is in-flight. + if self.replica_ctx.global_tx_manager.is_wounded(&tx_id) { + return Err(self.wounded_tx_error(tx_id)); + } + + let request = req.build().map_err(|e| NodesError::HttpError(e.to_string()))?; + let manager = self.replica_ctx.global_tx_manager.clone(); + let outcome = execute_blocking_http_cancellable( + &self.replica_ctx.call_reducer_blocking_client, + request, + move || manager.is_wounded(&tx_id), + |resp| { + let status = resp.status().as_u16(); + let body = resp.bytes()?; + Ok((status, body)) + }, + ); + let result = match outcome { + HttpOutcome::Done(r) => r.map_err(|e| NodesError::HttpError(e.to_string())), + HttpOutcome::Cancelled => return Err(self.wounded_tx_error(tx_id)), + }; + + WORKER_METRICS + .cross_db_reducer_calls_total + .with_label_values(&caller_identity) + .inc(); + WORKER_METRICS + .two_pc_outgoing_prepare_calls_total + .with_label_values(&caller_identity) + .inc(); + WORKER_METRICS + .cross_db_reducer_duration_seconds + .with_label_values(&caller_identity) + .observe(start.elapsed().as_secs_f64()); + + result.map(|(status, body)| (status, body, prepare_id)) } } @@ -1507,8 +1568,13 @@ mod test { logger, subscriptions: subs, call_reducer_client: ReplicaContext::new_call_reducer_client(&CallReducerOnDbConfig::default()), + call_reducer_blocking_client: ReplicaContext::new_call_reducer_blocking_client( + &CallReducerOnDbConfig::default(), + ), call_reducer_router: Arc::new(LocalReducerRouter::new("http://127.0.0.1:3000")), call_reducer_auth_token: None, + tx_id_nonce: Arc::default(), + global_tx_manager: Arc::default(), }, runtime, )) diff --git a/crates/core/src/host/mod.rs b/crates/core/src/host/mod.rs index 06e55de6444..e673831dc4f 100644 --- a/crates/core/src/host/mod.rs +++ b/crates/core/src/host/mod.rs @@ -24,19 +24,16 @@ use spacetimedb_schema::def::ModuleDef; /// /// Use this for every place in the 2PC / cross-DB call paths that needs to /// synchronously drive a future from blocking (WASM executor) context. -pub(crate) fn block_on_scoped(handle: &tokio::runtime::Handle, fut: F) -> F::Output +pub(crate) fn block_on_scoped(_handle: &tokio::runtime::Handle, fut: F) -> F::Output where F: Future + Send, F::Output: Send, { - std::thread::scope(|s| { - s.spawn(|| handle.block_on(fut)) - .join() - .expect("block_on_scoped: thread panicked") - }) + futures::executor::block_on(fut) } mod disk_storage; +pub mod global_tx; mod host_controller; mod module_common; #[allow(clippy::too_many_arguments)] diff --git a/crates/core/src/host/module_host.rs b/crates/core/src/host/module_host.rs index b3ecf0c28c7..3f75a4f6c1f 100644 --- a/crates/core/src/host/module_host.rs +++ b/crates/core/src/host/module_host.rs @@ -55,7 +55,7 @@ use spacetimedb_expr::expr::CollectViews; use spacetimedb_lib::db::raw_def::v9::Lifecycle; use spacetimedb_lib::identity::{AuthCtx, RequestId}; use spacetimedb_lib::metrics::ExecutionMetrics; -use spacetimedb_lib::{ConnectionId, Timestamp}; +use spacetimedb_lib::{ConnectionId, GlobalTxId, Timestamp}; use spacetimedb_primitives::{ArgId, ProcedureId, TableId, ViewFnPtr, ViewId}; use spacetimedb_query::compile_subscription; use spacetimedb_sats::raw_identifier::RawIdentifier; @@ -68,7 +68,8 @@ use spacetimedb_schema::schema::{Schema, TableSchema}; use spacetimedb_schema::table_name::TableName; use std::collections::VecDeque; use std::fmt; -use std::sync::atomic::AtomicBool; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::OnceLock; use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; use tokio::sync::oneshot; @@ -170,6 +171,7 @@ pub enum EventStatus { Committed(DatabaseUpdate), FailedUser(String), FailedInternal(String), + Wounded(String), OutOfEnergy, } @@ -570,6 +572,7 @@ pub fn call_identity_connected( None, None, None, + None, reducer_id, reducer_def, FunctionArgs::Nullary, @@ -594,6 +597,7 @@ pub fn call_identity_connected( // If the reducer returned an error or couldn't run due to insufficient energy, // abort the connection: the module code has decided it doesn't want this client. ReducerOutcome::Failed(message) => Err(ClientConnectedError::Rejected(*message)), + ReducerOutcome::Wounded(message) => Err(ClientConnectedError::Rejected(*message)), ReducerOutcome::BudgetExceeded => Err(ClientConnectedError::OutOfEnergy), } } else { @@ -619,6 +623,7 @@ pub struct CallReducerParams { pub timestamp: Timestamp, pub caller_identity: Identity, pub caller_connection_id: ConnectionId, + pub tx_id: Option, pub client: Option>, pub request_id: Option, pub timer: Option, @@ -639,6 +644,7 @@ impl CallReducerParams { timestamp, caller_identity, caller_connection_id: ConnectionId::ZERO, + tx_id: None, client: None, request_id: None, timer: None, @@ -950,6 +956,27 @@ pub enum ReducerCallError { ScheduleReducerNotFound, #[error("can't directly call special {0:?} lifecycle reducer")] LifecycleReducer(Lifecycle), + #[error("invalid prepare id: {0}")] + InvalidPrepareId(String), +} + +static PREPARE_COUNTER: AtomicU64 = AtomicU64::new(0); +static PREPARE_COUNTER_INIT: OnceLock<()> = OnceLock::new(); + +fn next_prepare_counter() -> u64 { + PREPARE_COUNTER_INIT.get_or_init(|| { + let seed = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_micros() as u64; + PREPARE_COUNTER.store(seed, Ordering::Relaxed); + }); + PREPARE_COUNTER.fetch_add(1, Ordering::Relaxed) +} + +pub(crate) fn generate_prepare_id(tx_id: GlobalTxId, coordinator_identity: Identity) -> String { + let counter = next_prepare_counter(); + format!("prepare-{tx_id}-{counter:016x}-{}", coordinator_identity.to_hex()) } #[derive(Debug, PartialEq, Eq)] @@ -963,7 +990,9 @@ impl From for ViewOutcome { fn from(status: EventStatus) -> Self { match status { EventStatus::Committed(_) => ViewOutcome::Success, - EventStatus::FailedUser(e) | EventStatus::FailedInternal(e) => ViewOutcome::Failed(e), + EventStatus::FailedUser(e) | EventStatus::FailedInternal(e) | EventStatus::Wounded(e) => { + ViewOutcome::Failed(e) + } EventStatus::OutOfEnergy => ViewOutcome::BudgetExceeded, } } @@ -1468,6 +1497,7 @@ impl ModuleHost { None, None, None, + None, reducer_id, reducer_def, FunctionArgs::Nullary, @@ -1491,7 +1521,7 @@ impl ModuleHost { fallback() } Ok(ReducerCallResult { - outcome: ReducerOutcome::Failed(_) | ReducerOutcome::BudgetExceeded, + outcome: ReducerOutcome::Failed(_) | ReducerOutcome::Wounded(_) | ReducerOutcome::BudgetExceeded, .. }) => fallback(), @@ -1552,6 +1582,7 @@ impl ModuleHost { module: &ModuleInfo, caller_identity: Identity, caller_connection_id: Option, + tx_id: Option, client: Option>, request_id: Option, timer: Option, @@ -1567,6 +1598,7 @@ impl ModuleHost { timestamp: Timestamp::now(), caller_identity, caller_connection_id, + tx_id, client, request_id, timer, @@ -1579,6 +1611,7 @@ impl ModuleHost { &self, caller_identity: Identity, caller_connection_id: Option, + tx_id: Option, client: Option>, request_id: Option, timer: Option, @@ -1589,11 +1622,26 @@ impl ModuleHost { let args = args .into_tuple_for_def(&self.info.module_def, reducer_def) .map_err(InvalidReducerArguments)?; + let _global_tx_lock_guard = if let Some(tx_id) = tx_id { + match self.acquire_global_tx_slot(tx_id).await { + Ok(guard) => Some(guard), + Err(outcome) => { + return Ok(ReducerCallResult { + outcome, + energy_used: EnergyQuanta::ZERO, + execution_duration: Default::default(), + }) + } + } + } else { + None + }; let caller_connection_id = caller_connection_id.unwrap_or(ConnectionId::ZERO); let call_reducer_params = CallReducerParams { timestamp: Timestamp::now(), caller_identity, caller_connection_id, + tx_id, client, request_id, timer, @@ -1601,19 +1649,22 @@ impl ModuleHost { args, }; - self.call( - &reducer_def.name, - call_reducer_params, - async |p, inst| Ok(inst.call_reducer(p)), - async |p, inst| inst.call_reducer(p).await, - ) - .await? + let result = self + .call( + &reducer_def.name, + call_reducer_params, + async |p, inst| Ok(inst.call_reducer(p)), + async |p, inst| inst.call_reducer(p).await, + ) + .await; + result? } async fn call_reducer_inner_with_return( &self, caller_identity: Identity, caller_connection_id: Option, + tx_id: Option, client: Option>, request_id: Option, timer: Option, @@ -1624,11 +1675,29 @@ impl ModuleHost { let args = args .into_tuple_for_def(&self.info.module_def, reducer_def) .map_err(InvalidReducerArguments)?; + let _global_tx_lock_guard = if let Some(tx_id) = tx_id { + match self.acquire_global_tx_slot(tx_id).await { + Ok(guard) => Some(guard), + Err(outcome) => { + return Ok(( + ReducerCallResult { + outcome, + energy_used: EnergyQuanta::ZERO, + execution_duration: Default::default(), + }, + None, + )) + } + } + } else { + None + }; let caller_connection_id = caller_connection_id.unwrap_or(ConnectionId::ZERO); let call_reducer_params = CallReducerParams { timestamp: Timestamp::now(), caller_identity, caller_connection_id, + tx_id, client, request_id, timer, @@ -1636,19 +1705,22 @@ impl ModuleHost { args, }; - self.call( - &reducer_def.name, - call_reducer_params, - async |p, inst| Ok(inst.call_reducer_with_return(p)), - async |p, inst| inst.call_reducer(p).await.map(|res| (res, None)), - ) - .await? + let result = self + .call( + &reducer_def.name, + call_reducer_params, + async |p, inst| Ok(inst.call_reducer_with_return(p)), + async |p, inst| inst.call_reducer(p).await.map(|res| (res, None)), + ) + .await; + result? } pub async fn call_reducer( &self, caller_identity: Identity, caller_connection_id: Option, + tx_id: Option, client: Option>, request_id: Option, timer: Option, @@ -1672,6 +1744,7 @@ impl ModuleHost { self.call_reducer_inner( caller_identity, caller_connection_id, + tx_id, client, request_id, timer, @@ -1699,6 +1772,7 @@ impl ModuleHost { &self, caller_identity: Identity, caller_connection_id: Option, + tx_id: Option, client: Option>, request_id: Option, timer: Option, @@ -1722,6 +1796,7 @@ impl ModuleHost { self.call_reducer_inner_with_return( caller_identity, caller_connection_id, + tx_id, client, request_id, timer, @@ -1755,27 +1830,19 @@ impl ModuleHost { &self, caller_identity: Identity, caller_connection_id: Option, + tx_id: Option, reducer_name: &str, args: FunctionArgs, // The actual coordinator database identity (from `X-Coordinator-Identity` header). // When `Some`, used for `prepare_id` namespacing and stored in `st_2pc_state` for // recovery. Falls back to `caller_identity` when `None` (e.g., internal calls). coordinator_identity_override: Option, + supplied_prepare_id: Option, ) -> Result<(String, ReducerCallResult, Option), ReducerCallError> { - use std::sync::atomic::{AtomicU64, Ordering}; - use std::sync::OnceLock; - // Counter seeded from current time on first use so that restarts begin from a - // different value than any existing st_2pc_state entries (which hold IDs from - // previous sessions starting at much smaller counter values). - static PREPARE_COUNTER: AtomicU64 = AtomicU64::new(0); - static PREPARE_COUNTER_INIT: OnceLock<()> = OnceLock::new(); - PREPARE_COUNTER_INIT.get_or_init(|| { - let seed = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_micros() as u64; - PREPARE_COUNTER.store(seed, Ordering::Relaxed); - }); + if tx_id.is_none() { + log::error!("prepare_reducer called without tx_id: caller_identity={caller_identity}, reducer_name={reducer_name}"); + } + let tx_id = tx_id.ok_or(ReducerCallError::NoSuchReducer)?; let (reducer_id, reducer_def) = self .info @@ -1797,6 +1864,7 @@ impl ModuleHost { timestamp: Timestamp::now(), caller_identity, caller_connection_id, + tx_id: Some(tx_id), client: None, request_id: None, timer: None, @@ -1807,15 +1875,15 @@ impl ModuleHost { // Resolve the effective coordinator identity before generating the prepare_id so // the prefix is namespaced correctly even when called from the HTTP prepare handler. let coordinator_identity = coordinator_identity_override.unwrap_or(caller_identity); - - // Include the coordinator identity so prepare_ids from different coordinators - // cannot collide on the participant's st_2pc_state table. - let coordinator_hex = coordinator_identity.to_hex(); - let prepare_id = format!( - "prepare-{}-{}", - &coordinator_hex.to_string()[..16], - PREPARE_COUNTER.fetch_add(1, Ordering::Relaxed), - ); + let prepare_id = supplied_prepare_id.unwrap_or_else(|| generate_prepare_id(tx_id, coordinator_identity)); + let prepare_tx_id = Self::tx_id_from_prepare_id(&prepare_id).ok_or_else(|| { + ReducerCallError::InvalidPrepareId(format!("prepare_id '{prepare_id}' is not parseable")) + })?; + if prepare_tx_id != tx_id { + return Err(ReducerCallError::InvalidPrepareId(format!( + "prepare_id '{prepare_id}' encodes tx_id {prepare_tx_id}, expected {tx_id}" + ))); + } // Channel for signalling PREPARED result back to this task. let (prepared_tx, prepared_rx) = tokio::sync::oneshot::channel::<(ReducerCallResult, Option)>(); @@ -1828,6 +1896,38 @@ impl ModuleHost { decision_sender: decision_tx, }, ); + //if let Some(tx_id) = tx_id { + let session = self.replica_ctx().global_tx_manager.ensure_session( + tx_id, + super::global_tx::GlobalTxRole::Participant, + tx_id.creator_db, + ); + session.set_state(super::global_tx::GlobalTxState::Preparing); + self.replica_ctx() + .global_tx_manager + .set_prepare_mapping(tx_id, prepare_id.clone()); + let global_tx_lock_guard = match self.acquire_global_tx_slot(tx_id).await { + Ok(guard) => guard, + Err(outcome) => { + self.prepared_txs.remove(&prepare_id); + self.replica_ctx().global_tx_manager.remove_prepare_mapping(&prepare_id); + self.replica_ctx() + .global_tx_manager + .mark_state(&tx_id, super::global_tx::GlobalTxState::Aborted); + self.replica_ctx().global_tx_manager.release(&tx_id); + self.replica_ctx().global_tx_manager.remove_session(&tx_id); + return Ok(( + String::new(), + ReducerCallResult { + outcome, + energy_used: EnergyQuanta::ZERO, + execution_duration: Default::default(), + }, + None, + )); + } + }; + //} // Spawn a background task that runs the reducer and holds the write lock // until we send a decision. The executor thread blocks inside @@ -1839,13 +1939,20 @@ impl ModuleHost { let _ = this .call( &reducer_name_owned, - (params, prepare_id_clone, coordinator_identity, prepared_tx, decision_rx), - async |(p, pid, cid, ptx, drx), inst| { - inst.call_reducer_prepare_and_hold(p, pid, cid, ptx, drx); + ( + params, + prepare_id_clone, + coordinator_identity, + prepared_tx, + decision_rx, + global_tx_lock_guard, + ), + async |(p, pid, cid, ptx, drx, guard), inst| { + inst.call_reducer_prepare_and_hold(p, pid, cid, ptx, drx, guard); Ok::<(), ReducerCallError>(()) }, // JS modules: no 2PC support yet. - async |(_p, _pid, _cid, _ptx, _drx), _inst| Err(ReducerCallError::NoSuchReducer), + async |(_p, _pid, _cid, _ptx, _drx, _guard), _inst| Err(ReducerCallError::NoSuchReducer), ) .await; }); @@ -1854,10 +1961,22 @@ impl ModuleHost { match prepared_rx.await { Ok((result, return_value)) => { if matches!(result.outcome, ReducerOutcome::Committed) { + //if let Some(tx_id) = tx_id { + self.replica_ctx() + .global_tx_manager + .mark_state(&tx_id, super::global_tx::GlobalTxState::Prepared); + // } Ok((prepare_id, result, return_value)) } else { // Reducer failed — remove the entry we registered (no hold in progress). self.prepared_txs.remove(&prepare_id); + // if let Some(tx_id) = tx_id { + self.replica_ctx().global_tx_manager.remove_prepare_mapping(&prepare_id); + self.replica_ctx() + .global_tx_manager + .mark_state(&tx_id, super::global_tx::GlobalTxState::Aborted); + self.replica_ctx().global_tx_manager.remove_session(&tx_id); + // } Ok((String::new(), result, return_value)) } } @@ -1867,6 +1986,11 @@ impl ModuleHost { /// Finalize a prepared transaction as COMMIT. pub fn commit_prepared(&self, prepare_id: &str) -> Result<(), String> { + if let Some(tx_id) = self.replica_ctx().global_tx_manager.remove_prepare_mapping(prepare_id) { + self.replica_ctx() + .global_tx_manager + .mark_state(&tx_id, super::global_tx::GlobalTxState::Committing); + } let info = self .prepared_txs .remove(prepare_id) @@ -1878,6 +2002,14 @@ impl ModuleHost { /// Abort a prepared transaction. pub fn abort_prepared(&self, prepare_id: &str) -> Result<(), String> { + if let Some(tx_id) = self.replica_ctx().global_tx_manager.remove_prepare_mapping(prepare_id) { + log::info!("2PC abort_prepared: aborting prepared transaction {tx_id} ({prepare_id})"); + self.replica_ctx() + .global_tx_manager + .mark_state(&tx_id, super::global_tx::GlobalTxState::Aborting); + } else { + log::info!("2PC abort_prepared: aborting legacy/unmapped prepare_id {prepare_id}"); + } let info = self .prepared_txs .remove(prepare_id) @@ -1887,23 +2019,238 @@ impl ModuleHost { Ok(()) } + pub async fn wound_global_tx(&self, tx_id: GlobalTxId) -> Result<(), String> { + let local_db = self.replica_ctx().database.database_identity; + if tx_id.creator_db != local_db { + return Err(format!( + "global transaction {tx_id} is coordinated by {}, not {}", + tx_id.creator_db, local_db + )); + } + + let Some(session) = self.replica_ctx().global_tx_manager.get_session(&tx_id) else { + log::info!("2PC wound: received wound for unknown transaction {tx_id} on {local_db}"); + return Ok(()); + }; + match session.state() { + super::global_tx::GlobalTxState::Committed + | super::global_tx::GlobalTxState::Aborted + | super::global_tx::GlobalTxState::Aborting => { + log::info!( + "2PC wound: transaction {tx_id} on {local_db} already in terminal/aborting state {:?}", + session.state() + ); + return Ok(()); + } + _ => {} + } + log::info!( + "2PC wound: coordinator {} is aborting transaction {tx_id} from state {:?}", + local_db, + session.state() + ); + let session = self + .replica_ctx() + .global_tx_manager + .wound(&tx_id) + .ok_or_else(|| format!("no such global transaction: {tx_id}"))?; + + if let Some(prepare_id) = session.prepare_id() { + let _ = self.abort_prepared(&prepare_id); + } + + let participants = session.participants(); + let client = self.replica_ctx().call_reducer_client.clone(); + let router = self.replica_ctx().call_reducer_router.clone(); + let auth_token = self.replica_ctx().call_reducer_auth_token.clone(); + for (participant_identity, prepare_id) in participants { + if participant_identity == local_db { + let _ = self.abort_prepared(&prepare_id); + continue; + } + + let base_url = router + .resolve_base_url(participant_identity) + .await + .map_err(|e| format!("failed to resolve participant {participant_identity}: {e}"))?; + let url = format!( + "{}/v1/database/{}/2pc/abort/{}", + base_url, + participant_identity.to_hex(), + prepare_id, + ); + let mut req = client.post(&url); + if let Some(token) = &auth_token { + req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); + } + match req.send().await { + Ok(resp) if resp.status().is_success() => {} + Ok(resp) => { + log::warn!( + "2PC wound: participant abort for {prepare_id} on {participant_identity} returned {}", + resp.status() + ); + } + Err(e) => { + log::warn!("2PC wound: transport error aborting {prepare_id} on {participant_identity}: {e}"); + } + } + } + + Ok(()) + } + + async fn acquire_global_tx_slot(&self, tx_id: GlobalTxId) -> Result { + let manager = self.replica_ctx().global_tx_manager.clone(); + let local_db = self.replica_ctx().database.database_identity; + let role = if tx_id.creator_db == local_db { + super::global_tx::GlobalTxRole::Coordinator + } else { + super::global_tx::GlobalTxRole::Participant + }; + manager.ensure_session(tx_id, role, tx_id.creator_db); + if let Some(outcome) = self.check_global_tx_wounded(tx_id) { + log::info!("global transaction {tx_id} arrived already wounded before scheduler admission"); + self.abort_global_tx_locally(tx_id, true); + return Err(outcome); + } + + let this = self.clone(); + match manager + .acquire(tx_id, move |owner| { + let this = this.clone(); + async move { + if owner.creator_db != local_db { + this.send_wound_to_coordinator(owner).await; + } + } + }) + .await + { + super::global_tx::AcquireDisposition::Acquired(lock_guard) => { + if let Some(outcome) = self.check_global_tx_wounded(tx_id) { + log::info!("global transaction {tx_id} was wounded immediately after scheduler admission"); + drop(lock_guard); + self.finish_abort_global_tx_locally(tx_id, true); + return Err(outcome); + } + return Ok(lock_guard); + } + super::global_tx::AcquireDisposition::Cancelled => { + log::info!("global transaction {tx_id} was cancelled while waiting for scheduler admission"); + self.abort_global_tx_locally(tx_id, true); + return Err(self + .check_global_tx_wounded(tx_id) + .unwrap_or_else(|| ReducerOutcome::Wounded(Box::new(Box::from(Self::wounded_message(tx_id)))))); + } + } + } + + fn check_global_tx_wounded(&self, tx_id: GlobalTxId) -> Option { + self.replica_ctx() + .global_tx_manager + .is_wounded(&tx_id) + .then(|| ReducerOutcome::Wounded(Box::new(Box::from(Self::wounded_message(tx_id))))) + } + + fn abort_global_tx_locally(&self, tx_id: GlobalTxId, remove_session: bool) { + log::info!( + "global transaction {tx_id} aborting locally on {}; remove_session={remove_session}", + self.replica_ctx().database.database_identity + ); + self.replica_ctx().global_tx_manager.release(&tx_id); + self.finish_abort_global_tx_locally(tx_id, remove_session); + } + + fn finish_abort_global_tx_locally(&self, tx_id: GlobalTxId, remove_session: bool) { + self.replica_ctx() + .global_tx_manager + .mark_state(&tx_id, super::global_tx::GlobalTxState::Aborted); + if remove_session { + self.replica_ctx().global_tx_manager.remove_session(&tx_id); + } + } + + fn wounded_message(tx_id: GlobalTxId) -> String { + format!("distributed transaction {tx_id} was wounded by an older transaction") + } + + fn tx_id_from_prepare_id(prepare_id: &str) -> Option { + let raw = prepare_id.strip_prefix("prepare-")?; + let mut parts = raw.rsplitn(3, '-'); + let _tail = parts.next()?; + let middle = parts.next()?; + let tx_component = parts.next().unwrap_or(middle); + if tx_component.starts_with("legacy:") { + return None; + } + tx_component.parse().ok() + } + + // Notify a remote coordinator that a transaction should be wounded. + async fn send_wound_to_coordinator(&self, tx_id: GlobalTxId) { + let client = self.replica_ctx().call_reducer_client.clone(); + let router = self.replica_ctx().call_reducer_router.clone(); + let auth_token = self.replica_ctx().call_reducer_auth_token.clone(); + let local_db = self.replica_ctx().database.database_identity; + let base_url = match router.resolve_base_url(tx_id.creator_db).await { + Ok(url) => url, + Err(e) => { + log::warn!("2PC wound: cannot resolve coordinator URL for {tx_id}: {e}"); + return; + } + }; + let url = format!( + "{}/v1/database/{}/2pc/wound/{}", + base_url, + tx_id.creator_db.to_hex(), + tx_id, + ); + let mut req = client.post(&url); + if let Some(token) = &auth_token { + req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); + } + log::info!("2PC wound: sending wound for {tx_id} to coordinator at {url}"); + WORKER_METRICS + .two_pc_wound_requests_sent_total + .with_label_values(&local_db) + .inc(); + match req.send().await { + Ok(resp) if resp.status().is_success() => { + log::info!("2PC wound: notified coordinator for {tx_id}"); + } + Ok(resp) => { + log::warn!("2PC wound: coordinator returned {} for {tx_id}", resp.status()); + } + Err(e) => { + log::warn!("2PC wound: transport error for {tx_id}: {e}"); + } + } + } + /// Delete a coordinator log entry for `prepare_id`. /// Called when B has confirmed it committed, so A can stop retransmitting. - pub fn ack_2pc_coordinator_commit(&self, prepare_id: &str) -> Result<(), anyhow::Error> { - let db = self.relational_db().clone(); - db.with_auto_commit::<_, _, anyhow::Error>(Workload::Internal, |tx| { - tx.delete_st_2pc_coordinator_log(prepare_id) - .map_err(anyhow::Error::from) - }) + pub fn ack_2pc_coordinator_commit(&self, _prepare_id: &str) -> Result<(), anyhow::Error> { + let _db = self.relational_db().clone(); + // db.with_auto_commit::<_, _, anyhow::Error>(Workload::Internal, |tx| { + // tx.delete_st_2pc_coordinator_log(prepare_id) + // .map_err(anyhow::Error::from) + // }) + Ok(()) } /// Check whether `prepare_id` is present in the coordinator log of this database. /// Used by participant B to ask coordinator A: "did you commit?" - pub fn has_2pc_coordinator_commit(&self, prepare_id: &str) -> bool { - let db = self.relational_db(); - db.pending_2pc_coordinator_commits() - .map(|rows| rows.iter().any(|r| r.participant_prepare_id == prepare_id)) - .unwrap_or(false) + pub async fn has_2pc_coordinator_commit(&self, prepare_id: &str) -> bool { + let db = self.relational_db().clone(); + let prepare_id = prepare_id.to_string(); + tokio::task::spawn_blocking(move || { + db.pending_2pc_coordinator_commits() + .map(|rows| rows.iter().any(|r| r.participant_prepare_id == prepare_id)) + .unwrap_or(false) + }) + .await + .expect("Couldn't spawn blocking task") } /// Crash recovery for the **coordinator** role. @@ -1930,6 +2277,7 @@ impl ModuleHost { let auth_token = replica_ctx.call_reducer_auth_token.clone(); for row in rows { let prepare_id = row.participant_prepare_id.clone(); + let recovered_tx_id = Self::tx_id_from_prepare_id(&prepare_id); let participant_identity = match Identity::from_hex(&row.participant_identity_hex) { Ok(id) => id, Err(e) => { @@ -1940,6 +2288,15 @@ impl ModuleHost { continue; } }; + if let Some(tx_id) = recovered_tx_id { + let session = replica_ctx.global_tx_manager.ensure_session( + tx_id, + super::global_tx::GlobalTxRole::Coordinator, + tx_id.creator_db, + ); + session.set_state(super::global_tx::GlobalTxState::Committing); + session.add_participant(participant_identity, prepare_id.clone()); + } let base_url = match router.resolve_base_url(participant_identity).await { Ok(url) => url, Err(e) => { @@ -1965,6 +2322,13 @@ impl ModuleHost { }) { log::warn!("recover_2pc_coordinator: delete coordinator log failed for {prepare_id}: {e}"); } + if let Some(tx_id) = recovered_tx_id { + replica_ctx + .global_tx_manager + .mark_state(&tx_id, super::global_tx::GlobalTxState::Committed); + replica_ctx.global_tx_manager.release(&tx_id); + replica_ctx.global_tx_manager.remove_session(&tx_id); + } } Ok(resp) => { log::warn!( @@ -2024,13 +2388,36 @@ impl ModuleHost { .map(ConnectionId::from_u128) .unwrap_or(ConnectionId::ZERO); let args = FunctionArgs::Bsatn(row.args_bsatn.clone().into()); + let recovered_tx_id = Self::tx_id_from_prepare_id(&original_prepare_id); + if let Some(tx_id) = recovered_tx_id { + let session = this.replica_ctx().global_tx_manager.ensure_session( + tx_id, + super::global_tx::GlobalTxRole::Participant, + coordinator_identity, + ); + session.set_state(super::global_tx::GlobalTxState::Prepared); + this.replica_ctx() + .global_tx_manager + .set_prepare_mapping(tx_id, original_prepare_id.clone()); + } // Step 1: Re-run the reducer to reacquire the write lock. let new_prepare_id = match this - .prepare_reducer(caller_identity, Some(caller_connection_id), &row.reducer_name, args, Some(coordinator_identity)) + .prepare_reducer( + caller_identity, + Some(caller_connection_id), + recovered_tx_id, + &row.reducer_name, + args, + Some(coordinator_identity), + None, + ) .await { Ok((pid, result, _rv)) if !pid.is_empty() => { + this.replica_ctx() + .global_tx_manager + .remove_prepare_mapping(&original_prepare_id); log::info!( "recover_2pc_participant: re-prepared {original_prepare_id} as {pid}: {:?}", result.outcome @@ -2976,6 +3363,10 @@ impl ModuleHost { self.replica_ctx().relational_db() } + pub fn mint_global_tx_id(&self, start_ts: Timestamp) -> GlobalTxId { + self.replica_ctx().mint_global_tx_id(start_ts) + } + pub(crate) fn replica_ctx(&self) -> &ReplicaContext { match &*self.inner { ModuleHostInner::Wasm(wasm) => wasm.instance_manager.module.replica_ctx(), @@ -3019,16 +3410,17 @@ fn args_error_log_message(function_kind: &str, function_name: &str) -> String { #[cfg(test)] mod tests { - use super::ModuleHost; + use super::{generate_prepare_id, ModuleHost}; use crate::client::{ ClientActorId, ClientConfig, ClientConnectionReceiver, ClientConnectionSender, OutboundMessage, Protocol, WsVersion, }; use crate::db::relational_db::tests_utils::{insert, with_auto_commit, TestDB}; + use crate::host::global_tx::{GlobalTxManager, GlobalTxRole}; use crate::subscription::module_subscription_actor::ModuleSubscriptions; use spacetimedb_client_api_messages::websocket::{common::RowListLen as _, v1 as ws_v1, v2 as ws_v2}; use spacetimedb_lib::identity::AuthCtx; - use spacetimedb_lib::{AlgebraicType, Identity}; + use spacetimedb_lib::{AlgebraicType, GlobalTxId, Identity, Timestamp}; use spacetimedb_sats::product; use std::sync::Arc; @@ -3125,4 +3517,55 @@ mod tests { Ok(()) } + + #[test] + fn generated_prepare_id_round_trips_tx_id() { + let tx_id = GlobalTxId { + start_ts: Timestamp::from_micros_since_unix_epoch(123), + creator_db: Identity::from_byte_array([7; 32]), + nonce: 9, + attempt: 2, + }; + let prepare_id = generate_prepare_id(tx_id, Identity::from_byte_array([3; 32])); + assert_eq!(ModuleHost::tx_id_from_prepare_id(&prepare_id), Some(tx_id)); + } + + #[test] + fn generated_prepare_ids_are_unique_per_call() { + let tx_id = GlobalTxId { + start_ts: Timestamp::from_micros_since_unix_epoch(456), + creator_db: Identity::from_byte_array([8; 32]), + nonce: 10, + attempt: 3, + }; + let coordinator = Identity::from_byte_array([4; 32]); + let a = generate_prepare_id(tx_id, coordinator); + let b = generate_prepare_id(tx_id, coordinator); + assert_ne!(a, b); + assert_eq!(ModuleHost::tx_id_from_prepare_id(&a), Some(tx_id)); + assert_eq!(ModuleHost::tx_id_from_prepare_id(&b), Some(tx_id)); + } + + #[test] + fn global_tx_session_keeps_multiple_prepare_ids_for_same_participant() { + let tx_id = GlobalTxId { + start_ts: Timestamp::from_micros_since_unix_epoch(789), + creator_db: Identity::from_byte_array([9; 32]), + nonce: 11, + attempt: 4, + }; + let participant = Identity::from_byte_array([5; 32]); + let manager = GlobalTxManager::default(); + let session = manager.ensure_session(tx_id, GlobalTxRole::Coordinator, Identity::from_byte_array([6; 32])); + session.add_participant(participant, "prepare-a".to_string()); + session.add_participant(participant, "prepare-b".to_string()); + + assert_eq!( + session.participants(), + vec![ + (participant, "prepare-a".to_string()), + (participant, "prepare-b".to_string()) + ] + ); + } } diff --git a/crates/core/src/host/reducer_router.rs b/crates/core/src/host/reducer_router.rs index dcbf20c51c8..815ad6ce876 100644 --- a/crates/core/src/host/reducer_router.rs +++ b/crates/core/src/host/reducer_router.rs @@ -28,6 +28,18 @@ pub trait ReducerCallRouter: Send + Sync + 'static { /// Returns an error string when the leader cannot be resolved /// (database not found, no leader elected yet, node has no network address, etc.). fn resolve_base_url<'a>(&'a self, database_identity: Identity) -> BoxFuture<'a, anyhow::Result>; + + /// Blocking variant of [`resolve_base_url`] for use on non-async threads. + /// + /// The default implementation drives the async version on a fresh OS thread with its own + /// minimal tokio runtime, so it is safe to call from any thread — including threads that + /// are already inside a tokio `block_on` context (e.g. the `SingleCoreExecutor` thread). + /// + /// Override for routers that can resolve without spawning (e.g. [`LocalReducerRouter`]). + fn resolve_base_url_blocking(&self, database_identity: Identity) -> anyhow::Result { + let fut = self.resolve_base_url(database_identity); + futures::executor::block_on(fut) + } } // Arc is itself a ReducerCallRouter. @@ -60,4 +72,8 @@ impl ReducerCallRouter for LocalReducerRouter { let url = self.base_url.clone(); Box::pin(async move { Ok(url) }) } + + fn resolve_base_url_blocking(&self, _database_identity: Identity) -> anyhow::Result { + Ok(self.base_url.clone()) + } } diff --git a/crates/core/src/host/v8/mod.rs b/crates/core/src/host/v8/mod.rs index 5810c67d7bd..6affa7d5386 100644 --- a/crates/core/src/host/v8/mod.rs +++ b/crates/core/src/host/v8/mod.rs @@ -327,8 +327,14 @@ impl JsInstanceEnv { /// /// Returns the handle used by reducers to read from `args` /// as well as the handle used to write the error message, if any. - fn start_funcall(&mut self, name: Identifier, ts: Timestamp, func_type: FuncCallType) { - self.instance_env.start_funcall(name, ts, func_type); + fn start_funcall( + &mut self, + name: Identifier, + ts: Timestamp, + func_type: FuncCallType, + tx_id: Option, + ) { + self.instance_env.start_funcall(name, ts, func_type, tx_id); } /// Returns the name of the most recent reducer to be run in this environment, @@ -613,7 +619,7 @@ enum JsWorkerRequest { }, } -static_assert_size!(CallReducerParams, 192); +static_assert_size!(CallReducerParams, 256); fn send_worker_reply(ctx: &str, reply_tx: JsReplyTx, value: T, trapped: bool) { if reply_tx.send(JsWorkerReply { value, trapped }).is_err() { @@ -1414,6 +1420,7 @@ impl WasmInstance for V8Instance<'_, '_, '_> { .map_result(|call_result| { call_result.map_err(|e| match e { ExecutionError::User(e) => anyhow::Error::msg(e), + ExecutionError::Wounded(e) => anyhow::Error::msg(e), ExecutionError::Recoverable(e) | ExecutionError::Trap(e) => e, }) }); @@ -1440,7 +1447,7 @@ where // Start the timer. // We'd like this tightly around `call`. - env.start_funcall(op.name().clone(), op.timestamp(), op.call_type()); + env.start_funcall(op.name().clone(), op.timestamp(), op.call_type(), op.tx_id()); v8::tc_scope!(scope, scope); let call_result = call(scope, op).map_err(|mut e| { @@ -1543,6 +1550,7 @@ mod test { caller_identity: &Identity::ONE, caller_connection_id: &ConnectionId::ZERO, timestamp: Timestamp::from_micros_since_unix_epoch(24), + tx_id: None, args: &ArgsTuple::nullary(), }; let buffer = v8::ArrayBuffer::new(scope, 4096); diff --git a/crates/core/src/host/v8/syscall/v1.rs b/crates/core/src/host/v8/syscall/v1.rs index 2ccd36620dd..2884ab8463e 100644 --- a/crates/core/src/host/v8/syscall/v1.rs +++ b/crates/core/src/host/v8/syscall/v1.rs @@ -496,6 +496,7 @@ pub(super) fn call_call_reducer( caller_identity: sender, caller_connection_id: conn_id, timestamp, + tx_id: _, args: reducer_args, } = op; // Serialize the arguments. diff --git a/crates/core/src/host/v8/syscall/v2.rs b/crates/core/src/host/v8/syscall/v2.rs index 5f3f6ed3edb..ce4d0ddfff1 100644 --- a/crates/core/src/host/v8/syscall/v2.rs +++ b/crates/core/src/host/v8/syscall/v2.rs @@ -427,6 +427,7 @@ pub(super) fn call_call_reducer<'scope>( caller_identity: sender, caller_connection_id: conn_id, timestamp, + tx_id: _, args: reducer_args, } = op; // Serialize the arguments. diff --git a/crates/core/src/host/wasm_common.rs b/crates/core/src/host/wasm_common.rs index 5d744bc2108..591ef5a8df5 100644 --- a/crates/core/src/host/wasm_common.rs +++ b/crates/core/src/host/wasm_common.rs @@ -357,6 +357,7 @@ pub fn err_to_errno(err: NodesError) -> Result<(NonZeroU16, Option), Nod NodesError::IndexRowNotFound => errno::NO_SUCH_ROW, NodesError::IndexCannotSeekRange => errno::WRONG_INDEX_ALGO, NodesError::ScheduleError(ScheduleError::DelayTooLong(_)) => errno::SCHEDULE_AT_DELAY_TOO_LONG, + NodesError::Wounded(message) => return Ok((errno::WOUNDED_TRANSACTION, Some(message))), NodesError::HttpError(message) => return Ok((errno::HTTP_ERROR, Some(message))), NodesError::Internal(ref internal) => match **internal { DBError::Datastore(DatastoreError::Index(IndexError::UniqueConstraintViolation( diff --git a/crates/core/src/host/wasm_common/module_host_actor.rs b/crates/core/src/host/wasm_common/module_host_actor.rs index 1b0dd352641..796d0c11915 100644 --- a/crates/core/src/host/wasm_common/module_host_actor.rs +++ b/crates/core/src/host/wasm_common/module_host_actor.rs @@ -21,7 +21,7 @@ use crate::host::{ use crate::identity::Identity; use crate::messages::control_db::HostType; use crate::module_host_context::ModuleCreationContext; -use crate::replica_context::ReplicaContext; +use crate::replica_context::{execute_blocking_http, ReplicaContext}; use crate::sql::ast::SchemaViewer; use crate::sql::execute::run_with_instance; use crate::subscription::module_subscription_actor::commit_and_broadcast_event; @@ -252,6 +252,7 @@ impl ExecutionStats { pub enum ExecutionError { User(Box), + Wounded(Box), Recoverable(anyhow::Error), Trap(anyhow::Error), } @@ -404,44 +405,62 @@ impl WasmModuleHostActor { /// /// Called AFTER B's commit is durable. Fire-and-forget: failure is tolerated because /// `recover_2pc_coordinator` on A will retransmit COMMIT on restart. -async fn send_ack_commit_to_coordinator( - client: reqwest::Client, - router: std::sync::Arc, - auth_token: Option, +fn _send_ack_commit_to_coordinator( + client: &reqwest::blocking::Client, + router: &std::sync::Arc, + auth_token: &Option, coordinator_identity: crate::identity::Identity, - prepare_id: String, + prepare_id: &str, ) { - let base_url = match router.resolve_base_url(coordinator_identity).await { - Ok(url) => url, - Err(e) => { - log::warn!("2PC ack-commit: cannot resolve coordinator URL: {e}"); - return; - } - }; - let url = format!( - "{}/v1/database/{}/2pc/ack-commit/{}", - base_url, - coordinator_identity.to_hex(), - prepare_id, - ); - let mut req = client.post(&url); - if let Some(token) = &auth_token { - req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); - } - match req.send().await { - Ok(resp) if resp.status().is_success() => { - log::info!("2PC ack-commit: notified coordinator for {prepare_id}"); - } - Ok(resp) => { - log::warn!( - "2PC ack-commit: coordinator returned {} for {prepare_id}", - resp.status() - ); + let client = client.clone(); + let router = router.clone(); + let auth_token = auth_token.clone(); + let prepare_id = prepare_id.to_owned(); + + std::thread::spawn(move || { + let base_url = match router.resolve_base_url_blocking(coordinator_identity) { + Ok(url) => url, + Err(e) => { + log::warn!("2PC ack-commit: cannot resolve coordinator URL: {e}"); + return; + } + }; + let url = format!( + "{}/v1/database/{}/2pc/ack-commit/{}", + base_url, + coordinator_identity.to_hex(), + prepare_id, + ); + let mut req = client.post(&url); + if let Some(token) = &auth_token { + req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); } - Err(e) => { - log::warn!("2PC ack-commit: transport error for {prepare_id}: {e}"); + let Ok(request) = req.build() else { return }; + match execute_blocking_http(&client, request, |resp| Ok(resp.status())) { + Ok(status) if status.is_success() => { + log::info!("2PC ack-commit: notified coordinator for {prepare_id}"); + } + Ok(status) => { + log::warn!("2PC ack-commit: coordinator returned {status} for {prepare_id}"); + } + Err(e) => { + log::warn!("2PC ack-commit: transport error for {prepare_id}: {e}"); + } } - } + }); +} + +fn wounded_status(replica_ctx: &ReplicaContext, tx_id: spacetimedb_lib::GlobalTxId) -> EventStatus { + let _ = replica_ctx.global_tx_manager.wound(&tx_id); + EventStatus::Wounded(format!( + "distributed transaction {tx_id} was wounded by an older transaction" + )) +} + +fn check_wounded(replica_ctx: &ReplicaContext, tx_id: Option) -> Option { + tx_id + .filter(|tx_id| replica_ctx.global_tx_manager.is_wounded(tx_id)) + .map(|tx_id| wounded_status(replica_ctx, tx_id)) } impl WasmModuleHostActor { @@ -634,9 +653,11 @@ impl WasmModuleInstance { coordinator_identity: crate::identity::Identity, prepared_tx: tokio::sync::oneshot::Sender<(ReducerCallResult, Option)>, decision_rx: std::sync::mpsc::Receiver, + _global_tx_lock_guard: crate::host::global_tx::GlobalTxLockGuard, ) { let stdb = self.instance.replica_ctx().relational_db().clone(); let replica_ctx = self.instance.replica_ctx().clone(); + let global_tx_id = params.tx_id; // Extract recovery info before params are consumed. let recovery_reducer_name = self @@ -652,7 +673,7 @@ impl WasmModuleInstance { let recovery_timestamp_micros = params.timestamp.to_micros_since_unix_epoch(); // Step 1: run the reducer and hold the write lock open. - let (mut tx, event, client, trapped) = crate::callgrind_flag::invoke_allowing_callgrind(|| { + let (mut tx, mut event, client, trapped) = crate::callgrind_flag::invoke_allowing_callgrind(|| { self.common.run_reducer_no_commit(None, params, &mut self.instance) }); self.trapped = trapped; @@ -660,6 +681,13 @@ impl WasmModuleInstance { let energy_quanta_used = event.energy_quanta_used; let total_duration = event.host_execution_duration; + if let Some(status) = check_wounded(&replica_ctx, global_tx_id) + && matches!(event.status, EventStatus::Committed(_)) + { + event.status = status; + event.reducer_return_value = None; + } + if !matches!(event.status, EventStatus::Committed(_)) { // Reducer failed — roll back and signal failure; no marker was written. let res = ReducerCallResult { @@ -670,6 +698,12 @@ impl WasmModuleInstance { let return_value = event.reducer_return_value.clone(); let _ = prepared_tx.send((res, return_value)); let _ = stdb.rollback_mut_tx(tx); + if let Some(tx_id) = global_tx_id { + replica_ctx + .global_tx_manager + .mark_state(&tx_id, crate::host::global_tx::GlobalTxState::Aborted); + replica_ctx.global_tx_manager.remove_session(&tx_id); + } return; } @@ -696,11 +730,11 @@ impl WasmModuleInstance { // Step 3: wait for the PREPARE marker to be durable before signalling PREPARED. // B must not claim PREPARED until the marker is on disk — if B crashes after // claiming PREPARED but before the marker is durable, recovery has nothing to recover. - if let Some(prepare_offset) = marker_tx_data.tx_offset() { - if let Some(mut durable) = stdb.durable_tx_offset() { - let handle = tokio::runtime::Handle::current(); - let _ = block_on_scoped(&handle, durable.wait_for(prepare_offset)); - } + if let Some(prepare_offset) = marker_tx_data.tx_offset() + && let Some(mut durable) = stdb.durable_tx_offset() + { + let handle = tokio::runtime::Handle::current(); + let _ = block_on_scoped(&handle, durable.wait_for(prepare_offset)); } // Step 4: signal PREPARED. @@ -712,10 +746,30 @@ impl WasmModuleInstance { let return_value = event.reducer_return_value.clone(); let _ = prepared_tx.send((res, return_value)); - // Step 4: wait for coordinator's decision (B never aborts on its own). - let commit = Self::wait_for_2pc_decision(decision_rx, &prepare_id, coordinator_identity, &replica_ctx); + // Step 4: wait for coordinator's decision, but abort early if the local + // transaction is wounded while this participant is prepared. + let commit = !global_tx_id + .map(|tx_id| replica_ctx.global_tx_manager.is_wounded(&tx_id)) + .unwrap_or(false) + && Self::wait_for_2pc_decision( + decision_rx, + &prepare_id, + coordinator_identity, + global_tx_id, + &replica_ctx, + ); if commit { + WORKER_METRICS + .two_pc_transactions_committed_as_participant_total + .with_label_values(&replica_ctx.database.database_identity) + .inc(); + if let Some(tx_id) = global_tx_id { + log::info!( + "2PC participant {} committing prepared transaction {tx_id} ({prepare_id})", + replica_ctx.database.database_identity + ); + } // Delete the marker in the same tx as the reducer changes (atomic commit). if let Err(e) = tx.delete_st_2pc_state(&prepare_id) { log::error!("call_reducer_prepare_and_hold: failed to delete st_2pc_state for {prepare_id}: {e}"); @@ -735,20 +789,19 @@ impl WasmModuleInstance { }); } - // Notify coordinator that B has committed so it can delete its coordinator log entry. - // Fire-and-forget: if this fails, coordinator's recover_2pc_coordinator will retry on - // restart, and B's commit_prepared will then return a harmless "not found" error. - let router = replica_ctx.call_reducer_router.clone(); - let client_http = replica_ctx.call_reducer_client.clone(); - let auth_token = replica_ctx.call_reducer_auth_token.clone(); - tokio::runtime::Handle::current().spawn(send_ack_commit_to_coordinator( - client_http, - router, - auth_token, - coordinator_identity, - prepare_id, - )); + if let Some(tx_id) = global_tx_id { + replica_ctx + .global_tx_manager + .mark_state(&tx_id, crate::host::global_tx::GlobalTxState::Committed); + replica_ctx.global_tx_manager.remove_session(&tx_id); + } } else { + if let Some(tx_id) = global_tx_id { + log::info!( + "2PC participant {} aborting prepared transaction {tx_id} ({prepare_id})", + replica_ctx.database.database_identity + ); + } // ABORT: roll back reducer changes; clean up the already-committed marker. let _ = stdb.rollback_mut_tx(tx); if let Err(e) = stdb.with_auto_commit::<_, _, anyhow::Error>(Workload::Internal, |del_tx| { @@ -758,66 +811,91 @@ impl WasmModuleInstance { "call_reducer_prepare_and_hold: abort: failed to delete st_2pc_state for {prepare_id}: {e}" ); } + if let Some(tx_id) = global_tx_id { + replica_ctx + .global_tx_manager + .mark_state(&tx_id, crate::host::global_tx::GlobalTxState::Aborted); + replica_ctx.global_tx_manager.remove_session(&tx_id); + } } } /// Wait for a 2PC COMMIT or ABORT decision for `prepare_id`. /// - /// First waits on `decision_rx` for up to 60 seconds. If no decision arrives, - /// switches to polling the coordinator's `GET /2pc/status/{prepare_id}` endpoint - /// every 5 seconds until a definitive answer is received. - /// - /// **B never aborts on its own** — ABORT is only returned when A explicitly says so. + /// First waits on `decision_rx` for up to 60 seconds, checking periodically for a + /// local wound signal. If no decision arrives, switches to polling the + /// coordinator's `GET /2pc/status/{prepare_id}` endpoint every 5 seconds until a + /// definitive answer is received or the local transaction is wounded. fn wait_for_2pc_decision( decision_rx: std::sync::mpsc::Receiver, prepare_id: &str, coordinator_identity: crate::identity::Identity, + global_tx_id: Option, replica_ctx: &std::sync::Arc, ) -> bool { - match decision_rx.recv_timeout(Duration::from_secs(60)) { - Ok(commit) => return commit, - Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { - log::warn!("2PC prepare_id={prepare_id}: no decision after 60s, polling coordinator"); + let decision_wait_deadline = std::time::Instant::now() + Duration::from_secs(20); + while std::time::Instant::now() < decision_wait_deadline { + if global_tx_id + .map(|tx_id| replica_ctx.global_tx_manager.is_wounded(&tx_id)) + .unwrap_or(false) + { + log::info!("2PC prepare_id={prepare_id}: local transaction wounded while waiting for decision"); + return false; } - Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => { - log::warn!("2PC prepare_id={prepare_id}: decision channel closed, polling coordinator"); + + let remaining = decision_wait_deadline.saturating_duration_since(std::time::Instant::now()); + let wait_slice = remaining.min(Duration::from_secs(1)); + match decision_rx.recv_timeout(wait_slice) { + Ok(commit) => return commit, + Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {} + Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => { + log::warn!("2PC prepare_id={prepare_id}: decision channel closed, polling coordinator"); + break; + } } } - let handle = tokio::runtime::Handle::current(); - let client = replica_ctx.call_reducer_client.clone(); + log::warn!("2PC prepare_id={prepare_id}: no decision after 60s, polling coordinator"); + + let client = replica_ctx.call_reducer_blocking_client.clone(); let router = replica_ctx.call_reducer_router.clone(); let auth_token = replica_ctx.call_reducer_auth_token.clone(); let prepare_id_owned = prepare_id.to_owned(); loop { - let decision = block_on_scoped( - &handle, - Self::query_coordinator_status( - &client, - &router, - auth_token.clone(), - coordinator_identity, - &prepare_id_owned, - ), + if global_tx_id + .map(|tx_id| replica_ctx.global_tx_manager.is_wounded(&tx_id)) + .unwrap_or(false) + { + log::info!("2PC prepare_id={prepare_id}: local transaction wounded during status polling"); + return false; + } + + let decision = Self::query_coordinator_status( + &client, + &router, + auth_token.clone(), + coordinator_identity, + &prepare_id_owned, ); match decision { Some(commit) => return commit, - None => std::thread::sleep(Duration::from_secs(5)), + None => std::thread::sleep(Duration::from_secs(1)), } } } /// Query `GET /v1/database/{coordinator}/2pc/status/{prepare_id}`. /// - /// Returns `Some(true)` = COMMIT, `Some(false)` = ABORT, `None` = transient error (retry). - async fn query_coordinator_status( - client: &reqwest::Client, + /// Blocks the calling thread. Returns `Some(true)` = COMMIT, `Some(false)` = ABORT, + /// `None` = transient error (retry). + fn query_coordinator_status( + client: &reqwest::blocking::Client, router: &std::sync::Arc, auth_token: Option, coordinator_identity: crate::identity::Identity, prepare_id: &str, ) -> Option { - let base_url = match router.resolve_base_url(coordinator_identity).await { + let base_url = match router.resolve_base_url_blocking(coordinator_identity) { Ok(url) => url, Err(e) => { log::warn!("2PC status poll: cannot resolve coordinator URL: {e}"); @@ -831,16 +909,24 @@ impl WasmModuleInstance { prepare_id, ); let mut req = client.get(&url); - if let Some(token) = &auth_token { + if let Some(ref token) = auth_token { req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); } - match req.send().await { - Ok(resp) if resp.status().is_success() => { - let body = resp.text().await.unwrap_or_default(); - Some(body.trim() == "commit") + let request = match req.build() { + Ok(r) => r, + Err(e) => { + log::warn!("2PC status poll: failed to build request: {e}"); + return None; } - Ok(resp) => { - log::warn!("2PC status poll: coordinator returned {}", resp.status()); + }; + match execute_blocking_http(client, request, |resp| { + let success = resp.status().is_success(); + let body = resp.text().unwrap_or_default(); + Ok((success, body)) + }) { + Ok((true, body)) => Some(body.trim() == "commit"), + Ok((false, _)) => { + log::warn!("2PC status poll: coordinator returned non-success"); None } Err(e) => { @@ -1126,12 +1212,13 @@ impl InstanceCommon { params: CallReducerParams, inst: &mut I, ) -> (ReducerCallResult, Option, bool) { + let managed_global_tx_id = if tx.is_none() { params.tx_id } else { None }; let (mut tx, event, client, trapped) = self.run_reducer_no_commit(tx, params, inst); let energy_quanta_used = event.energy_quanta_used; let total_duration = event.host_execution_duration; - // Take participants before commit so we can write the coordinator log atomically. + // Take participant prepare targets before commit so we can write the coordinator log atomically. let prepared_participants = inst.take_prepared_participants(); // If this coordinator tx is committed and has participants, write coordinator log @@ -1140,7 +1227,7 @@ impl InstanceCommon { // crash recovery). if matches!(event.status, EventStatus::Committed(_)) && !prepared_participants.is_empty() { for (db_identity, prepare_id) in &prepared_participants { - if let Err(e) = tx.insert_st_2pc_coordinator_log(prepare_id, &db_identity.to_hex().to_string()) { + if let Err(e) = tx.insert_st_2pc_coordinator_log(prepare_id, db_identity.to_hex().as_ref()) { log::error!("insert_st_2pc_coordinator_log failed for {prepare_id}: {e}"); } } @@ -1155,11 +1242,19 @@ impl InstanceCommon { let committed = matches!(event.status, EventStatus::Committed(_)); let stdb = self.info.subscriptions.relational_db().clone(); let handle = tokio::runtime::Handle::current(); + let local_db = inst.replica_ctx().database.database_identity; + + if committed { + WORKER_METRICS + .two_pc_transactions_committed_total + .with_label_values(&local_db) + .inc(); + } // Wait for A's coordinator log (committed atomically with the tx) to be // durable before sending COMMIT to B. This guarantees that if A crashes // after sending COMMIT, recovery can retransmit from the durable log. - // Only needed for COMMIT — ABORT carries no durability requirement. + // Only needed for COMMIT; ABORT carries no durability requirement. if committed { if let Some(mut durable_offset) = stdb.durable_tx_offset() { block_on_scoped(&handle, async move { @@ -1170,64 +1265,85 @@ impl InstanceCommon { } } - // Fire-and-forget: send COMMIT/ABORT to each participant. + // Send COMMIT/ABORT to each participant synchronously. // The coordinator log (written atomically with A's tx above) is the - // durability guarantee — if a send fails, recover_2pc_coordinator retransmits - // on restart and recover_2pc_participant polls the status endpoint. - // Blocking the executor here would stall A's next reducer for up to - // 30 s × number of participants with no correctness benefit. - let replica_ctx = inst.replica_ctx().clone(); - handle.spawn(async move { - let client = replica_ctx.call_reducer_client.clone(); - let router = replica_ctx.call_reducer_router.clone(); - let auth_token = replica_ctx.call_reducer_auth_token.clone(); - for (db_identity, prepare_id) in &prepared_participants { - let action = if committed { "commit" } else { "abort" }; - let base_url = match router.resolve_base_url(*db_identity).await { - Ok(url) => url, - Err(e) => { - log::error!("2PC {action}: failed to resolve base URL for {db_identity}: {e}"); - continue; - } - }; - let url = format!( - "{}/v1/database/{}/2pc/{}/{}", - base_url, - db_identity.to_hex(), - action, - prepare_id, - ); - let mut req = client.post(&url); - if let Some(ref token) = auth_token { - req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); + // durability guarantee; if a send fails, recovery retransmits on restart. + let replica_ctx = inst.replica_ctx(); + let client = &replica_ctx.call_reducer_blocking_client; + let router = &replica_ctx.call_reducer_router; + let auth_token = &replica_ctx.call_reducer_auth_token; + + for (db_identity, prepare_id) in &prepared_participants { + let action = if committed { "commit" } else { "abort" }; + let base_url = match router.resolve_base_url_blocking(*db_identity) { + Ok(url) => url, + Err(e) => { + log::error!("2PC {action}: failed to resolve base URL for {db_identity}: {e}"); + continue; + } + }; + let url = format!( + "{}/v1/database/{}/2pc/{}/{}", + base_url, + db_identity.to_hex(), + action, + prepare_id, + ); + let mut req = client.post(&url); + if let Some(token) = auth_token { + req = req.header(http::header::AUTHORIZATION, format!("Bearer {token}")); + } + let request = match req.build() { + Ok(r) => r, + Err(e) => { + log::error!("2PC {action}: failed to build request for {prepare_id} on {db_identity}: {e}"); + continue; } - match req.send().await { - Ok(resp) if resp.status().is_success() => { - log::info!("2PC {action}: {prepare_id} on {db_identity}"); - // B acknowledged COMMIT — remove coordinator log entry - // (best-effort; recovery will clean up on restart if missed). - if committed { - if let Err(e) = stdb - .with_auto_commit::<_, _, anyhow::Error>(Workload::Internal, |del_tx| { - Ok(del_tx.delete_st_2pc_coordinator_log(prepare_id)?) - }) - { - log::warn!("delete_st_2pc_coordinator_log failed for {prepare_id}: {e}"); - } + }; + match execute_blocking_http(client, request, |resp| Ok(resp.status())) { + Ok(status) if status.is_success() => { + log::info!("2PC {action}: {prepare_id} on {db_identity}"); + if committed { + if let Err(e) = replica_ctx + .subscriptions + .relational_db() + .with_auto_commit::<_, _, anyhow::Error>(Workload::Internal, |del_tx| { + Ok(del_tx.delete_st_2pc_coordinator_log(prepare_id)?) + }) + { + log::warn!("delete_st_2pc_coordinator_log failed for {prepare_id}: {e}"); } } - Ok(resp) => { - log::error!( - "2PC {action}: failed for {prepare_id} on {db_identity}: status {}", - resp.status() - ); + } + Ok(status) => { + if committed { + log::error!("2PC {action}: failed for {prepare_id} on {db_identity}: status {status}"); + } else { + log::warn!("2PC {action}: best-effort abort for {prepare_id} on {db_identity} returned status {status}"); } - Err(e) => { + } + Err(e) => { + if committed { log::error!("2PC {action}: transport error for {prepare_id} on {db_identity}: {e}"); + } else { + log::warn!("2PC {action}: best-effort abort transport error for {prepare_id} on {db_identity}: {e}"); } } } - }); + } + } + + if let Some(tx_id) = managed_global_tx_id { + let manager = &inst.replica_ctx().global_tx_manager; + manager.mark_state( + &tx_id, + if matches!(event.status, EventStatus::Committed(_)) { + crate::host::global_tx::GlobalTxState::Committed + } else { + crate::host::global_tx::GlobalTxState::Aborted + }, + ); + manager.remove_session(&tx_id); } let res = ReducerCallResult { @@ -1262,6 +1378,7 @@ impl InstanceCommon { timestamp, caller_identity, caller_connection_id, + tx_id, client, request_id, reducer_id, @@ -1270,7 +1387,7 @@ impl InstanceCommon { } = params; let caller_connection_id_opt = (caller_connection_id != ConnectionId::ZERO).then_some(caller_connection_id); - let replica_ctx = inst.replica_ctx(); + let replica_ctx = inst.replica_ctx().clone(); let stdb = replica_ctx.relational_db(); let info = self.info.clone(); let reducer_def = info.module_def.reducer_by_id(reducer_id); @@ -1286,6 +1403,7 @@ impl InstanceCommon { caller_identity: &caller_identity, caller_connection_id: &caller_connection_id, timestamp, + tx_id, args: &args, }; @@ -1327,6 +1445,7 @@ impl InstanceCommon { ); (EventStatus::FailedUser(err.into()), None) } + Err(ExecutionError::Wounded(err)) => (EventStatus::Wounded(err.into()), None), // We haven't actually committed yet - `commit_and_broadcast_event` will commit // for us and replace this with the actual database update. Ok(return_value) => { @@ -1355,6 +1474,15 @@ impl InstanceCommon { } }; + let status = if let Some(status) = check_wounded(&replica_ctx, tx_id) + && matches!(status, EventStatus::Committed(_)) + { + reducer_return_value = None; + status + } else { + status + }; + // Only re-evaluate and update views if the reducer's execution was successful let (out, trapped) = if !trapped && matches!(status, EventStatus::Committed(_)) { self.call_views_with_tx(tx, caller_identity, inst, timestamp) @@ -1367,11 +1495,16 @@ impl InstanceCommon { vm_metrics.report_total_duration(out.total_duration); vm_metrics.report_abi_duration(out.abi_duration); - let status = match &out.outcome { + let mut status = match &out.outcome { ViewOutcome::BudgetExceeded => EventStatus::OutOfEnergy, ViewOutcome::Failed(err) => EventStatus::FailedInternal(err.clone()), ViewOutcome::Success => status, }; + if let Some(wounded) = check_wounded(&replica_ctx, tx_id) + && matches!(status, EventStatus::Committed(_)) + { + status = wounded; + } if !matches!(status, EventStatus::Committed(_)) { reducer_return_value = None; } @@ -1638,6 +1771,10 @@ impl InstanceCommon { inst.log_traceback("view", &view_name, &anyhow::anyhow!(err)); self.handle_outer_error(&result.stats.energy, &view_name).into() } + (Err(ExecutionError::Wounded(err)), _) => { + inst.log_traceback("view", &view_name, &anyhow::anyhow!(err)); + self.handle_outer_error(&result.stats.energy, &view_name).into() + } (Ok(raw), sender) => { // This is wrapped in a closure to simplify error handling. let outcome: Result = (|| { @@ -2046,6 +2183,9 @@ pub trait InstanceOp { fn name(&self) -> &Identifier; fn timestamp(&self) -> Timestamp; fn call_type(&self) -> FuncCallType; + fn tx_id(&self) -> Option { + None + } } /// Describes a view call in a cheaply shareable way. @@ -2117,6 +2257,7 @@ pub struct ReducerOp<'a> { pub caller_identity: &'a Identity, pub caller_connection_id: &'a ConnectionId, pub timestamp: Timestamp, + pub tx_id: Option, /// The arguments passed to the reducer. pub args: &'a ArgsTuple, } @@ -2131,6 +2272,9 @@ impl InstanceOp for ReducerOp<'_> { fn call_type(&self) -> FuncCallType { FuncCallType::Reducer } + fn tx_id(&self) -> Option { + self.tx_id + } } impl From> for execution_context::ReducerContext { @@ -2141,6 +2285,7 @@ impl From> for execution_context::ReducerContext { caller_identity, caller_connection_id, timestamp, + tx_id: _, args, }: ReducerOp<'_>, ) -> Self { diff --git a/crates/core/src/host/wasmtime/wasm_instance_env.rs b/crates/core/src/host/wasmtime/wasm_instance_env.rs index e23e3da01bb..8bd7e273359 100644 --- a/crates/core/src/host/wasmtime/wasm_instance_env.rs +++ b/crates/core/src/host/wasmtime/wasm_instance_env.rs @@ -17,7 +17,7 @@ use crate::subscription::module_subscription_manager::TransactionOffset; use anyhow::{anyhow, Context as _}; use spacetimedb_data_structures::map::IntMap; use spacetimedb_datastore::locking_tx_datastore::{FuncCallType, MutTxId, ViewCallInfo}; -use spacetimedb_lib::{bsatn, ConnectionId, Identity, Timestamp}; +use spacetimedb_lib::{bsatn, ConnectionId, GlobalTxId, Identity, Timestamp}; use spacetimedb_primitives::errno::HOST_CALL_FAILURE; use spacetimedb_primitives::{errno, ColId}; use spacetimedb_schema::def::ModuleDef; @@ -259,6 +259,7 @@ impl WasmInstanceEnv { args: bytes::Bytes, ts: Timestamp, func_type: FuncCallType, + tx_id: Option, ) -> (BytesSourceId, u32) { // Create the output sink. // Reducers which fail will write their error message here. @@ -267,7 +268,7 @@ impl WasmInstanceEnv { let args = self.create_bytes_source(args).unwrap(); - self.instance_env.start_funcall(name, ts, func_type); + self.instance_env.start_funcall(name, ts, func_type, tx_id); (args, errors) } @@ -1996,11 +1997,9 @@ impl WasmInstanceEnv { let args_buf = mem.deref_slice(args_ptr, args_len)?; let args = bytes::Bytes::copy_from_slice(args_buf); - let handle = tokio::runtime::Handle::current(); - let fut = env + let result = env .instance_env .call_reducer_on_db(database_identity, &reducer_name, args); - let result = super::super::block_on_scoped(&handle, fut); match result { Ok((status, body)) => { @@ -2016,6 +2015,14 @@ impl WasmInstanceEnv { bytes_source.0.write_to(mem, out)?; Ok(errno::HTTP_ERROR.get() as u32) } + Err(NodesError::Wounded(err)) => { + let err_bytes = bsatn::to_vec(&err).with_context(|| { + format!("Failed to BSATN-serialize call_reducer_on_db wounded error: {err:?}") + })?; + let bytes_source = WasmInstanceEnv::create_bytes_source(env, err_bytes.into())?; + bytes_source.0.write_to(mem, out)?; + Ok(errno::WOUNDED_TRANSACTION.get() as u32) + } Err(e) => Err(WasmError::Db(e)), } }) @@ -2024,9 +2031,10 @@ impl WasmInstanceEnv { /// 2PC variant of `call_reducer_on_db`. /// /// Calls the remote database's `/prepare/{reducer}` endpoint instead of `/call/{reducer}`. - /// On success, parses the `X-Prepare-Id` header and stores the participant info in - /// `InstanceEnv::prepared_participants` so the runtime can commit/abort after the - /// coordinator's reducer completes. + /// The coordinator assigns the `prepare_id` before sending the request and stores the + /// participant info in `InstanceEnv::contacted_participants` so the runtime can + /// commit/abort after the coordinator's reducer completes, even if the request is + /// wounded while in flight. /// /// Returns the HTTP status code on success, writing the response body to `*out` /// as a [`BytesSource`]. @@ -2055,20 +2063,12 @@ impl WasmInstanceEnv { let args_buf = mem.deref_slice(args_ptr, args_len)?; let args = bytes::Bytes::copy_from_slice(args_buf); - let handle = tokio::runtime::Handle::current(); - let fut = env + let result = env .instance_env .call_reducer_on_db_2pc(database_identity, &reducer_name, args); - let result = super::super::block_on_scoped(&handle, fut); match result { - Ok((status, body, prepare_id)) => { - // If we got a prepare_id, register this participant. - if let Some(pid) = prepare_id - && status < 300 - { - env.instance_env.prepared_participants.push((database_identity, pid)); - } + Ok((status, body, _prepare_id)) => { let bytes_source = WasmInstanceEnv::create_bytes_source(env, body)?; bytes_source.0.write_to(mem, out)?; Ok(status as u32) @@ -2081,6 +2081,14 @@ impl WasmInstanceEnv { bytes_source.0.write_to(mem, out)?; Ok(errno::HTTP_ERROR.get() as u32) } + Err(NodesError::Wounded(err)) => { + let err_bytes = bsatn::to_vec(&err).with_context(|| { + format!("Failed to BSATN-serialize call_reducer_on_db_2pc wounded error: {err:?}") + })?; + let bytes_source = WasmInstanceEnv::create_bytes_source(env, err_bytes.into())?; + bytes_source.0.write_to(mem, out)?; + Ok(errno::WOUNDED_TRANSACTION.get() as u32) + } Err(e) => Err(WasmError::Db(e)), } }) diff --git a/crates/core/src/host/wasmtime/wasmtime_module.rs b/crates/core/src/host/wasmtime/wasmtime_module.rs index fb01e3a4763..78424746d5b 100644 --- a/crates/core/src/host/wasmtime/wasmtime_module.rs +++ b/crates/core/src/host/wasmtime/wasmtime_module.rs @@ -107,6 +107,17 @@ pub(super) enum ViewResultSinkError { UnexpectedCode(i32), } +const WOUNDED_ERROR_PREFIX: &str = "__STDB_WOUNDED__:"; + +fn decode_reducer_failure(result: Vec) -> ExecutionError { + let message = string_from_utf8_lossy_owned(result); + if let Some(message) = message.strip_prefix(WOUNDED_ERROR_PREFIX) { + ExecutionError::Wounded(message.into()) + } else { + ExecutionError::User(message.into()) + } +} + /// Handle the return code from a function using a result sink. /// /// On success, returns the result bytes. @@ -114,7 +125,7 @@ pub(super) enum ViewResultSinkError { fn handle_result_sink_code(code: i32, result: Vec) -> Result, ExecutionError> { match code { 0 => Ok(result), - CALL_FAILURE => Err(ExecutionError::User(string_from_utf8_lossy_owned(result).into())), + CALL_FAILURE => Err(decode_reducer_failure(result)), _ => Err(ExecutionError::Recoverable(anyhow::anyhow!("unknown return code"))), } } @@ -253,6 +264,7 @@ impl module_host_actor::WasmInstancePre for WasmtimeModule { res.map_err(|e| match e { ExecutionError::User(err) => InitializationError::Setup(err), + ExecutionError::Wounded(err) => InitializationError::Setup(err), ExecutionError::Recoverable(err) | ExecutionError::Trap(err) => { let func = SETUP_DUNDER.to_owned(); InitializationError::RuntimeError { err, func } @@ -463,7 +475,7 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { let (args_source, errors_sink) = store .data_mut() - .start_funcall(reducer_name, args_bytes, op.timestamp, op.call_type()); + .start_funcall(reducer_name, args_bytes, op.timestamp, op.call_type(), op.tx_id); let call_result = call_sync_typed_func( &self.call_reducer, @@ -502,7 +514,7 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { let (args_source, errors_sink) = store .data_mut() - .start_funcall(op.name.clone(), args_bytes, op.timestamp, op.call_type()); + .start_funcall(op.name.clone(), args_bytes, op.timestamp, op.call_type(), None); let call_result = call_view_export( &mut *store, @@ -538,7 +550,7 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { let (args_source, errors_sink) = store .data_mut() - .start_funcall(op.name.clone(), args_bytes, op.timestamp, op.call_type()); + .start_funcall(op.name.clone(), args_bytes, op.timestamp, op.call_type(), None); let call_result = call_view_export( &mut *store, @@ -565,7 +577,7 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { } fn take_prepared_participants(&mut self) -> Vec<(Identity, String)> { - core::mem::take(&mut self.store.data_mut().instance_env_mut().prepared_participants) + core::mem::take(&mut self.store.data_mut().instance_env_mut().contacted_participants) } #[tracing::instrument(level = "trace", skip_all)] @@ -582,10 +594,13 @@ impl module_host_actor::WasmInstance for WasmtimeInstance { let [conn_id_0, conn_id_1] = prepare_connection_id_for_call(op.caller_connection_id); // Prepare arguments to the reducer + the error sink & start timings. - let (args_source, result_sink) = - store - .data_mut() - .start_funcall(op.name.clone(), op.arg_bytes, op.timestamp, FuncCallType::Procedure); + let (args_source, result_sink) = store.data_mut().start_funcall( + op.name.clone(), + op.arg_bytes, + op.timestamp, + FuncCallType::Procedure, + None, + ); let Some(call_procedure) = self.call_procedure.as_ref() else { let res = module_host_actor::ProcedureExecuteResult { diff --git a/crates/core/src/replica_context.rs b/crates/core/src/replica_context.rs index 307dcf9f70c..ab3d695efec 100644 --- a/crates/core/src/replica_context.rs +++ b/crates/core/src/replica_context.rs @@ -3,11 +3,14 @@ use spacetimedb_commitlog::SizeOnDisk; use super::database_logger::DatabaseLogger; use crate::db::relational_db::RelationalDB; use crate::error::DBError; +use crate::host::global_tx::GlobalTxManager; use crate::host::reducer_router::ReducerCallRouter; use crate::messages::control_db::Database; use crate::subscription::module_subscription_actor::ModuleSubscriptions; +use spacetimedb_lib::{GlobalTxId, Timestamp}; use std::io; use std::ops::Deref; +use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -47,10 +50,19 @@ pub struct ReplicaContext { pub replica_id: u64, pub logger: Arc, pub subscriptions: ModuleSubscriptions, - /// Warmed HTTP/2 client for [`crate::host::instance_env::InstanceEnv::call_reducer_on_db`]. + /// Async HTTP/2 client for fire-and-forget coordinator/recovery tasks that run inside + /// tokio async tasks (e.g. `recover_2pc_coordinator`, `send_ack_commit_to_coordinator`). /// /// `reqwest::Client` is internally an `Arc`, so cloning `ReplicaContext` shares the pool. pub call_reducer_client: reqwest::Client, + /// Blocking HTTP client for cross-db calls made directly from the WASM executor thread. + /// + /// Used by [`crate::host::instance_env::InstanceEnv::call_reducer_on_db`] and the + /// 2PC participant's `wait_for_2pc_decision` polling loop, both of which run on the + /// `SingleCoreExecutor` std::thread and must block without yielding to tokio. + /// + /// `reqwest::blocking::Client` is also internally an `Arc`. + pub call_reducer_blocking_client: reqwest::blocking::Client, /// Resolves the HTTP base URL of the leader node for a given database identity. /// /// - Standalone: always returns the local node URL ([`crate::host::reducer_router::LocalReducerRouter`]). @@ -64,10 +76,14 @@ pub struct ReplicaContext { /// /// `None` in contexts where no auth token is configured (e.g. unit tests). pub call_reducer_auth_token: Option, + /// Per-database nonce used when minting reducer transaction ids. + pub tx_id_nonce: Arc, + /// In-memory distributed transaction sessions and lock scheduling state. + pub global_tx_manager: Arc, } impl ReplicaContext { - /// Build a warmed `reqwest::Client` from `config`. + /// Build a warmed async `reqwest::Client` from `config`. /// /// Uses HTTP/2 prior knowledge (h2c) for all connections. /// The server must be configured to accept h2c (HTTP/2 cleartext) connections. @@ -81,6 +97,119 @@ impl ReplicaContext { .build() .expect("failed to build call_reducer_on_db HTTP client") } + + /// Build a warmed blocking `reqwest::blocking::Client` from `config`. + /// + /// Used by the WASM executor thread and 2PC participant polling loop, which block their + /// OS thread synchronously rather than yielding to tokio. + /// + /// `reqwest::blocking::Client::build()` internally creates and drops a mini tokio runtime, + /// which panics if called from inside an async context. We build it on a fresh OS thread + /// so it is safe to call from `async fn` at startup. + pub fn new_call_reducer_blocking_client(config: &CallReducerOnDbConfig) -> reqwest::blocking::Client { + let tcp_keepalive = config.tcp_keepalive; + let pool_idle_timeout = config.pool_idle_timeout; + let pool_max_idle_per_host = config.pool_max_idle_per_host; + let timeout = config.request_timeout; + std::thread::scope(|s| { + s.spawn(move || { + reqwest::blocking::Client::builder() + .tcp_keepalive(tcp_keepalive) + .pool_idle_timeout(pool_idle_timeout) + .pool_max_idle_per_host(pool_max_idle_per_host) + .timeout(timeout) + .http2_prior_knowledge() + .build() + .expect("failed to build call_reducer_on_db blocking HTTP client") + }) + .join() + .expect("blocking client builder thread panicked") + }) + } +} + +/// Outcome of [`execute_blocking_http_cancellable`]. +pub enum HttpOutcome { + Done(reqwest::Result), + Cancelled, +} + +/// Like [`execute_blocking_http`] but polls `should_cancel` every 50 ms while the HTTP +/// call is in-flight. If `should_cancel()` returns `true` the function returns +/// [`HttpOutcome::Cancelled`] immediately; the background HTTP thread is detached and +/// completes on its own (its result is silently discarded). +/// +/// All response reading must happen inside `f` — same rule as [`execute_blocking_http`]. +pub fn execute_blocking_http_cancellable( + client: &reqwest::blocking::Client, + request: reqwest::blocking::Request, + should_cancel: impl Fn() -> bool, + f: F, +) -> HttpOutcome +where + F: FnOnce(reqwest::blocking::Response) -> reqwest::Result + Send + 'static, + T: Send + 'static, +{ + use std::sync::mpsc; + let (tx, rx) = mpsc::channel::>(); + let client = client.clone(); + let handle = std::thread::spawn(move || { + let result = client.execute(request).and_then(f); + let _ = tx.send(result); + }); + let result = loop { + match rx.recv_timeout(std::time::Duration::from_millis(10)) { + Ok(result) => break Some(result), + Err(mpsc::RecvTimeoutError::Timeout) => { + if should_cancel() { + // Drop handle — thread is detached and its result discarded. + return HttpOutcome::Cancelled; + } + } + // Sender dropped without sending → thread panicked. + Err(mpsc::RecvTimeoutError::Disconnected) => break None, + } + }; + match result { + Some(r) => HttpOutcome::Done(r), + None => std::panic::resume_unwind(handle.join().unwrap_err()), + } +} + +/// Execute a blocking reqwest request on a fresh OS thread, processing the response inside +/// that same thread. +/// +/// In debug builds, `reqwest 0.12` calls `wait::enter()` on every I/O operation +/// (`send`, `bytes`, `text`, …). That function creates and immediately drops a mini +/// tokio runtime as a nesting-check, which panics if the calling thread is already +/// inside a tokio `block_on` context (e.g. the `SingleCoreExecutor` WASM thread). +/// +/// By running both the send **and** all response reading inside a scoped OS thread that +/// has no tokio context, the assertion always passes. The closure `f` receives the +/// `Response` and must fully consume it (read body, extract headers, etc.) before +/// returning — do not let the `Response` escape the closure. +pub fn execute_blocking_http( + client: &reqwest::blocking::Client, + request: reqwest::blocking::Request, + f: F, +) -> reqwest::Result +where + F: FnOnce(reqwest::blocking::Response) -> reqwest::Result + Send + 'static, + T: Send + 'static, +{ + let client = client.clone(); + std::thread::scope(|s| { + s.spawn(move || client.execute(request).and_then(f)) + .join() + .unwrap_or_else(|e| std::panic::resume_unwind(e)) + }) +} + +impl ReplicaContext { + pub fn mint_global_tx_id(&self, start_ts: Timestamp) -> GlobalTxId { + let nonce = self.tx_id_nonce.fetch_add(1, Ordering::Relaxed); + GlobalTxId::new(start_ts, self.database.database_identity, nonce, 0) + } } impl ReplicaContext { diff --git a/crates/core/src/startup.rs b/crates/core/src/startup.rs index 79694237edd..a0eeda9da77 100644 --- a/crates/core/src/startup.rs +++ b/crates/core/src/startup.rs @@ -413,10 +413,13 @@ impl Cores { #[cfg(target_os = "linux")] fn cores_to_cpuset(cores: &[CoreId]) -> Option { - cores.iter().copied().try_fold(nix::sched::CpuSet::new(), |mut cpuset, core| { - cpuset.set(core.id).ok()?; - Some(cpuset) - }) + cores + .iter() + .copied() + .try_fold(nix::sched::CpuSet::new(), |mut cpuset, core| { + cpuset.set(core.id).ok()?; + Some(cpuset) + }) } #[cfg(target_os = "linux")] @@ -591,10 +594,7 @@ mod tests { #[cfg(target_os = "linux")] { assert!(split.tokio.workers.is_none()); - assert_eq!( - cpuset_cardinality(split.tokio.blocking.as_ref().unwrap()), - 20 - ); + assert_eq!(cpuset_cardinality(split.tokio.blocking.as_ref().unwrap()), 20); assert!(split.rayon.dedicated.is_none()); assert_eq!(split.rayon.shared.as_ref().unwrap().0, 20); } diff --git a/crates/core/src/subscription/module_subscription_actor.rs b/crates/core/src/subscription/module_subscription_actor.rs index 92f296f3b8c..635808dd48f 100644 --- a/crates/core/src/subscription/module_subscription_actor.rs +++ b/crates/core/src/subscription/module_subscription_actor.rs @@ -346,6 +346,7 @@ impl ModuleSubscriptions { let error = match &event.status { EventStatus::FailedUser(err) => err.clone(), EventStatus::FailedInternal(err) => err.clone(), + EventStatus::Wounded(err) => err.clone(), EventStatus::OutOfEnergy => "reducer ran out of energy".into(), EventStatus::Committed(_) => { tracing::warn!("Unexpected committed status in reducer failure branch"); @@ -1616,7 +1617,10 @@ impl ModuleSubscriptions { *db_update = DatabaseUpdate::from_writes(&tx_data); (read_tx, tx_data, tx_metrics) } - EventStatus::FailedUser(_) | EventStatus::FailedInternal(_) | EventStatus::OutOfEnergy => { + EventStatus::FailedUser(_) + | EventStatus::FailedInternal(_) + | EventStatus::Wounded(_) + | EventStatus::OutOfEnergy => { // If the transaction failed, we need to rollback the mutable tx. // We don't need to do any subscription updates in this case, so we will exit early. diff --git a/crates/core/src/worker_metrics/mod.rs b/crates/core/src/worker_metrics/mod.rs index 5b84e230045..062e032e5b0 100644 --- a/crates/core/src/worker_metrics/mod.rs +++ b/crates/core/src/worker_metrics/mod.rs @@ -48,11 +48,51 @@ metrics_group!( #[labels(caller_identity: Identity)] pub cross_db_reducer_calls_total: IntCounterVec, + #[name = spacetime_2pc_outgoing_prepare_calls_total] + #[help = "Total number of outgoing 2PC prepare calls made by this database while acting as coordinator."] + #[labels(database_identity: Identity)] + pub two_pc_outgoing_prepare_calls_total: IntCounterVec, + + #[name = spacetime_2pc_prepare_calls_received_total] + #[help = "Total number of incoming 2PC prepare calls received by this database."] + #[labels(database_identity: Identity)] + pub two_pc_prepare_calls_received_total: IntCounterVec, + + #[name = spacetime_2pc_transactions_committed_total] + #[help = "Total number of 2PC transactions committed by this database as coordinator after preparing remote participants."] + #[labels(database_identity: Identity)] + pub two_pc_transactions_committed_total: IntCounterVec, + + #[name = spacetime_2pc_transactions_committed_as_participant_total] + #[help = "Total number of 2PC transactions committed by this database as a participant."] + #[labels(database_identity: Identity)] + pub two_pc_transactions_committed_as_participant_total: IntCounterVec, + #[name = spacetime_cross_db_reducer_duration_seconds] #[help = "Duration of cross-database reducer calls in seconds."] #[labels(caller_identity: Identity)] pub cross_db_reducer_duration_seconds: HistogramVec, + #[name = spacetime_transactions_wounded_total] + #[help = "Total number of distributed transactions that were marked wounded."] + #[labels(coordinator_identity: Identity, role: str)] + pub transactions_wounded_total: IntCounterVec, + + #[name = spacetime_2pc_wound_requests_sent_total] + #[help = "Total number of wound requests sent to a remote coordinator."] + #[labels(database_identity: Identity)] + pub two_pc_wound_requests_sent_total: IntCounterVec, + + #[name = spacetime_global_tx_waiting_on_younger_owner_total] + #[help = "Total number of times a transaction tried to acquire the global transaction lock and found itself behind a younger owner."] + #[labels(coordinator_identity: Identity, role: str)] + pub global_tx_waiting_on_younger_owner_total: IntCounterVec, + + #[name = spacetime_global_tx_younger_owner_finished_within_grace_period_total] + #[help = "Total number of times a younger lock owner finished within the wound grace period after blocking an older transaction."] + #[labels(coordinator_identity: Identity, role: str)] + pub global_tx_younger_owner_finished_within_grace_period_total: IntCounterVec, + #[name = jemalloc_active_bytes] #[help = "Number of bytes in jemallocs heap"] #[labels(node_id: str)] diff --git a/crates/datastore/src/execution_context.rs b/crates/datastore/src/execution_context.rs index f2e24a5876e..d740c09aba3 100644 --- a/crates/datastore/src/execution_context.rs +++ b/crates/datastore/src/execution_context.rs @@ -79,7 +79,6 @@ impl TryFrom<&txdata::Inputs> for ReducerContext { let caller_identity = bsatn::from_reader(args)?; let caller_connection_id = bsatn::from_reader(args)?; let timestamp = bsatn::from_reader(args)?; - let name = RawIdentifier::new(&**inputs.reducer_name); let name = ReducerName::new(Identifier::new_assume_valid(name)); diff --git a/crates/datastore/src/locking_tx_datastore/mut_tx.rs b/crates/datastore/src/locking_tx_datastore/mut_tx.rs index 0a27ffa9388..dbdf5a1427e 100644 --- a/crates/datastore/src/locking_tx_datastore/mut_tx.rs +++ b/crates/datastore/src/locking_tx_datastore/mut_tx.rs @@ -2669,9 +2669,7 @@ impl MutTxId { /// Return all rows in `st_2pc_state` (prepared but not yet committed/aborted). /// Used on recovery: each row describes a transaction to resume. pub fn scan_st_2pc_state(&self) -> Result> { - self.iter(ST_2PC_STATE_ID)? - .map(|row| St2pcStateRow::try_from(row)) - .collect() + self.iter(ST_2PC_STATE_ID)?.map(St2pcStateRow::try_from).collect() } /// Insert a row into `st_2pc_coordinator_log` recording that the coordinator has @@ -2712,7 +2710,7 @@ impl MutTxId { /// Used on coordinator crash-recovery to retransmit COMMIT to participants. pub fn scan_st_2pc_coordinator_log(&self) -> Result> { self.iter(ST_2PC_COORDINATOR_LOG_ID)? - .map(|row| St2pcCoordinatorLogRow::try_from(row)) + .map(St2pcCoordinatorLogRow::try_from) .collect() } diff --git a/crates/guard/src/lib.rs b/crates/guard/src/lib.rs index af937910a54..cc96bfc87e9 100644 --- a/crates/guard/src/lib.rs +++ b/crates/guard/src/lib.rs @@ -92,6 +92,8 @@ pub fn ensure_binaries_built() -> PathBuf { use reqwest::blocking::Client; +const SMOKETEST_DEDICATED_DATABASE_CORES: &str = "1"; + pub struct SpacetimeDbGuard { pub child: Child, pub host_url: String, @@ -279,6 +281,10 @@ impl SpacetimeDbGuard { &data_dir_str, "--listen-addr", &address, + // Test-spawned servers should not inherit the CLI's machine-dependent + // default dedicated DB core count. + "--dedicated-database-cores", + SMOKETEST_DEDICATED_DATABASE_CORES, ]; if let Some(ref port) = pg_port_str { args.extend(["--pg-port", port]); diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 2e8b9c08336..0f25a6fad87 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -22,6 +22,7 @@ pub mod operator; pub mod query; pub mod scheduler; pub mod st_var; +pub mod tx_id; pub mod version; pub mod type_def { @@ -47,6 +48,7 @@ pub use spacetimedb_sats::__make_register_reftype; pub use spacetimedb_sats::{self as sats, bsatn, buffer, de, ser}; pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType}; pub use spacetimedb_sats::{AlgebraicValue, ProductValue}; +pub use tx_id::{GlobalTxId, TX_ID_HEADER}; pub const MODULE_ABI_MAJOR_VERSION: u16 = 10; diff --git a/crates/lib/src/tx_id.rs b/crates/lib/src/tx_id.rs new file mode 100644 index 00000000000..7b6aa9072f3 --- /dev/null +++ b/crates/lib/src/tx_id.rs @@ -0,0 +1,77 @@ +use crate::{Identity, SpacetimeType, Timestamp}; +use std::fmt; +use std::str::FromStr; + +/// Header used to propagate distributed reducer transaction ids across remote calls. +pub const TX_ID_HEADER: &str = "X-Spacetime-Tx-Id"; + +/// A distributed reducer transaction identifier. +/// +/// Ordering is primarily by `start_ts`, so this can later support wound-wait. +/// `creator_db` namespaces the id globally, `nonce` breaks ties for +/// multiple transactions started on the same database at the same timestamp, +/// and `attempt` tracks retries of the same logical distributed transaction. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, SpacetimeType)] +#[sats(crate = crate)] +pub struct GlobalTxId { + pub start_ts: Timestamp, + pub creator_db: Identity, + pub nonce: u32, + pub attempt: u32, +} + +impl GlobalTxId { + pub const fn new(start_ts: Timestamp, creator_db: Identity, nonce: u32, attempt: u32) -> Self { + Self { + start_ts, + creator_db, + nonce, + attempt, + } + } + + pub const fn next_attempt(self) -> Self { + Self { + attempt: self.attempt + 1, + ..self + } + } +} + +impl fmt::Display for GlobalTxId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}:{}:{:08x}:{:08x}", + self.start_ts.to_micros_since_unix_epoch(), + self.creator_db.to_hex(), + self.nonce, + self.attempt, + ) + } +} + +impl FromStr for GlobalTxId { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + let mut parts = s.splitn(4, ':'); + let start_ts = parts.next().ok_or("missing tx timestamp")?; + let creator_db = parts.next().ok_or("missing tx creator db")?; + let nonce = parts.next().ok_or("missing tx nonce")?; + let attempt = parts.next().ok_or("missing tx attempt")?; + if parts.next().is_some() { + return Err("too many tx id components"); + } + + let start_ts = start_ts + .parse::() + .map(Timestamp::from_micros_since_unix_epoch) + .map_err(|_| "invalid tx timestamp")?; + let creator_db = Identity::from_hex(creator_db).map_err(|_| "invalid tx creator db")?; + let nonce = u32::from_str_radix(nonce, 16).map_err(|_| "invalid tx nonce")?; + let attempt = u32::from_str_radix(attempt, 16).map_err(|_| "invalid tx attempt")?; + + Ok(Self::new(start_ts, creator_db, nonce, attempt)) + } +} diff --git a/crates/primitives/src/errno.rs b/crates/primitives/src/errno.rs index 5c422941715..7bd79c4d3a8 100644 --- a/crates/primitives/src/errno.rs +++ b/crates/primitives/src/errno.rs @@ -35,6 +35,10 @@ macro_rules! errnos { "ABI call can only be made while within a read-only transaction" ), HTTP_ERROR(21, "The HTTP request failed"), + WOUNDED_TRANSACTION( + 22, + "The distributed transaction was wounded by an older transaction" + ), ); }; } diff --git a/crates/smoketests/tests/smoketests/cross_db_chain.rs b/crates/smoketests/tests/smoketests/cross_db_chain.rs new file mode 100644 index 00000000000..002ca4ff649 --- /dev/null +++ b/crates/smoketests/tests/smoketests/cross_db_chain.rs @@ -0,0 +1,135 @@ +use spacetimedb_smoketests::Smoketest; + +/// Module code shared by all three databases (A = initiator, B = relay, C = receiver). +/// +/// Tables: +/// - `PingLog(id auto_inc PK, message: String, priority: u32)` — records pings received. +/// +/// Reducers: +/// - `record_ping(payload)` — terminal: inserts payload into ping_log. +/// - `relay_ping(c_hex, payload)` — middle hop: forwards payload to C via `record_ping`, +/// then records a "relay:" entry locally so B's participation is verifiable. +/// - `chain_ping(b_hex, c_hex, message, priority)` — initiator: encodes a PingPayload and +/// calls `relay_ping` on B (which in turn calls `record_ping` on C), then records a +/// "chain:" entry locally. +/// +/// This exercises a two-hop cross-DB call chain (A → B → C). +const MODULE_CODE: &str = r#" +use spacetimedb::{log, ReducerContext, Table, Identity, SpacetimeType}; + +#[derive(SpacetimeType)] +pub struct PingPayload { + pub message: String, + pub priority: u32, +} + +#[spacetimedb::table(accessor = ping_log, public)] +pub struct PingLog { + #[primary_key] + #[auto_inc] + id: u64, + message: String, + priority: u32, +} + +/// Terminal hop: stores the payload in ping_log. +#[spacetimedb::reducer] +pub fn record_ping(ctx: &ReducerContext, payload: PingPayload) { + log::info!("record_ping: message={} priority={}", payload.message, payload.priority); + ctx.db.ping_log().insert(PingLog { id: 0, message: payload.message, priority: payload.priority }); +} + +/// Middle hop: forwards payload to C via `record_ping`, then records locally. +#[spacetimedb::reducer] +pub fn relay_ping(ctx: &ReducerContext, c_hex: String, payload: PingPayload) { + log::info!("relay_ping: forwarding to {c_hex}"); + let c = Identity::from_hex(&c_hex).expect("invalid C identity hex"); + let args = spacetimedb::spacetimedb_lib::bsatn::to_vec(&(PingPayload { message: payload.message.clone(), priority: payload.priority },)) + .expect("failed to encode args for record_ping"); + spacetimedb::remote_reducer::call_reducer_on_db(c, "record_ping", &args) + .unwrap_or_else(|e| panic!("relay_ping: call to C failed: {e}")); + ctx.db.ping_log().insert(PingLog { id: 0, message: format!("relay:{}", payload.message), priority: payload.priority }); +} + +/// Initiating hop: calls `relay_ping` on B (which calls `record_ping` on C), then records locally. +#[spacetimedb::reducer] +pub fn chain_ping(ctx: &ReducerContext, b_hex: String, c_hex: String, message: String, priority: u32) { + log::info!("chain_ping: starting A->B->C chain, message={message}"); + let b = Identity::from_hex(&b_hex).expect("invalid B identity hex"); + let payload = PingPayload { message: message.clone(), priority }; + let args = spacetimedb::spacetimedb_lib::bsatn::to_vec(&(c_hex, payload)) + .expect("failed to encode args for relay_ping"); + spacetimedb::remote_reducer::call_reducer_on_db(b, "relay_ping", &args) + .unwrap_or_else(|e| panic!("chain_ping: call to B failed: {e}")); + ctx.db.ping_log().insert(PingLog { id: 0, message: format!("chain:{message}"), priority }); +} +"#; + +fn query_ping_log(test: &Smoketest, db_identity: &str) -> String { + test.spacetime(&[ + "sql", + "--server", + &test.server_url, + db_identity, + "SELECT message, priority FROM ping_log ORDER BY id", + ]) + .unwrap_or_else(|e| panic!("sql query on {db_identity} failed: {e}")) +} + +/// Two-hop chain: A.chain_ping → B.relay_ping → C.record_ping. +/// +/// After the call: +/// - C's ping_log has the original message. +/// - B's ping_log has "relay:", confirming B was the relay. +/// - A's ping_log has "chain:", confirming A initiated the chain. +#[test] +fn test_cross_db_chain_call() { + let pid = std::process::id(); + let db_a_name = format!("chain-a-{pid}"); + let db_b_name = format!("chain-b-{pid}"); + let db_c_name = format!("chain-c-{pid}"); + + let mut test = Smoketest::builder().module_code(MODULE_CODE).autopublish(false).build(); + + // Publish C first (terminal), then B (relay), then A (initiator). + test.publish_module_named(&db_c_name, false) + .expect("failed to publish C"); + let db_c_identity = test.database_identity.clone().expect("C identity not set"); + + test.publish_module_named(&db_b_name, false) + .expect("failed to publish B"); + let db_b_identity = test.database_identity.clone().expect("B identity not set"); + + test.publish_module_named(&db_a_name, false) + .expect("failed to publish A"); + let db_a_identity = test.database_identity.clone().expect("A identity not set"); + + // Initiate the A → B → C chain. + test.call("chain_ping", &[&db_b_identity, &db_c_identity, "hello-chain", "7"]) + .expect("chain_ping call failed"); + + // C should have the original message. + let c_log = query_ping_log(&test, &db_c_identity); + assert!( + c_log.contains("hello-chain"), + "C ping_log should contain 'hello-chain' (original message), got:\n{c_log}" + ); + assert!( + c_log.contains('7'), + "C ping_log should contain priority 7, got:\n{c_log}" + ); + + // B should have "relay:hello-chain", confirming it was the relay hop. + let b_log = query_ping_log(&test, &db_b_identity); + assert!( + b_log.contains("relay:hello-chain"), + "B ping_log should contain 'relay:hello-chain', got:\n{b_log}" + ); + + // A should have "chain:hello-chain", confirming it initiated the chain. + let a_log = query_ping_log(&test, &db_a_identity); + assert!( + a_log.contains("chain:hello-chain"), + "A ping_log should contain 'chain:hello-chain', got:\n{a_log}" + ); +} diff --git a/crates/standalone/config.toml b/crates/standalone/config.toml index e7d1aec30ea..b5cda9d27a1 100644 --- a/crates/standalone/config.toml +++ b/crates/standalone/config.toml @@ -34,4 +34,8 @@ directives = [ # Apply a V8 heap limit in MiB. Set to 0 to use V8's default limit. # heap-limit-mb = 0 +[global-tx] +# Delay before an older transaction wounds a younger owner. +# wound-grace-period = "10ms" + # vim: set nowritebackup: << otherwise triggers cargo-watch diff --git a/crates/standalone/src/lib.rs b/crates/standalone/src/lib.rs index d7264f409a3..4f676947eaa 100644 --- a/crates/standalone/src/lib.rs +++ b/crates/standalone/src/lib.rs @@ -10,7 +10,7 @@ use async_trait::async_trait; use clap::{ArgMatches, Command}; use http::StatusCode; use spacetimedb::client::ClientActorIndex; -use spacetimedb::config::{CertificateAuthority, MetadataFile, V8HeapPolicyConfig}; +use spacetimedb::config::{CertificateAuthority, GlobalTxConfig, MetadataFile, V8HeapPolicyConfig}; use spacetimedb::db; use spacetimedb::db::persistence::LocalPersistenceProvider; use spacetimedb::energy::{EnergyBalance, EnergyQuanta, NullEnergyMonitor}; @@ -45,6 +45,7 @@ pub struct StandaloneOptions { pub db_config: db::Config, pub websocket: WebSocketOptions, pub v8_heap_policy: V8HeapPolicyConfig, + pub global_tx: GlobalTxConfig, /// HTTP base URL of this node's API server (e.g. `"http://127.0.0.1:3000"`). /// Used to configure the `LocalReducerRouter` so that cross-DB reducer calls /// reach the correct address when the server listens on a dynamic port. @@ -86,6 +87,7 @@ impl StandaloneEnv { data_dir, config.db_config, config.v8_heap_policy, + config.global_tx, program_store.clone(), energy_monitor, persistence_provider, @@ -658,6 +660,7 @@ mod tests { }, websocket: WebSocketOptions::default(), v8_heap_policy: V8HeapPolicyConfig::default(), + global_tx: GlobalTxConfig::default(), local_api_url: "http://127.0.0.1:3000".to_owned(), }; diff --git a/crates/standalone/src/subcommands/start.rs b/crates/standalone/src/subcommands/start.rs index 4601da4ecd4..c31724f3c26 100644 --- a/crates/standalone/src/subcommands/start.rs +++ b/crates/standalone/src/subcommands/start.rs @@ -244,6 +244,7 @@ pub async fn exec(args: &ArgMatches, db_cores: JobCores) -> anyhow::Result<()> { db_config, websocket: config.websocket, v8_heap_policy: config.common.v8_heap_policy, + global_tx: config.common.global_tx, local_api_url: format!("http://127.0.0.1:{local_port}"), }, &certs, diff --git a/modules/chain-call-repro/Cargo.toml b/modules/chain-call-repro/Cargo.toml new file mode 100644 index 00000000000..37e966784f4 --- /dev/null +++ b/modules/chain-call-repro/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "chain-call-repro" +version = "0.0.0" +edition.workspace = true + +[lib] +crate-type = ["cdylib"] +bench = false + +[dependencies] +spacetimedb = { path = "../../crates/bindings", features = ["unstable"] } diff --git a/modules/chain-call-repro/run.sh b/modules/chain-call-repro/run.sh new file mode 100755 index 00000000000..7d1fa8074ae --- /dev/null +++ b/modules/chain-call-repro/run.sh @@ -0,0 +1,302 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +SERVER="${SPACETIME_SERVER:-local}" +A_CLIENTS="${A_CLIENTS:-4}" +B_CLIENTS="${B_CLIENTS:-4}" +ITERATIONS="${ITERATIONS:-25}" +BURN_ITERS="${BURN_ITERS:-0}" +HOLD_ITERS="${HOLD_ITERS:-25000000}" +RUN_ID="$(date +%Y%m%d%H%M%S)-$$" +DB_A="independent-repro-a-${RUN_ID}" +DB_B="independent-repro-b-${RUN_ID}" +DB_C="independent-repro-c-${RUN_ID}" +TMP_DIR="$(mktemp -d)" +PUBLISH_FIRST=1 +RUN_FOREVER=0 +RUN_A_CLIENTS=1 +RUN_B_CLIENTS=1 +DB_A_ID="${DB_A_ID:-}" +DB_B_ID="${DB_B_ID:-}" +DB_C_ID="${DB_C_ID:-}" + +usage() { + cat <<'EOF' +Usage: ./run.sh [options] + +Options: + --skip-publish Reuse existing DB identities from DB_A_ID, DB_B_ID, and DB_C_ID. + --forever Run client calls forever instead of stopping after ITERATIONS. + --only-a-client Run only A clients. + --only-b-client Run only B clients. + --help Show this help. + +Environment: + SPACETIME_SERVER Server name. Defaults to local. + A_CLIENTS Number of A clients. Defaults to 4. + B_CLIENTS Number of B clients. Defaults to 4. + ITERATIONS Calls per client when not using --forever. Defaults to 25. + BURN_ITERS Burn work per reducer call. Defaults to 0. + HOLD_ITERS Burn work after remote prepare succeeds. Defaults to 25000000. + DB_A_ID Required with --skip-publish. + DB_B_ID Required with --skip-publish. + DB_C_ID Required with --skip-publish. +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --skip-publish) + PUBLISH_FIRST=0 + ;; + --forever) + RUN_FOREVER=1 + ;; + --only-a-client) + RUN_B_CLIENTS=0 + ;; + --only-b-client) + RUN_A_CLIENTS=0 + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "unknown option: $1" >&2 + echo >&2 + usage >&2 + exit 1 + ;; + esac + shift +done + +if [[ "$RUN_A_CLIENTS" -eq 0 && "$RUN_B_CLIENTS" -eq 0 ]]; then + echo "nothing to do: both A and B clients were disabled" >&2 + exit 1 +fi + +cleanup() { + local pids + + pids="$(jobs -pr)" || true + if [[ -n "$pids" ]]; then + kill $pids 2>/dev/null || true + fi + rm -rf "$TMP_DIR" +} +trap cleanup EXIT + +publish_db() { + local db_name="$1" + local output + local identity + + output="$(cd "$SCRIPT_DIR" && spacetime publish --server "$SERVER" --clear-database -y "$db_name")" + printf '%s\n' "$output" >&2 + + identity="$( + printf '%s\n' "$output" \ + | grep -Eo 'identity: [0-9a-fA-F]+' \ + | sed 's/^identity: //' \ + | tail -n1 + )" + if [[ -z "$identity" ]]; then + echo "failed to parse identity from publish output for $db_name" >&2 + return 1 + fi + + printf '%s\n' "$identity" +} + +run_a_client() { + local client_id="$1" + local failures=0 + local log_file + local failure_log_file + local tmp_output + local seq + + log_file="$TMP_DIR/a-client-${client_id}.log" + failure_log_file="$TMP_DIR/a-client-${client_id}.failures.log" + tmp_output="$TMP_DIR/a-client-${client_id}.tmp" + : >"$failure_log_file" + seq=1 + while :; do + if ! ( + cd "$SCRIPT_DIR" && + spacetime call --server "$SERVER" -- "$DB_A_ID" call_b_from_a \ + "$DB_B_ID" \ + "a-client-${client_id}" \ + "$seq" \ + "a-msg-client-${client_id}-seq-${seq}" \ + "$BURN_ITERS" \ + "$HOLD_ITERS" + ) >"$tmp_output" 2>&1; then + failures=$((failures + 1)) + { + echo "=== failure $failures for a-client-${client_id} seq $seq ===" + cat "$tmp_output" + echo + } >>"$failure_log_file" + fi + mv "$tmp_output" "$log_file" + + if [[ "$RUN_FOREVER" -eq 0 && "$seq" -ge "$ITERATIONS" ]]; then + break + fi + seq=$((seq + 1)) + done + + printf '%s\n' "$failures" >"$TMP_DIR/a-client-${client_id}.failures" +} + +run_b_client() { + local client_id="$1" + local failures=0 + local log_file + local failure_log_file + local tmp_output + local seq + + log_file="$TMP_DIR/b-client-${client_id}.log" + failure_log_file="$TMP_DIR/b-client-${client_id}.failures.log" + tmp_output="$TMP_DIR/b-client-${client_id}.tmp" + : >"$failure_log_file" + seq=1 + while :; do + if ! ( + cd "$SCRIPT_DIR" && + spacetime call --server "$SERVER" -- "$DB_B_ID" call_c_from_b \ + "$DB_C_ID" \ + "b-client-${client_id}" \ + "$seq" \ + "b-msg-client-${client_id}-seq-${seq}" \ + "$BURN_ITERS" \ + "$HOLD_ITERS" + ) >"$tmp_output" 2>&1; then + failures=$((failures + 1)) + { + echo "=== failure $failures for b-client-${client_id} seq $seq ===" + cat "$tmp_output" + echo + } >>"$failure_log_file" + fi + mv "$tmp_output" "$log_file" + + if [[ "$RUN_FOREVER" -eq 0 && "$seq" -ge "$ITERATIONS" ]]; then + break + fi + seq=$((seq + 1)) + done + + printf '%s\n' "$failures" >"$TMP_DIR/b-client-${client_id}.failures" +} + +if [[ "$PUBLISH_FIRST" -eq 1 ]]; then + echo "Publishing independent-call repro module to A, B, and C on server '$SERVER'..." + DB_C_ID="$(publish_db "$DB_C")" + DB_B_ID="$(publish_db "$DB_B")" + DB_A_ID="$(publish_db "$DB_A")" +else + if [[ -z "$DB_A_ID" || -z "$DB_B_ID" || -z "$DB_C_ID" ]]; then + echo "DB_A_ID, DB_B_ID, and DB_C_ID are required with --skip-publish" >&2 + exit 1 + fi +fi + +echo "A identity: $DB_A_ID" +echo "B identity: $DB_B_ID" +echo "C identity: $DB_C_ID" +echo "Client logs directory: $TMP_DIR" +if [[ "$RUN_A_CLIENTS" -eq 1 ]]; then + echo "A client logs: $TMP_DIR/a-client-.log" +fi +if [[ "$RUN_B_CLIENTS" -eq 1 ]]; then + echo "B client logs: $TMP_DIR/b-client-.log" +fi +if [[ "$RUN_FOREVER" -eq 1 ]]; then + echo "Starting clients in forever mode..." +else + echo "Starting clients with $ITERATIONS calls each..." +fi +echo "Prepare hold burn iters: $HOLD_ITERS" +echo "Workload note: run both A and B clients together to create contention on B and drive wound flow." +echo "A clients enabled: $RUN_A_CLIENTS ($A_CLIENTS configured)" +echo "B clients enabled: $RUN_B_CLIENTS ($B_CLIENTS configured)" + +if [[ "$RUN_A_CLIENTS" -eq 1 ]]; then + for ((client_id = 1; client_id <= A_CLIENTS; client_id++)); do + run_a_client "$client_id" & + done +fi +if [[ "$RUN_B_CLIENTS" -eq 1 ]]; then + for ((client_id = 1; client_id <= B_CLIENTS; client_id++)); do + run_b_client "$client_id" & + done +fi +wait + +if [[ "$RUN_FOREVER" -eq 1 ]]; then + exit 0 +fi + +A_FAILURES=0 +if [[ "$RUN_A_CLIENTS" -eq 1 ]]; then + for ((client_id = 1; client_id <= A_CLIENTS; client_id++)); do + client_failures="$(cat "$TMP_DIR/a-client-${client_id}.failures")" + A_FAILURES=$((A_FAILURES + client_failures)) + done +fi + +B_FAILURES=0 +if [[ "$RUN_B_CLIENTS" -eq 1 ]]; then + for ((client_id = 1; client_id <= B_CLIENTS; client_id++)); do + client_failures="$(cat "$TMP_DIR/b-client-${client_id}.failures")" + B_FAILURES=$((B_FAILURES + client_failures)) + done +fi + +A_SUCCESSES=$((RUN_A_CLIENTS * A_CLIENTS * ITERATIONS - A_FAILURES)) +B_SUCCESSES=$((RUN_B_CLIENTS * B_CLIENTS * ITERATIONS - B_FAILURES)) +TOTAL_FAILURES=$((A_FAILURES + B_FAILURES)) + +echo "Successful A->B calls: $A_SUCCESSES" +echo "Failed A->B calls: $A_FAILURES" +echo "Successful B->C calls: $B_SUCCESSES" +echo "Failed B->C calls: $B_FAILURES" + +if [[ "$RUN_A_CLIENTS" -eq 1 && "$A_SUCCESSES" -gt 0 ]]; then + (cd "$SCRIPT_DIR" && spacetime call --server "$SERVER" -- "$DB_A_ID" assert_kind_count sent_to_b "$A_SUCCESSES") + (cd "$SCRIPT_DIR" && spacetime call --server "$SERVER" -- "$DB_B_ID" assert_kind_count recv_from_a "$A_SUCCESSES") +fi + +if [[ "$RUN_B_CLIENTS" -eq 1 && "$B_SUCCESSES" -gt 0 ]]; then + (cd "$SCRIPT_DIR" && spacetime call --server "$SERVER" -- "$DB_B_ID" assert_kind_count sent_to_c "$B_SUCCESSES") + (cd "$SCRIPT_DIR" && spacetime call --server "$SERVER" -- "$DB_C_ID" assert_kind_count recv_from_b "$B_SUCCESSES") +fi + +if [[ "$TOTAL_FAILURES" -ne 0 ]]; then + echo + echo "At least one client call failed. Sample failure logs:" + find "$TMP_DIR" -name '*-client-*.failures.log' -type f -size +0c -print \ + | head -n 10 \ + | while read -r log_file; do + echo "--- $log_file ---" + cat "$log_file" + done + exit 1 +fi + +echo +echo "Run complete." +echo "Flows exercised independently:" +echo "A reducer calls B" +echo "B reducer calls C" +echo "Use these database identities to inspect state manually if needed:" +echo "A: $DB_A_ID" +echo "B: $DB_B_ID" +echo "C: $DB_C_ID" diff --git a/modules/chain-call-repro/src/lib.rs b/modules/chain-call-repro/src/lib.rs new file mode 100644 index 00000000000..bd2f1631779 --- /dev/null +++ b/modules/chain-call-repro/src/lib.rs @@ -0,0 +1,137 @@ +use spacetimedb::{ + remote_reducer::{into_reducer_error_message, RemoteCallError}, + Identity, ReducerContext, SpacetimeType, Table, +}; + +#[derive(SpacetimeType, Clone)] +pub struct CallPayload { + pub client_label: String, + pub seq: u64, + pub message: String, +} + +#[spacetimedb::table(accessor = call_log, public)] +pub struct CallLog { + #[primary_key] + #[auto_inc] + id: u64, + kind: String, + client_label: String, + seq: u64, + message: String, +} + +fn log_entry(ctx: &ReducerContext, kind: &str, payload: &CallPayload) { + ctx.db.call_log().insert(CallLog { + id: 0, + kind: kind.to_string(), + client_label: payload.client_label.clone(), + seq: payload.seq, + message: payload.message.clone(), + }); +} + +fn burn(iters: u64) { + if iters == 0 { + return; + } + + let mut x = 1u64; + for i in 0..iters { + x = x.wrapping_mul(6364136223846793005u64).wrapping_add(i | 1); + } + if x == 0 { + panic!("impossible burn result"); + } +} + +#[spacetimedb::reducer] +pub fn record_on_b(ctx: &ReducerContext, payload: CallPayload, burn_iters: u64) -> Result<(), String> { + burn(burn_iters); + log_entry(ctx, "recv_from_a", &payload); + Ok(()) +} + +#[spacetimedb::reducer] +pub fn record_on_c(ctx: &ReducerContext, payload: CallPayload, burn_iters: u64) -> Result<(), String> { + burn(burn_iters); + log_entry(ctx, "recv_from_b", &payload); + Ok(()) +} + +#[spacetimedb::reducer] +pub fn call_b_from_a( + ctx: &ReducerContext, + b_hex: String, + client_label: String, + seq: u64, + message: String, + burn_iters: u64, + hold_iters: u64, +) -> Result<(), String> { + burn(burn_iters); + + let b = Identity::from_hex(&b_hex).expect("invalid B identity"); + let payload = CallPayload { + client_label, + seq, + message, + }; + let args = spacetimedb::spacetimedb_lib::bsatn::to_vec(&(payload.clone(), burn_iters)) + .expect("failed to encode args for record_on_b"); + spacetimedb::remote_reducer::call_reducer_on_db_2pc(b, "record_on_b", &args) + .map_err(|e| match e { + RemoteCallError::Wounded(_) => into_reducer_error_message(e), + _ => format!("call_b_from_a: call to B failed: {e}"), + })?; + + // Hold A open after B is prepared so B keeps its global-tx admission lock + // long enough for concurrent work on B to contend and trigger wound flow. + burn(hold_iters); + + log_entry(ctx, "sent_to_b", &payload); + Ok(()) +} + +#[spacetimedb::reducer] +pub fn call_c_from_b( + ctx: &ReducerContext, + c_hex: String, + client_label: String, + seq: u64, + message: String, + burn_iters: u64, + hold_iters: u64, +) -> Result<(), String> { + burn(burn_iters); + + let c = Identity::from_hex(&c_hex).expect("invalid C identity"); + let payload = CallPayload { + client_label, + seq, + message, + }; + let args = spacetimedb::spacetimedb_lib::bsatn::to_vec(&(payload.clone(), burn_iters)) + .expect("failed to encode args for record_on_c"); + spacetimedb::remote_reducer::call_reducer_on_db_2pc(c, "record_on_c", &args) + .map_err(|e| match e { + RemoteCallError::Wounded(_) => into_reducer_error_message(e), + _ => format!("call_c_from_b: call to C failed: {e}"), + })?; + + // Hold B open after C is prepared so B remains the global-tx owner while + // A-originated work attempts to prepare on B. + burn(hold_iters); + + log_entry(ctx, "sent_to_c", &payload); + Ok(()) +} + +#[spacetimedb::reducer] +pub fn assert_kind_count(ctx: &ReducerContext, kind: String, expected: u64) -> Result<(), String> { + let actual = ctx.db.call_log().iter().filter(|row| row.kind == kind).count() as u64; + if actual != expected { + return Err(format!("expected kind '{kind}' count {expected}, got {actual}")); + } + Ok(()) +} diff --git a/modules/tpcc-metrics/src/lib.rs b/modules/tpcc-metrics/src/lib.rs index 96721512d34..823e2f0b13d 100644 --- a/modules/tpcc-metrics/src/lib.rs +++ b/modules/tpcc-metrics/src/lib.rs @@ -1,5 +1,7 @@ use spacetimedb::{reducer, table, ReducerContext, Table}; +const BUCKET_SIZE_MS: u64 = 1_000; + #[table(accessor = state, public)] pub struct State { #[primary_key] @@ -9,46 +11,101 @@ pub struct State { pub run_end_ms: u64, pub measure_start_ms: u64, pub measure_end_ms: u64, + pub warehouse_count: u64, +} - pub order_count: u64, +#[table(accessor = txn, public)] +pub struct Txn { + #[primary_key] + #[auto_inc] + pub id: u64, pub measurement_time_ms: u64, + pub latency_ms: u16, } -#[reducer] -pub fn reset(ctx: &ReducerContext, warmup_duration_ms: u64, measure_start_ms: u64, measure_end_ms: u64) { +#[table(accessor = txn_bucket, public)] +pub struct TxnBucket { + #[primary_key] + pub bucket_start_ms: u64, + pub count: u64, +} + +fn clear_tables(ctx: &ReducerContext) { for row in ctx.db.state().iter() { - ctx.db.state().delete(row); + ctx.db.state().id().delete(row.id); + } + + for row in ctx.db.txn().iter() { + ctx.db.txn().id().delete(row.id); } + for row in ctx.db.txn_bucket().iter() { + ctx.db.txn_bucket() + .bucket_start_ms() + .delete(row.bucket_start_ms); + } +} + +#[reducer] +pub fn reset( + ctx: &ReducerContext, + warehouse_count: u64, + warmup_duration_ms: u64, + measure_start_ms: u64, + measure_end_ms: u64, +) { + clear_tables(ctx); + ctx.db.state().insert(State { id: 0, - order_count: 0, - measurement_time_ms: 0, run_start_ms: measure_start_ms - warmup_duration_ms, run_end_ms: measure_end_ms + warmup_duration_ms, measure_start_ms, measure_end_ms, + warehouse_count, }); } #[reducer] pub fn clear_state(ctx: &ReducerContext) { - for row in ctx.db.state().iter() { - ctx.db.state().delete(row); - } + clear_tables(ctx); } #[reducer] -pub fn register_completed_order(ctx: &ReducerContext) { - // We intentionally do not check if the current time is within the measurement window, - // this is the driver's reponsibility - +pub fn record_txn(ctx: &ReducerContext, latency_ms: u16) { let current_time_ms = ctx.timestamp.to_duration_since_unix_epoch().unwrap().as_millis() as u64; - let mut state = ctx.db.state().id().find(0).unwrap(); + ctx.db.txn().insert(Txn { + id: 0, + measurement_time_ms: current_time_ms, + latency_ms, + }); +} - state.order_count += 1; - state.measurement_time_ms = current_time_ms; +#[reducer] +pub fn record_txn_bucket(ctx: &ReducerContext) { + let current_time_ms = ctx + .timestamp + .to_duration_since_unix_epoch() + .unwrap() + .as_millis() as u64; + let Some(state) = ctx.db.state().id().find(0) else { + return; + }; - ctx.db.state().id().update(state); + let bucket_offset_ms = current_time_ms.saturating_sub(state.run_start_ms); + let bucket_start_ms = + state.run_start_ms + ((bucket_offset_ms / BUCKET_SIZE_MS) * BUCKET_SIZE_MS); + + if let Some(bucket) = ctx.db.txn_bucket().bucket_start_ms().find(bucket_start_ms) { + ctx.db.txn_bucket().bucket_start_ms().update(TxnBucket { + count: bucket.count.saturating_add(1), + ..bucket + }); + } else { + ctx.db.txn_bucket().insert(TxnBucket { + bucket_start_ms, + count: 1, + }); + } } diff --git a/modules/tpcc-results/coordinator/tpcc-1774908053099/summary.json b/modules/tpcc-results/coordinator/tpcc-1774908053099/summary.json new file mode 100644 index 00000000000..d942bebebd3 --- /dev/null +++ b/modules/tpcc-results/coordinator/tpcc-1774908053099/summary.json @@ -0,0 +1,348 @@ +{ + "run_id": "tpcc-1774908053099", + "driver_count": 1, + "drivers": [ + "driver-135613" + ], + "generated_at_ms": 1774908449186, + "total_transactions": 129, + "tpmc_like": 134.0, + "transaction_mix": { + "delivery": 3.10077519379845, + "new_order": 51.93798449612403, + "order_status": 5.426356589147287, + "payment": 37.2093023255814, + "stock_level": 2.3255813953488373 + }, + "conformance": { + "new_order_rollbacks": 0, + "new_order_total": 67, + "new_order_remote_order_lines": 0, + "new_order_total_order_lines": 649, + "payment_remote": 0, + "payment_total": 48, + "payment_by_last_name": 31, + "order_status_by_last_name": 3, + "order_status_total": 7, + "delivery_queued": 4, + "delivery_completed": 5, + "delivery_processed_districts": 50, + "delivery_skipped_districts": 0 + }, + "transactions": { + "delivery": { + "count": 4, + "success": 4, + "failure": 0, + "mean_latency_ms": 8.75, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 9, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 4, + "sum_ms": 35, + "max_ms": 9 + } + }, + "new_order": { + "count": 67, + "success": 67, + "failure": 0, + "mean_latency_ms": 9.701492537313433, + "p50_latency_ms": 10, + "p95_latency_ms": 20, + "p99_latency_ms": 20, + "max_latency_ms": 11, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 54, + 13, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 67, + "sum_ms": 650, + "max_ms": 11 + } + }, + "order_status": { + "count": 7, + "success": 7, + "failure": 0, + "mean_latency_ms": 1.7142857142857142, + "p50_latency_ms": 2, + "p95_latency_ms": 2, + "p99_latency_ms": 2, + "max_latency_ms": 2, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 2, + 5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 7, + "sum_ms": 12, + "max_ms": 2 + } + }, + "payment": { + "count": 48, + "success": 48, + "failure": 0, + "mean_latency_ms": 8.458333333333334, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 10, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 48, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 48, + "sum_ms": 406, + "max_ms": 10 + } + }, + "stock_level": { + "count": 3, + "success": 3, + "failure": 0, + "mean_latency_ms": 5.666666666666667, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 6, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 1, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 3, + "sum_ms": 17, + "max_ms": 6 + } + } + }, + "delivery": { + "queued": 4, + "completed": 5, + "pending": 0, + "processed_districts": 50, + "skipped_districts": 0, + "completion_mean_ms": 24.8, + "completion_p50_ms": 50, + "completion_p95_ms": 50, + "completion_p99_ms": 50, + "completion_max_ms": 40, + "completion_histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 0, + 1, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 5, + "sum_ms": 124, + "max_ms": 40 + } + } +} \ No newline at end of file diff --git a/modules/tpcc-results/coordinator/tpcc-1774908671807/summary.json b/modules/tpcc-results/coordinator/tpcc-1774908671807/summary.json new file mode 100644 index 00000000000..3b0bfc4d75e --- /dev/null +++ b/modules/tpcc-results/coordinator/tpcc-1774908671807/summary.json @@ -0,0 +1,348 @@ +{ + "run_id": "tpcc-1774908671807", + "driver_count": 1, + "drivers": [ + "driver-137679" + ], + "generated_at_ms": 1774909101311, + "total_transactions": 151, + "tpmc_like": 138.0, + "transaction_mix": { + "delivery": 3.9735099337748343, + "new_order": 45.6953642384106, + "order_status": 4.635761589403973, + "payment": 41.05960264900662, + "stock_level": 4.635761589403973 + }, + "conformance": { + "new_order_rollbacks": 0, + "new_order_total": 69, + "new_order_remote_order_lines": 0, + "new_order_total_order_lines": 735, + "payment_remote": 0, + "payment_total": 62, + "payment_by_last_name": 39, + "order_status_by_last_name": 4, + "order_status_total": 7, + "delivery_queued": 6, + "delivery_completed": 10, + "delivery_processed_districts": 100, + "delivery_skipped_districts": 0 + }, + "transactions": { + "delivery": { + "count": 6, + "success": 6, + "failure": 0, + "mean_latency_ms": 8.166666666666666, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 10, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 6, + "sum_ms": 49, + "max_ms": 10 + } + }, + "new_order": { + "count": 69, + "success": 69, + "failure": 0, + "mean_latency_ms": 9.91304347826087, + "p50_latency_ms": 10, + "p95_latency_ms": 20, + "p99_latency_ms": 20, + "max_latency_ms": 16, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 51, + 18, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 69, + "sum_ms": 684, + "max_ms": 16 + } + }, + "order_status": { + "count": 7, + "success": 7, + "failure": 0, + "mean_latency_ms": 1.1428571428571428, + "p50_latency_ms": 1, + "p95_latency_ms": 2, + "p99_latency_ms": 2, + "max_latency_ms": 2, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 6, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 7, + "sum_ms": 8, + "max_ms": 2 + } + }, + "payment": { + "count": 62, + "success": 62, + "failure": 0, + "mean_latency_ms": 8.661290322580646, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 20, + "max_latency_ms": 11, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 60, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 62, + "sum_ms": 537, + "max_ms": 11 + } + }, + "stock_level": { + "count": 7, + "success": 7, + "failure": 0, + "mean_latency_ms": 5.428571428571429, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 6, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 3, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 7, + "sum_ms": 38, + "max_ms": 6 + } + } + }, + "delivery": { + "queued": 6, + "completed": 10, + "pending": 0, + "processed_districts": 100, + "skipped_districts": 0, + "completion_mean_ms": 21.0, + "completion_p50_ms": 50, + "completion_p95_ms": 50, + "completion_p99_ms": 50, + "completion_max_ms": 23, + "completion_histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 0, + 4, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 10, + "sum_ms": 210, + "max_ms": 23 + } + } +} \ No newline at end of file diff --git a/modules/tpcc-results/coordinator/tpcc-1774909349292/summary.json b/modules/tpcc-results/coordinator/tpcc-1774909349292/summary.json new file mode 100644 index 00000000000..5b26641bdf2 --- /dev/null +++ b/modules/tpcc-results/coordinator/tpcc-1774909349292/summary.json @@ -0,0 +1,348 @@ +{ + "run_id": "tpcc-1774909349292", + "driver_count": 1, + "drivers": [ + "driver-152145" + ], + "generated_at_ms": 1774909758833, + "total_transactions": 143, + "tpmc_like": 132.0, + "transaction_mix": { + "delivery": 4.895104895104895, + "new_order": 48.25174825174825, + "order_status": 1.3986013986013985, + "payment": 42.65734265734266, + "stock_level": 2.797202797202797 + }, + "conformance": { + "new_order_rollbacks": 3, + "new_order_total": 69, + "new_order_remote_order_lines": 0, + "new_order_total_order_lines": 719, + "payment_remote": 0, + "payment_total": 61, + "payment_by_last_name": 35, + "order_status_by_last_name": 0, + "order_status_total": 2, + "delivery_queued": 7, + "delivery_completed": 9, + "delivery_processed_districts": 90, + "delivery_skipped_districts": 0 + }, + "transactions": { + "delivery": { + "count": 7, + "success": 7, + "failure": 0, + "mean_latency_ms": 7.857142857142857, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 10, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 7, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 7, + "sum_ms": 55, + "max_ms": 10 + } + }, + "new_order": { + "count": 69, + "success": 66, + "failure": 3, + "mean_latency_ms": 9.782608695652174, + "p50_latency_ms": 10, + "p95_latency_ms": 20, + "p99_latency_ms": 20, + "max_latency_ms": 19, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 1, + 2, + 0, + 43, + 23, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 69, + "sum_ms": 675, + "max_ms": 19 + } + }, + "order_status": { + "count": 2, + "success": 2, + "failure": 0, + "mean_latency_ms": 1.0, + "p50_latency_ms": 1, + "p95_latency_ms": 1, + "p99_latency_ms": 1, + "max_latency_ms": 1, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 2, + "sum_ms": 2, + "max_ms": 1 + } + }, + "payment": { + "count": 61, + "success": 61, + "failure": 0, + "mean_latency_ms": 8.721311475409836, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 20, + "max_latency_ms": 14, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 59, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 61, + "sum_ms": 532, + "max_ms": 14 + } + }, + "stock_level": { + "count": 4, + "success": 4, + "failure": 0, + "mean_latency_ms": 6.25, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 7, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 4, + "sum_ms": 25, + "max_ms": 7 + } + } + }, + "delivery": { + "queued": 7, + "completed": 9, + "pending": 0, + "processed_districts": 90, + "skipped_districts": 0, + "completion_mean_ms": 22.0, + "completion_p50_ms": 50, + "completion_p95_ms": 50, + "completion_p99_ms": 50, + "completion_max_ms": 23, + "completion_histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 0, + 0, + 9, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 9, + "sum_ms": 198, + "max_ms": 23 + } + } +} \ No newline at end of file diff --git a/modules/tpcc-results/coordinator/tpcc-1774910938757/summary.json b/modules/tpcc-results/coordinator/tpcc-1774910938757/summary.json new file mode 100644 index 00000000000..da574a497ae --- /dev/null +++ b/modules/tpcc-results/coordinator/tpcc-1774910938757/summary.json @@ -0,0 +1,348 @@ +{ + "run_id": "tpcc-1774910938757", + "driver_count": 1, + "drivers": [ + "driver-179375" + ], + "generated_at_ms": 1774911335944, + "total_transactions": 137, + "tpmc_like": 140.0, + "transaction_mix": { + "delivery": 5.839416058394161, + "new_order": 51.824817518248175, + "order_status": 4.37956204379562, + "payment": 34.306569343065696, + "stock_level": 3.6496350364963503 + }, + "conformance": { + "new_order_rollbacks": 1, + "new_order_total": 71, + "new_order_remote_order_lines": 0, + "new_order_total_order_lines": 699, + "payment_remote": 0, + "payment_total": 47, + "payment_by_last_name": 28, + "order_status_by_last_name": 2, + "order_status_total": 6, + "delivery_queued": 8, + "delivery_completed": 13, + "delivery_processed_districts": 130, + "delivery_skipped_districts": 0 + }, + "transactions": { + "delivery": { + "count": 8, + "success": 8, + "failure": 0, + "mean_latency_ms": 7.875, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 9, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 8, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 8, + "sum_ms": 63, + "max_ms": 9 + } + }, + "new_order": { + "count": 71, + "success": 70, + "failure": 1, + "mean_latency_ms": 9.450704225352112, + "p50_latency_ms": 10, + "p95_latency_ms": 20, + "p99_latency_ms": 20, + "max_latency_ms": 13, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 1, + 0, + 56, + 14, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 71, + "sum_ms": 671, + "max_ms": 13 + } + }, + "order_status": { + "count": 6, + "success": 6, + "failure": 0, + "mean_latency_ms": 1.3333333333333333, + "p50_latency_ms": 1, + "p95_latency_ms": 2, + "p99_latency_ms": 2, + "max_latency_ms": 2, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 4, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 6, + "sum_ms": 8, + "max_ms": 2 + } + }, + "payment": { + "count": 47, + "success": 47, + "failure": 0, + "mean_latency_ms": 8.340425531914894, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 10, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 47, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 47, + "sum_ms": 392, + "max_ms": 10 + } + }, + "stock_level": { + "count": 5, + "success": 5, + "failure": 0, + "mean_latency_ms": 6.0, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 6, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 5, + "sum_ms": 30, + "max_ms": 6 + } + } + }, + "delivery": { + "queued": 8, + "completed": 13, + "pending": 0, + "processed_districts": 130, + "skipped_districts": 0, + "completion_mean_ms": 22.615384615384617, + "completion_p50_ms": 50, + "completion_p95_ms": 50, + "completion_p99_ms": 50, + "completion_max_ms": 33, + "completion_histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 0, + 2, + 11, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 13, + "sum_ms": 294, + "max_ms": 33 + } + } +} \ No newline at end of file diff --git a/modules/tpcc-results/coordinator/tpcc-1774911348130/summary.json b/modules/tpcc-results/coordinator/tpcc-1774911348130/summary.json new file mode 100644 index 00000000000..9de79650048 --- /dev/null +++ b/modules/tpcc-results/coordinator/tpcc-1774911348130/summary.json @@ -0,0 +1,348 @@ +{ + "run_id": "tpcc-1774911348130", + "driver_count": 1, + "drivers": [ + "driver-184437" + ], + "generated_at_ms": 1774911743197, + "total_transactions": 144, + "tpmc_like": 124.0, + "transaction_mix": { + "delivery": 4.166666666666667, + "new_order": 43.75, + "order_status": 6.25, + "payment": 43.75, + "stock_level": 2.0833333333333335 + }, + "conformance": { + "new_order_rollbacks": 1, + "new_order_total": 63, + "new_order_remote_order_lines": 0, + "new_order_total_order_lines": 610, + "payment_remote": 0, + "payment_total": 63, + "payment_by_last_name": 35, + "order_status_by_last_name": 3, + "order_status_total": 9, + "delivery_queued": 6, + "delivery_completed": 7, + "delivery_processed_districts": 70, + "delivery_skipped_districts": 0 + }, + "transactions": { + "delivery": { + "count": 6, + "success": 6, + "failure": 0, + "mean_latency_ms": 8.0, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 9, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 6, + "sum_ms": 48, + "max_ms": 9 + } + }, + "new_order": { + "count": 63, + "success": 62, + "failure": 1, + "mean_latency_ms": 9.634920634920634, + "p50_latency_ms": 10, + "p95_latency_ms": 20, + "p99_latency_ms": 20, + "max_latency_ms": 12, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 1, + 0, + 51, + 11, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 63, + "sum_ms": 607, + "max_ms": 12 + } + }, + "order_status": { + "count": 9, + "success": 9, + "failure": 0, + "mean_latency_ms": 1.2222222222222223, + "p50_latency_ms": 1, + "p95_latency_ms": 2, + "p99_latency_ms": 2, + "max_latency_ms": 2, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 7, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 9, + "sum_ms": 11, + "max_ms": 2 + } + }, + "payment": { + "count": 63, + "success": 63, + "failure": 0, + "mean_latency_ms": 8.714285714285714, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 20, + "max_latency_ms": 11, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 62, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 63, + "sum_ms": 549, + "max_ms": 11 + } + }, + "stock_level": { + "count": 3, + "success": 3, + "failure": 0, + "mean_latency_ms": 5.666666666666667, + "p50_latency_ms": 5, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 7, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 2, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 3, + "sum_ms": 17, + "max_ms": 7 + } + } + }, + "delivery": { + "queued": 6, + "completed": 7, + "pending": 0, + "processed_districts": 70, + "skipped_districts": 0, + "completion_mean_ms": 21.714285714285715, + "completion_p50_ms": 20, + "completion_p95_ms": 50, + "completion_p99_ms": 50, + "completion_max_ms": 32, + "completion_histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 0, + 4, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 7, + "sum_ms": 152, + "max_ms": 32 + } + } +} \ No newline at end of file diff --git a/modules/tpcc-results/tpcc-1774902157443/driver-110838/txn_events.ndjson b/modules/tpcc-results/tpcc-1774902157443/driver-110838/txn_events.ndjson new file mode 100644 index 00000000000..e69de29bb2d diff --git a/modules/tpcc-results/tpcc-1774908053099/driver-135613/summary.json b/modules/tpcc-results/tpcc-1774908053099/driver-135613/summary.json new file mode 100644 index 00000000000..cdc99d2238a --- /dev/null +++ b/modules/tpcc-results/tpcc-1774908053099/driver-135613/summary.json @@ -0,0 +1,354 @@ +{ + "run_id": "tpcc-1774908053099", + "driver_id": "driver-135613", + "uri": "http://127.0.0.1:3000", + "database": "tpcc-0", + "terminal_start": 1, + "terminals": 10, + "warehouse_count": 1, + "warmup_secs": 5, + "measure_secs": 30, + "measure_start_ms": 1774908117135, + "measure_end_ms": 1774908417135, + "generated_at_ms": 1774908449154, + "total_transactions": 129, + "tpmc_like": 134.0, + "transaction_mix": { + "delivery": 3.10077519379845, + "new_order": 51.93798449612403, + "order_status": 5.426356589147287, + "payment": 37.2093023255814, + "stock_level": 2.3255813953488373 + }, + "conformance": { + "new_order_rollbacks": 0, + "new_order_total": 67, + "new_order_remote_order_lines": 0, + "new_order_total_order_lines": 649, + "payment_remote": 0, + "payment_total": 48, + "payment_by_last_name": 31, + "order_status_by_last_name": 3, + "order_status_total": 7, + "delivery_queued": 4, + "delivery_completed": 5, + "delivery_processed_districts": 50, + "delivery_skipped_districts": 0 + }, + "transactions": { + "delivery": { + "count": 4, + "success": 4, + "failure": 0, + "mean_latency_ms": 8.75, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 9, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 4, + "sum_ms": 35, + "max_ms": 9 + } + }, + "new_order": { + "count": 67, + "success": 67, + "failure": 0, + "mean_latency_ms": 9.701492537313433, + "p50_latency_ms": 10, + "p95_latency_ms": 20, + "p99_latency_ms": 20, + "max_latency_ms": 11, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 54, + 13, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 67, + "sum_ms": 650, + "max_ms": 11 + } + }, + "order_status": { + "count": 7, + "success": 7, + "failure": 0, + "mean_latency_ms": 1.7142857142857142, + "p50_latency_ms": 2, + "p95_latency_ms": 2, + "p99_latency_ms": 2, + "max_latency_ms": 2, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 2, + 5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 7, + "sum_ms": 12, + "max_ms": 2 + } + }, + "payment": { + "count": 48, + "success": 48, + "failure": 0, + "mean_latency_ms": 8.458333333333334, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 10, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 48, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 48, + "sum_ms": 406, + "max_ms": 10 + } + }, + "stock_level": { + "count": 3, + "success": 3, + "failure": 0, + "mean_latency_ms": 5.666666666666667, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 6, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 1, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 3, + "sum_ms": 17, + "max_ms": 6 + } + } + }, + "delivery": { + "queued": 4, + "completed": 5, + "pending": 0, + "processed_districts": 50, + "skipped_districts": 0, + "completion_mean_ms": 24.8, + "completion_p50_ms": 50, + "completion_p95_ms": 50, + "completion_p99_ms": 50, + "completion_max_ms": 40, + "completion_histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 0, + 1, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 5, + "sum_ms": 124, + "max_ms": 40 + } + } +} \ No newline at end of file diff --git a/modules/tpcc-results/tpcc-1774908053099/driver-135613/txn_events.ndjson b/modules/tpcc-results/tpcc-1774908053099/driver-135613/txn_events.ndjson new file mode 100644 index 00000000000..d093f177fc4 --- /dev/null +++ b/modules/tpcc-results/tpcc-1774908053099/driver-135613/txn_events.ndjson @@ -0,0 +1,129 @@ +{"timestamp_ms":1774908121466,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908123028,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908124423,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"order_status","success":true,"latency_ms":2,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908127571,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908128766,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908128813,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908130617,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908131168,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908132024,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908132322,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"order_status","success":true,"latency_ms":2,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908133381,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908133904,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908136054,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908139719,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908140427,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908140680,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":8,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908142152,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908144719,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":8,"transaction":"order_status","success":true,"latency_ms":2,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908145003,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908150378,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908150956,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908152727,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"delivery","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908160203,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908162820,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908163790,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908165653,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908167462,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908167470,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908167668,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908169378,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908169794,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908174548,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908182635,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908183780,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908186875,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908188634,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908190523,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908193785,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"delivery","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908197437,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908198027,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908198704,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908200820,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908206045,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908209758,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908213591,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908213883,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908214554,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908214560,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908215166,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908218653,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908221646,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908226323,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908228315,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908231465,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908231609,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908238965,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908241141,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908242514,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908245736,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908247835,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908249290,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908251095,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908251502,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908252595,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908254738,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908254986,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908255198,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"stock_level","success":true,"latency_ms":5,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908256398,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908256476,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908260433,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908260960,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908263528,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908263857,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908274343,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908277278,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908277947,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908278524,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908279834,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908282623,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908285219,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908286879,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908287687,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"order_status","success":true,"latency_ms":2,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908289779,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908298806,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908304704,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908305322,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908306557,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908309276,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908310464,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908312748,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908315859,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908318039,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908319278,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908321339,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908322737,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"delivery","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908322956,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908332566,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908333067,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908339175,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908339942,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908344256,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908345147,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908345855,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908347891,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908352498,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908354355,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908354914,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908355858,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908364693,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908366305,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908369761,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908371345,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908372605,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908375644,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908377972,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":9,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908378750,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908380882,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908389858,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908393234,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908394970,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908397496,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908403370,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908404418,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908404676,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"order_status","success":true,"latency_ms":2,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908406292,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908407090,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908408620,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":3,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908411669,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908416742,"run_id":"tpcc-1774908053099","driver_id":"driver-135613","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} diff --git a/modules/tpcc-results/tpcc-1774908671807/driver-137679/summary.json b/modules/tpcc-results/tpcc-1774908671807/driver-137679/summary.json new file mode 100644 index 00000000000..4b928b9fd8c --- /dev/null +++ b/modules/tpcc-results/tpcc-1774908671807/driver-137679/summary.json @@ -0,0 +1,354 @@ +{ + "run_id": "tpcc-1774908671807", + "driver_id": "driver-137679", + "uri": "http://127.0.0.1:3000", + "database": "tpcc-0", + "terminal_start": 1, + "terminals": 10, + "warehouse_count": 1, + "warmup_secs": 5, + "measure_secs": 30, + "measure_start_ms": 1774908738409, + "measure_end_ms": 1774909038409, + "generated_at_ms": 1774909101276, + "total_transactions": 151, + "tpmc_like": 138.0, + "transaction_mix": { + "delivery": 3.9735099337748343, + "new_order": 45.6953642384106, + "order_status": 4.635761589403973, + "payment": 41.05960264900662, + "stock_level": 4.635761589403973 + }, + "conformance": { + "new_order_rollbacks": 0, + "new_order_total": 69, + "new_order_remote_order_lines": 0, + "new_order_total_order_lines": 735, + "payment_remote": 0, + "payment_total": 62, + "payment_by_last_name": 39, + "order_status_by_last_name": 4, + "order_status_total": 7, + "delivery_queued": 6, + "delivery_completed": 10, + "delivery_processed_districts": 100, + "delivery_skipped_districts": 0 + }, + "transactions": { + "delivery": { + "count": 6, + "success": 6, + "failure": 0, + "mean_latency_ms": 8.166666666666666, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 10, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 6, + "sum_ms": 49, + "max_ms": 10 + } + }, + "new_order": { + "count": 69, + "success": 69, + "failure": 0, + "mean_latency_ms": 9.91304347826087, + "p50_latency_ms": 10, + "p95_latency_ms": 20, + "p99_latency_ms": 20, + "max_latency_ms": 16, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 51, + 18, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 69, + "sum_ms": 684, + "max_ms": 16 + } + }, + "order_status": { + "count": 7, + "success": 7, + "failure": 0, + "mean_latency_ms": 1.1428571428571428, + "p50_latency_ms": 1, + "p95_latency_ms": 2, + "p99_latency_ms": 2, + "max_latency_ms": 2, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 6, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 7, + "sum_ms": 8, + "max_ms": 2 + } + }, + "payment": { + "count": 62, + "success": 62, + "failure": 0, + "mean_latency_ms": 8.661290322580646, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 20, + "max_latency_ms": 11, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 60, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 62, + "sum_ms": 537, + "max_ms": 11 + } + }, + "stock_level": { + "count": 7, + "success": 7, + "failure": 0, + "mean_latency_ms": 5.428571428571429, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 6, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 3, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 7, + "sum_ms": 38, + "max_ms": 6 + } + } + }, + "delivery": { + "queued": 6, + "completed": 10, + "pending": 0, + "processed_districts": 100, + "skipped_districts": 0, + "completion_mean_ms": 21.0, + "completion_p50_ms": 50, + "completion_p95_ms": 50, + "completion_p99_ms": 50, + "completion_max_ms": 23, + "completion_histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 0, + 4, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 10, + "sum_ms": 210, + "max_ms": 23 + } + } +} \ No newline at end of file diff --git a/modules/tpcc-results/tpcc-1774908671807/driver-137679/txn_events.ndjson b/modules/tpcc-results/tpcc-1774908671807/driver-137679/txn_events.ndjson new file mode 100644 index 00000000000..7ed6c30ae41 --- /dev/null +++ b/modules/tpcc-results/tpcc-1774908671807/driver-137679/txn_events.ndjson @@ -0,0 +1,151 @@ +{"timestamp_ms":1774908741236,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908743144,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908745345,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"delivery","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908745536,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"payment","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908747970,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908750867,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908752176,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908753345,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908753762,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"stock_level","success":true,"latency_ms":5,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908754690,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908755680,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908756491,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908756824,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908757528,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908759914,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908761289,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908768639,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"delivery","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908769022,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908769271,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908771231,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908771729,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908772471,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908778051,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908778141,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908778285,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908788859,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908789941,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908794317,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908795015,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908797224,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908798877,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908799635,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908800468,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908801338,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908802675,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908809532,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"stock_level","success":true,"latency_ms":5,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908812586,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908815847,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908816410,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908817156,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908818314,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"payment","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908824321,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908824760,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908827202,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908830371,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":12,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908830900,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908831270,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908841063,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908844253,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908845238,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908847694,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908848742,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908850201,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908856059,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":16,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908856397,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908856988,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908859195,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908859849,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908860382,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908861753,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908862375,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908865409,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908865911,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908868093,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908870806,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908877438,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908878128,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908878643,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908886023,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908886748,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908888063,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908888124,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908892497,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"order_status","success":true,"latency_ms":2,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908896306,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908896525,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908897237,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908897929,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908898905,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908901547,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908901594,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908902030,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908905548,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908906349,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908913260,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908917908,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908922766,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908925875,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908926201,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908926731,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908928442,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908928749,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908928979,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908929587,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908931036,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908931723,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908934351,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908938128,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908939870,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"delivery","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908940272,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908942231,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908949939,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908952038,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"delivery","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908953561,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908954601,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908955039,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908959635,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908960337,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908961355,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908962292,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"delivery","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908962880,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908964628,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908965602,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908969654,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908970138,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908970170,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908971268,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908974579,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908979707,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908981536,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908981735,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908983018,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908984912,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908985963,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908988567,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908989507,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908994028,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908997021,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908997286,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774908998737,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909000134,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909000799,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909000822,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909004720,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909006840,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909007299,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909011923,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909014143,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909014650,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"delivery","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909015718,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909017990,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909018446,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909020104,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909024175,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909027737,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909031245,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":9,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909032175,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":5,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909032611,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":2,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909033487,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909034569,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909036146,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":12,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909037046,"run_id":"tpcc-1774908671807","driver_id":"driver-137679","terminal_id":10,"transaction":"stock_level","success":true,"latency_ms":4,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} diff --git a/modules/tpcc-results/tpcc-1774909172568/driver-138825/txn_events.ndjson b/modules/tpcc-results/tpcc-1774909172568/driver-138825/txn_events.ndjson new file mode 100644 index 00000000000..fabc60b29ac --- /dev/null +++ b/modules/tpcc-results/tpcc-1774909172568/driver-138825/txn_events.ndjson @@ -0,0 +1,30 @@ +{"timestamp_ms":1774909244308,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909248022,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909251460,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":1,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909258885,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909262108,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909262639,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909264356,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909266079,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":9,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909266363,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909267213,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909271161,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":3,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909278965,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":6,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909281550,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909285071,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909285455,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909288286,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":9,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909288804,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909289708,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":1,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909290317,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909290947,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909291594,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909293561,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909294141,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909295389,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909298121,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":1,"transaction":"stock_level","success":true,"latency_ms":5,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909300404,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909301778,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909303514,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909304011,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909311056,"run_id":"tpcc-1774909172568","driver_id":"driver-138825","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":8," \ No newline at end of file diff --git a/modules/tpcc-results/tpcc-1774909349292/driver-152145/summary.json b/modules/tpcc-results/tpcc-1774909349292/driver-152145/summary.json new file mode 100644 index 00000000000..8422823beeb --- /dev/null +++ b/modules/tpcc-results/tpcc-1774909349292/driver-152145/summary.json @@ -0,0 +1,354 @@ +{ + "run_id": "tpcc-1774909349292", + "driver_id": "driver-152145", + "uri": "http://127.0.0.1:3000", + "database": "tpcc-0", + "terminal_start": 1, + "terminals": 10, + "warehouse_count": 1, + "warmup_secs": 5, + "measure_secs": 30, + "measure_start_ms": 1774909414551, + "measure_end_ms": 1774909714551, + "generated_at_ms": 1774909758803, + "total_transactions": 143, + "tpmc_like": 132.0, + "transaction_mix": { + "delivery": 4.895104895104895, + "new_order": 48.25174825174825, + "order_status": 1.3986013986013985, + "payment": 42.65734265734266, + "stock_level": 2.797202797202797 + }, + "conformance": { + "new_order_rollbacks": 3, + "new_order_total": 69, + "new_order_remote_order_lines": 0, + "new_order_total_order_lines": 719, + "payment_remote": 0, + "payment_total": 61, + "payment_by_last_name": 35, + "order_status_by_last_name": 0, + "order_status_total": 2, + "delivery_queued": 7, + "delivery_completed": 9, + "delivery_processed_districts": 90, + "delivery_skipped_districts": 0 + }, + "transactions": { + "delivery": { + "count": 7, + "success": 7, + "failure": 0, + "mean_latency_ms": 7.857142857142857, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 10, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 7, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 7, + "sum_ms": 55, + "max_ms": 10 + } + }, + "new_order": { + "count": 69, + "success": 66, + "failure": 3, + "mean_latency_ms": 9.782608695652174, + "p50_latency_ms": 10, + "p95_latency_ms": 20, + "p99_latency_ms": 20, + "max_latency_ms": 19, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 1, + 2, + 0, + 43, + 23, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 69, + "sum_ms": 675, + "max_ms": 19 + } + }, + "order_status": { + "count": 2, + "success": 2, + "failure": 0, + "mean_latency_ms": 1.0, + "p50_latency_ms": 1, + "p95_latency_ms": 1, + "p99_latency_ms": 1, + "max_latency_ms": 1, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 2, + "sum_ms": 2, + "max_ms": 1 + } + }, + "payment": { + "count": 61, + "success": 61, + "failure": 0, + "mean_latency_ms": 8.721311475409836, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 20, + "max_latency_ms": 14, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 59, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 61, + "sum_ms": 532, + "max_ms": 14 + } + }, + "stock_level": { + "count": 4, + "success": 4, + "failure": 0, + "mean_latency_ms": 6.25, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 7, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 4, + "sum_ms": 25, + "max_ms": 7 + } + } + }, + "delivery": { + "queued": 7, + "completed": 9, + "pending": 0, + "processed_districts": 90, + "skipped_districts": 0, + "completion_mean_ms": 22.0, + "completion_p50_ms": 50, + "completion_p95_ms": 50, + "completion_p99_ms": 50, + "completion_max_ms": 23, + "completion_histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 0, + 0, + 9, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 9, + "sum_ms": 198, + "max_ms": 23 + } + } +} \ No newline at end of file diff --git a/modules/tpcc-results/tpcc-1774909349292/driver-152145/txn_events.ndjson b/modules/tpcc-results/tpcc-1774909349292/driver-152145/txn_events.ndjson new file mode 100644 index 00000000000..9ef507b9a5f --- /dev/null +++ b/modules/tpcc-results/tpcc-1774909349292/driver-152145/txn_events.ndjson @@ -0,0 +1,143 @@ +{"timestamp_ms":1774909415523,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909418015,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909418318,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909419652,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909420772,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909421012,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909422177,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909427669,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909430506,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"new_order","success":false,"latency_ms":1,"rollback":true,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":"item 100001 not found"} +{"timestamp_ms":1774909432494,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909434140,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909435419,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909445283,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909449324,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909450087,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909461615,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"new_order","success":false,"latency_ms":2,"rollback":true,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":"item 100001 not found"} +{"timestamp_ms":1774909463025,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909465626,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909468921,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909469422,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909469686,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909470109,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"delivery","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909471730,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909472641,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"payment","success":true,"latency_ms":14,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909472899,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909473798,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909475135,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909477754,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909478983,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909482708,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909482905,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909482951,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909484044,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909492086,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909494390,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909497407,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"new_order","success":false,"latency_ms":2,"rollback":true,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":"item 100001 not found"} +{"timestamp_ms":1774909498785,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909502205,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909507094,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909507246,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909507730,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909511157,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909514429,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909516172,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909517706,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909517924,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909518275,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909520232,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909520649,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909521985,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":12,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909529395,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909529606,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909533982,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909536868,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909537552,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909538036,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909540125,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909540514,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909541636,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909542845,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909549397,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909552349,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909553488,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909557586,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909558968,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909562077,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909563462,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909563946,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909566524,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909568607,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909570458,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909573340,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909574191,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909574360,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909577009,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909577725,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":12,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909585014,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909588317,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909589303,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909593418,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909595951,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909596155,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"stock_level","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909599589,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909601059,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909603655,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909603877,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909604815,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909604912,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909610750,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909612972,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909613428,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909614081,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909615898,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909617364,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909617661,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909619216,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909619710,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909621244,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":12,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909623584,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909625248,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909625607,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909630115,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909631421,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909634407,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909635758,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909635883,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909637581,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909641657,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909642151,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909644231,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"delivery","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909644667,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"delivery","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909646383,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909647273,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909651051,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"payment","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909652976,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909653296,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909653384,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909654808,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909655136,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909657243,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909658694,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909659080,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909662020,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909662063,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909671503,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":19,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909675387,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909676616,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909676783,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909677417,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909684358,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909684919,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909688030,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909689112,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909691867,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909694450,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909697472,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909698081,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909702423,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909703024,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909708654,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909710370,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909712918,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774909713070,"run_id":"tpcc-1774909349292","driver_id":"driver-152145","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} diff --git a/modules/tpcc-results/tpcc-1774910938757/driver-179375/summary.json b/modules/tpcc-results/tpcc-1774910938757/driver-179375/summary.json new file mode 100644 index 00000000000..31203cab4cb --- /dev/null +++ b/modules/tpcc-results/tpcc-1774910938757/driver-179375/summary.json @@ -0,0 +1,354 @@ +{ + "run_id": "tpcc-1774910938757", + "driver_id": "driver-179375", + "uri": "http://127.0.0.1:3000", + "database": "tpcc-0", + "terminal_start": 1, + "terminals": 10, + "warehouse_count": 1, + "warmup_secs": 5, + "measure_secs": 30, + "measure_start_ms": 1774911013905, + "measure_end_ms": 1774911313905, + "generated_at_ms": 1774911335911, + "total_transactions": 137, + "tpmc_like": 140.0, + "transaction_mix": { + "delivery": 5.839416058394161, + "new_order": 51.824817518248175, + "order_status": 4.37956204379562, + "payment": 34.306569343065696, + "stock_level": 3.6496350364963503 + }, + "conformance": { + "new_order_rollbacks": 1, + "new_order_total": 71, + "new_order_remote_order_lines": 0, + "new_order_total_order_lines": 699, + "payment_remote": 0, + "payment_total": 47, + "payment_by_last_name": 28, + "order_status_by_last_name": 2, + "order_status_total": 6, + "delivery_queued": 8, + "delivery_completed": 13, + "delivery_processed_districts": 130, + "delivery_skipped_districts": 0 + }, + "transactions": { + "delivery": { + "count": 8, + "success": 8, + "failure": 0, + "mean_latency_ms": 7.875, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 9, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 8, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 8, + "sum_ms": 63, + "max_ms": 9 + } + }, + "new_order": { + "count": 71, + "success": 70, + "failure": 1, + "mean_latency_ms": 9.450704225352112, + "p50_latency_ms": 10, + "p95_latency_ms": 20, + "p99_latency_ms": 20, + "max_latency_ms": 13, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 1, + 0, + 56, + 14, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 71, + "sum_ms": 671, + "max_ms": 13 + } + }, + "order_status": { + "count": 6, + "success": 6, + "failure": 0, + "mean_latency_ms": 1.3333333333333333, + "p50_latency_ms": 1, + "p95_latency_ms": 2, + "p99_latency_ms": 2, + "max_latency_ms": 2, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 4, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 6, + "sum_ms": 8, + "max_ms": 2 + } + }, + "payment": { + "count": 47, + "success": 47, + "failure": 0, + "mean_latency_ms": 8.340425531914894, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 10, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 47, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 47, + "sum_ms": 392, + "max_ms": 10 + } + }, + "stock_level": { + "count": 5, + "success": 5, + "failure": 0, + "mean_latency_ms": 6.0, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 6, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 5, + "sum_ms": 30, + "max_ms": 6 + } + } + }, + "delivery": { + "queued": 8, + "completed": 13, + "pending": 0, + "processed_districts": 130, + "skipped_districts": 0, + "completion_mean_ms": 22.615384615384617, + "completion_p50_ms": 50, + "completion_p95_ms": 50, + "completion_p99_ms": 50, + "completion_max_ms": 33, + "completion_histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 0, + 2, + 11, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 13, + "sum_ms": 294, + "max_ms": 33 + } + } +} \ No newline at end of file diff --git a/modules/tpcc-results/tpcc-1774910938757/driver-179375/txn_events.ndjson b/modules/tpcc-results/tpcc-1774910938757/driver-179375/txn_events.ndjson new file mode 100644 index 00000000000..1efcb34c1b1 --- /dev/null +++ b/modules/tpcc-results/tpcc-1774910938757/driver-179375/txn_events.ndjson @@ -0,0 +1,137 @@ +{"timestamp_ms":1774911015678,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"delivery","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911018420,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911018981,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911021714,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911025655,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911028533,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"delivery","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911029620,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911030037,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911030687,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911031628,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911035974,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911037311,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911038794,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911041094,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911041533,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911041838,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911043746,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911045138,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911046003,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911048103,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911049036,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911058201,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911060595,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911063231,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911066017,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911066719,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911066734,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911071474,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911072336,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":12,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911073570,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911080582,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911081056,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911082601,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911086211,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911087124,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911092334,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911093279,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911099642,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911100108,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911104086,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911112640,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911116118,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911116537,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911117263,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911117646,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911118944,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911120695,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911123677,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911125237,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911126810,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911128147,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911130474,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911136200,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911136601,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911140072,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911141671,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911144629,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911144689,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911147387,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911147435,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911149874,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911150002,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911150500,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911153549,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911155410,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911157451,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911158883,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911160945,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911165938,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911166260,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911168596,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911168982,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911172632,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911179945,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911180341,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911180750,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911180893,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911182413,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"delivery","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911183739,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911185140,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911186938,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911188187,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911191634,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911193115,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":13,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911196476,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":12,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911196640,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911197762,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"order_status","success":true,"latency_ms":2,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911202852,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911207014,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911209506,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911212317,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911215158,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911215179,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911216257,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911219060,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"order_status","success":true,"latency_ms":2,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911228010,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911228606,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911231138,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":3,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911235317,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911236516,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911238479,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911248549,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911248750,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911251552,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911252007,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911253797,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911255504,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":3,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911257262,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911258409,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911261044,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911264120,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911264348,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911264884,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911266466,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911268976,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911269608,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911271250,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911272908,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911273183,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911273789,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911278341,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911279930,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911282084,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911287065,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911295995,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911297359,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911297442,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911298895,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911300893,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"stock_level","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911301946,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911303064,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":2,"transaction":"new_order","success":false,"latency_ms":2,"rollback":true,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":"item 100001 not found"} +{"timestamp_ms":1774911307590,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911307662,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911308510,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911311078,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911312776,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":4,"transaction":"delivery","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911313398,"run_id":"tpcc-1774910938757","driver_id":"driver-179375","terminal_id":7,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} diff --git a/modules/tpcc-results/tpcc-1774911348130/driver-184437/summary.json b/modules/tpcc-results/tpcc-1774911348130/driver-184437/summary.json new file mode 100644 index 00000000000..c9d27156bec --- /dev/null +++ b/modules/tpcc-results/tpcc-1774911348130/driver-184437/summary.json @@ -0,0 +1,354 @@ +{ + "run_id": "tpcc-1774911348130", + "driver_id": "driver-184437", + "uri": "http://127.0.0.1:3000", + "database": "tpcc-0", + "terminal_start": 1, + "terminals": 10, + "warehouse_count": 1, + "warmup_secs": 5, + "measure_secs": 30, + "measure_start_ms": 1774911415116, + "measure_end_ms": 1774911715116, + "generated_at_ms": 1774911743163, + "total_transactions": 144, + "tpmc_like": 124.0, + "transaction_mix": { + "delivery": 4.166666666666667, + "new_order": 43.75, + "order_status": 6.25, + "payment": 43.75, + "stock_level": 2.0833333333333335 + }, + "conformance": { + "new_order_rollbacks": 1, + "new_order_total": 63, + "new_order_remote_order_lines": 0, + "new_order_total_order_lines": 610, + "payment_remote": 0, + "payment_total": 63, + "payment_by_last_name": 35, + "order_status_by_last_name": 3, + "order_status_total": 9, + "delivery_queued": 6, + "delivery_completed": 7, + "delivery_processed_districts": 70, + "delivery_skipped_districts": 0 + }, + "transactions": { + "delivery": { + "count": 6, + "success": 6, + "failure": 0, + "mean_latency_ms": 8.0, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 9, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 6, + "sum_ms": 48, + "max_ms": 9 + } + }, + "new_order": { + "count": 63, + "success": 62, + "failure": 1, + "mean_latency_ms": 9.634920634920634, + "p50_latency_ms": 10, + "p95_latency_ms": 20, + "p99_latency_ms": 20, + "max_latency_ms": 12, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 1, + 0, + 51, + 11, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 63, + "sum_ms": 607, + "max_ms": 12 + } + }, + "order_status": { + "count": 9, + "success": 9, + "failure": 0, + "mean_latency_ms": 1.2222222222222223, + "p50_latency_ms": 1, + "p95_latency_ms": 2, + "p99_latency_ms": 2, + "max_latency_ms": 2, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 7, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 9, + "sum_ms": 11, + "max_ms": 2 + } + }, + "payment": { + "count": 63, + "success": 63, + "failure": 0, + "mean_latency_ms": 8.714285714285714, + "p50_latency_ms": 10, + "p95_latency_ms": 10, + "p99_latency_ms": 20, + "max_latency_ms": 11, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 62, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 63, + "sum_ms": 549, + "max_ms": 11 + } + }, + "stock_level": { + "count": 3, + "success": 3, + "failure": 0, + "mean_latency_ms": 5.666666666666667, + "p50_latency_ms": 5, + "p95_latency_ms": 10, + "p99_latency_ms": 10, + "max_latency_ms": 7, + "histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 2, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 3, + "sum_ms": 17, + "max_ms": 7 + } + } + }, + "delivery": { + "queued": 6, + "completed": 7, + "pending": 0, + "processed_districts": 70, + "skipped_districts": 0, + "completion_mean_ms": 21.714285714285715, + "completion_p50_ms": 20, + "completion_p95_ms": 50, + "completion_p99_ms": 50, + "completion_max_ms": 32, + "completion_histogram": { + "buckets_ms": [ + 1, + 2, + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 60000, + 120000 + ], + "counts": [ + 0, + 0, + 0, + 0, + 4, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "count": 7, + "sum_ms": 152, + "max_ms": 32 + } + } +} \ No newline at end of file diff --git a/modules/tpcc-results/tpcc-1774911348130/driver-184437/txn_events.ndjson b/modules/tpcc-results/tpcc-1774911348130/driver-184437/txn_events.ndjson new file mode 100644 index 00000000000..b77e826e899 --- /dev/null +++ b/modules/tpcc-results/tpcc-1774911348130/driver-184437/txn_events.ndjson @@ -0,0 +1,144 @@ +{"timestamp_ms":1774911415270,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911415875,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911416024,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911420011,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911420564,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911420614,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911422674,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911423192,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911426327,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911426598,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911427420,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911430162,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911431498,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911432373,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911433687,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911435747,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911436663,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911440674,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911441706,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911445613,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911452070,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911452086,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"payment","success":true,"latency_ms":6,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911454872,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":12,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911455746,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911458933,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911459003,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911459127,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911461957,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911464188,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"order_status","success":true,"latency_ms":2,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911469261,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911469447,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911472722,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911473037,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"stock_level","success":true,"latency_ms":5,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911473300,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":7,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911474316,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911475259,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911478946,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911479381,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911482284,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911484614,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911486628,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911487926,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911491138,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911491625,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911493935,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911494700,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911508104,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911509902,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911510757,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911515518,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911518298,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911518616,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911520785,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911522800,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911524204,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":7,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911524385,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911525364,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911527729,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911530253,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911531032,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911531531,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911531755,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911535161,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911535191,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911536771,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911538960,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911551268,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911551506,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911553296,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911557816,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911563311,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911568854,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911569428,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911569630,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911572917,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911575050,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911575619,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":7,"transaction":"new_order","success":false,"latency_ms":2,"rollback":true,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":"item 100001 not found"} +{"timestamp_ms":1774911577565,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911578145,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911578312,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911578420,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"stock_level","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911583673,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911586412,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911589100,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911593279,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911593880,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":7,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911595515,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911596638,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911598090,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"stock_level","success":true,"latency_ms":5,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911599396,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911604078,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911604774,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911606709,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911609960,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":11,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911611447,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911612429,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911613753,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911616178,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911617410,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911617812,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"payment","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911619000,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911619173,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"delivery","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911622639,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911622866,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911628352,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911632272,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":15,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911633764,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911635699,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911637262,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":12,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911637912,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911638099,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911640305,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911642474,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911642832,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911645763,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":13,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911648083,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"delivery","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911650708,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911653037,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":9,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":10,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911655222,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911655965,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911659030,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911661467,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911662554,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":14,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911666256,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911668107,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911668216,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911668705,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911671445,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911682394,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":5,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911683827,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":2,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911686799,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":1,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911687134,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911689308,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":7,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":6,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911690291,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"payment","success":true,"latency_ms":11,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911691615,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"new_order","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":8,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911692446,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":10,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":5,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911694741,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":4,"transaction":"new_order","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":9,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911695095,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"delivery","success":true,"latency_ms":7,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911698029,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":3,"transaction":"payment","success":true,"latency_ms":10,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911702984,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"order_status","success":true,"latency_ms":1,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911705406,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"order_status","success":true,"latency_ms":2,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911709380,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":true,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911711415,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":6,"transaction":"payment","success":true,"latency_ms":9,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} +{"timestamp_ms":1774911713374,"run_id":"tpcc-1774911348130","driver_id":"driver-184437","terminal_id":8,"transaction":"payment","success":true,"latency_ms":8,"rollback":false,"remote":false,"by_last_name":false,"order_line_count":0,"remote_order_line_count":0,"detail":null} diff --git a/modules/tpcc/src/lib.rs b/modules/tpcc/src/lib.rs index c0c83e5965b..e62ed165d64 100644 --- a/modules/tpcc/src/lib.rs +++ b/modules/tpcc/src/lib.rs @@ -181,7 +181,10 @@ pub struct Item { pub i_data: String, } -#[table(accessor = stock)] +#[table( + accessor = stock, + index(accessor = by_stock_key_quantity, btree(columns = [stock_key, s_quantity])), +)] #[derive(Clone, Debug)] pub struct Stock { #[primary_key] @@ -484,19 +487,17 @@ pub fn order_status( } #[reducer] -pub fn stock_level( - ctx: &ReducerContext, - w_id: u32, - d_id: u8, - threshold: i32, -) -> Result { +pub fn stock_level(ctx: &ReducerContext, w_id: u32, d_id: u8, threshold: i32) -> Result { let _timer = LogStopwatch::new("stock_level"); + let _timer_district = LogStopwatch::new("stock_level_district"); let district = find_district(ctx, w_id, d_id)?; + _timer_district.end(); let start_o_id = district.d_next_o_id.saturating_sub(20); let end_o_id = district.d_next_o_id; let mut item_ids = BTreeSet::new(); + let _timer_filter = LogStopwatch::new("stock_level_filter"); for line in ctx .db .order_line() @@ -505,14 +506,16 @@ pub fn stock_level( { item_ids.insert(line.ol_i_id); } + _timer_filter.end(); let mut low_stock_count = 0u32; + let _timer_count = LogStopwatch::new("stock_level_count"); for item_id in item_ids { - let stock = find_stock(ctx, w_id, item_id)?; - if stock.s_quantity < threshold { + if find_low_stock(ctx, w_id, item_id, threshold).is_some() { low_stock_count += 1; } } + _timer_count.end(); Ok(StockLevelResult { warehouse_id: w_id, @@ -786,6 +789,15 @@ fn find_customer_by_id(tx: &ReducerContext, w_id: WarehouseId, d_id: u8, c_id: u .ok_or_else(|| format!("customer ({w_id}, {d_id}, {c_id}) not found")) } +fn find_low_stock(tx: &ReducerContext, w_id: WarehouseId, item_id: u32, threshold: i32) -> Option { + let stock_key = pack_stock_key(w_id, item_id); + tx.db + .stock() + .by_stock_key_quantity() + .filter((stock_key, 0..threshold)) + .next() +} + fn find_stock(tx: &ReducerContext, w_id: WarehouseId, item_id: u32) -> Result { let stock_key = pack_stock_key(w_id, item_id); tx.db diff --git a/modules/tpcc/src/load.rs b/modules/tpcc/src/load.rs index 72fa21f3866..9b0437b4dbe 100644 --- a/modules/tpcc/src/load.rs +++ b/modules/tpcc/src/load.rs @@ -53,6 +53,8 @@ pub struct TpccLoadConfigRequest { pub database_number: u32, pub num_databases: u32, pub warehouses_per_database: u32, + pub warehouse_id_offset: u32, + pub skip_items: bool, pub batch_size: u32, pub seed: u64, pub load_c_last: u32, @@ -69,6 +71,8 @@ pub struct TpccLoadConfig { pub database_number: u32, pub num_databases: u32, pub warehouses_per_database: u32, + pub warehouse_id_offset: u32, + pub skip_items: bool, pub batch_size: u32, pub seed: u64, pub load_c_last: u32, @@ -227,6 +231,8 @@ fn configure_tpcc_load_internal(ctx: &ReducerContext, request: TpccLoadConfigReq database_number: request.database_number, num_databases: request.num_databases, warehouses_per_database: request.warehouses_per_database, + warehouse_id_offset: request.warehouse_id_offset, + skip_items: request.skip_items, batch_size: request.batch_size, seed: request.seed, load_c_last: request.load_c_last, @@ -265,6 +271,21 @@ fn validate_request(request: &TpccLoadConfigRequest) -> Result<(), String> { request.num_databases, request.warehouses_per_database )); } + // Validate that the warehouse ID range for this database doesn't overflow u32. + // warehouse_start = database_number * warehouses_per_database + warehouse_id_offset + 1 + // warehouse_end = warehouse_start + warehouses_per_database - 1 + if request + .database_number + .checked_mul(request.warehouses_per_database) + .and_then(|v| v.checked_add(request.warehouse_id_offset)) + .and_then(|v| v.checked_add(request.warehouses_per_database)) + .is_none() + { + return Err(format!( + "warehouse id range overflow u32 (database_number={}, warehouses_per_database={}, warehouse_id_offset={})", + request.database_number, request.warehouses_per_database, request.warehouse_id_offset + )); + } Ok(()) } @@ -272,8 +293,16 @@ fn initial_state(request: &TpccLoadConfigRequest, now: Timestamp) -> TpccLoadSta TpccLoadState { singleton_id: LOAD_SINGLETON_ID, status: TpccLoadStatus::Idle, - phase: TpccLoadPhase::Items, - next_warehouse_id: warehouse_start(request.database_number, request.warehouses_per_database), + phase: if request.skip_items { + TpccLoadPhase::WarehousesDistricts + } else { + TpccLoadPhase::Items + }, + next_warehouse_id: warehouse_start( + request.database_number, + request.warehouses_per_database, + request.warehouse_id_offset, + ), next_district_id: 1, next_item_id: 1, next_order_id: 1, @@ -291,6 +320,8 @@ fn config_as_request(config: &TpccLoadConfig) -> TpccLoadConfigRequest { database_number: config.database_number, num_databases: config.num_databases, warehouses_per_database: config.warehouses_per_database, + warehouse_id_offset: config.warehouse_id_offset, + skip_items: config.skip_items, batch_size: config.batch_size, seed: config.seed, load_c_last: config.load_c_last, @@ -306,8 +337,9 @@ fn build_remote_warehouses(request: &TpccLoadConfigRequest) -> Vec Result { let _timer = LogStopwatch::new("load_warehouses_district_chunk"); - let end_warehouse = warehouse_end(config.database_number, config.warehouses_per_database); - if job.next_warehouse_id < warehouse_start(config.database_number, config.warehouses_per_database) + let end_warehouse = warehouse_end(config.database_number, config.warehouses_per_database, config.warehouse_id_offset); + if job.next_warehouse_id < warehouse_start(config.database_number, config.warehouses_per_database, config.warehouse_id_offset) || job.next_warehouse_id > end_warehouse { return Err(format!("invalid warehouse cursor {}", job.next_warehouse_id)); @@ -436,7 +468,7 @@ fn load_warehouse_district_chunk( TpccLoadPhase::WarehousesDistricts }, next_warehouse_id: if job.next_warehouse_id == end_warehouse { - warehouse_start(config.database_number, config.warehouses_per_database) + warehouse_start(config.database_number, config.warehouses_per_database, config.warehouse_id_offset) } else { job.next_warehouse_id + 1 }, @@ -450,8 +482,8 @@ fn load_warehouse_district_chunk( fn load_stock_chunk(ctx: &ReducerContext, config: &TpccLoadConfig, job: &TpccLoadJob) -> Result { let _timer = LogStopwatch::new("load_stock_chunk"); - let start_warehouse = warehouse_start(config.database_number, config.warehouses_per_database); - let end_warehouse = warehouse_end(config.database_number, config.warehouses_per_database); + let start_warehouse = warehouse_start(config.database_number, config.warehouses_per_database, config.warehouse_id_offset); + let end_warehouse = warehouse_end(config.database_number, config.warehouses_per_database, config.warehouse_id_offset); if job.next_warehouse_id < start_warehouse || job.next_warehouse_id > end_warehouse { return Err(format!("invalid stock warehouse cursor {}", job.next_warehouse_id)); } @@ -491,8 +523,8 @@ fn load_customer_history_chunk( job: &TpccLoadJob, ) -> Result { let _timer = LogStopwatch::new("load_customer_history_chunk"); - let start_warehouse = warehouse_start(config.database_number, config.warehouses_per_database); - let end_warehouse = warehouse_end(config.database_number, config.warehouses_per_database); + let start_warehouse = warehouse_start(config.database_number, config.warehouses_per_database, config.warehouse_id_offset); + let end_warehouse = warehouse_end(config.database_number, config.warehouses_per_database, config.warehouse_id_offset); if job.next_warehouse_id < start_warehouse || job.next_warehouse_id > end_warehouse { return Err(format!("invalid customer warehouse cursor {}", job.next_warehouse_id)); } @@ -541,8 +573,8 @@ fn load_customer_history_chunk( fn load_order_chunk(ctx: &ReducerContext, config: &TpccLoadConfig, job: &TpccLoadJob) -> Result { let _timer = LogStopwatch::new("load_order_chunk"); - let start_warehouse = warehouse_start(config.database_number, config.warehouses_per_database); - let end_warehouse = warehouse_end(config.database_number, config.warehouses_per_database); + let start_warehouse = warehouse_start(config.database_number, config.warehouses_per_database, config.warehouse_id_offset); + let end_warehouse = warehouse_end(config.database_number, config.warehouses_per_database, config.warehouse_id_offset); if job.next_warehouse_id < start_warehouse || job.next_warehouse_id > end_warehouse { return Err(format!("invalid order warehouse cursor {}", job.next_warehouse_id)); } @@ -834,21 +866,26 @@ fn customer_permutation(config: &TpccLoadConfig, warehouse_id: WarehouseId, dist permutation } -fn warehouse_range(database_number: u32, warehouses_per_database: u32) -> std::ops::Range { - let start = warehouse_start(database_number, warehouses_per_database); +fn warehouse_range( + database_number: u32, + warehouses_per_database: u32, + offset: u32, +) -> std::ops::Range { + let start = warehouse_start(database_number, warehouses_per_database, offset); let end = start + warehouses_per_database; start..end } -fn warehouse_start(database_number: u32, warehouses_per_database: u32) -> WarehouseId { +fn warehouse_start(database_number: u32, warehouses_per_database: u32, offset: u32) -> WarehouseId { database_number .checked_mul(warehouses_per_database) + .and_then(|value| value.checked_add(offset)) .and_then(|value| value.checked_add(1)) .expect("warehouse id arithmetic validated at configure_tpcc_load time") } -fn warehouse_end(database_number: u32, warehouses_per_database: u32) -> WarehouseId { - warehouse_start(database_number, warehouses_per_database) + warehouses_per_database - 1 +fn warehouse_end(database_number: u32, warehouses_per_database: u32, offset: u32) -> WarehouseId { + warehouse_start(database_number, warehouses_per_database, offset) + warehouses_per_database - 1 } fn deterministic_rng(seed: u64, tag: u64, parts: &[u64]) -> StdRng { diff --git a/modules/tpcc/src/new_order.rs b/modules/tpcc/src/new_order.rs index 7fd0c939b04..00dc06fa434 100644 --- a/modules/tpcc/src/new_order.rs +++ b/modules/tpcc/src/new_order.rs @@ -1,9 +1,7 @@ use std::collections::HashMap; use crate::{ - district, find_customer_by_id, find_district, find_stock, find_warehouse, item, order_line, pack_order_key, - remote::{call_remote_reducer, remote_warehouse_home}, - stock, District, Item, OrderLine, Stock, WarehouseId, DISTRICTS_PER_WAREHOUSE, TAX_SCALE, + DISTRICTS_PER_WAREHOUSE, District, Item, OrderLine, Stock, TAX_SCALE, WarehouseId, district, find_customer_by_id, find_district, find_stock, find_warehouse, item, order_line, pack_order_key, remote::{call_remote_reducer, remote_warehouse_home, simulate_remote_call}, stock }; use spacetimedb::{log_stopwatch::LogStopwatch, reducer, Identity, ReducerContext, SpacetimeType, Table, Timestamp}; @@ -189,6 +187,13 @@ fn call_remote_order_multiple_items_and_decrement_stock( "order_multiple_items_and_decrement_stocks", &input, ) + // simulate_remote_call( + // ctx, + // remote_database_identity, + // "order_multiple_items_and_decrement_stocks", + // &input, + // )?; + // Ok(simulated_remote_order_outputs(input)) } struct ProcessedNewOrderItem { @@ -262,6 +267,29 @@ pub struct OrderMultipleItemsInput { terminal_warehouse: WarehouseId, } +fn simulated_remote_order_outputs(input: OrderMultipleItemsInput) -> Vec { + input + .lines + .into_iter() + .map(|line| simulated_remote_order_output(line, input.district)) + .collect() +} + +fn simulated_remote_order_output(line: NewOrderLineAndIndex, district: u8) -> OrderItemOutput { + let stock_data = if line.line.item_id % 10 == 0 { + "SIMULATED ORIGINAL STOCK" + } else { + "SIMULATED REMOTE STOCK" + }; + + OrderItemOutput { + s_dist: format!("REMOTE-D{district:02}"), + s_data: stock_data.to_string(), + updated_quantity: adjust_stock_quantity(100, line.line.quantity as i32), + index: line.index, + } +} + #[reducer] fn order_multiple_items_and_decrement_stocks( ctx: &ReducerContext, diff --git a/modules/tpcc/src/payment.rs b/modules/tpcc/src/payment.rs index 59710db9587..c64e533abcd 100644 --- a/modules/tpcc/src/payment.rs +++ b/modules/tpcc/src/payment.rs @@ -1,7 +1,5 @@ use crate::{ - customer, district, find_district, find_warehouse, history, - remote::{call_remote_reducer, remote_warehouse_home}, - resolve_customer, warehouse, Customer, CustomerSelector, District, History, Warehouse, MAX_C_DATA_LEN, + Customer, CustomerSelector, District, History, MAX_C_DATA_LEN, Warehouse, customer, district, find_district, find_warehouse, history, remote::{call_remote_reducer, remote_warehouse_home, simulate_remote_call}, resolve_customer, warehouse }; use spacetimedb::{ log_stopwatch::LogStopwatch, procedure, reducer, Identity, ProcedureContext, ReducerContext, SpacetimeType, Table, @@ -134,6 +132,13 @@ fn call_remote_resolve_and_update_customer_for_payment( "resolve_and_update_customer_for_payment", request, ) + // simulate_remote_call( + // ctx, + // remote_database_identity, + // "resolve_and_update_customer_for_payment", + // request, + // )?; + // Ok(simulated_remote_customer(request)) } #[procedure] @@ -149,6 +154,53 @@ fn process_remote_payment(ctx: &mut ProcedureContext, request: PaymentRequest) - }) } +fn simulated_remote_customer(request: &PaymentRequest) -> Customer { + let (customer_id, customer_first, customer_last) = match &request.customer_selector { + CustomerSelector::ById(customer_id) => ( + *customer_id, + format!("Remote{customer_id}"), + format!("Customer{customer_id}"), + ), + CustomerSelector::ByLastName(last_name) => ( + simulated_customer_id_from_last_name(last_name), + "Remote".to_string(), + last_name.clone(), + ), + }; + + Customer { + customer_key: 0, + c_w_id: request.customer_warehouse_id, + c_d_id: request.customer_district_id, + c_id: customer_id, + c_first: customer_first, + c_middle: "OE".to_string(), + c_last: customer_last, + c_street_1: "REMOTE".to_string(), + c_street_2: "SIMULATED".to_string(), + c_city: "REMOTE".to_string(), + c_state: "RM".to_string(), + c_zip: "000000000".to_string(), + c_phone: "0000000000000000".to_string(), + c_since: request.now, + c_credit: "GC".to_string(), + c_credit_lim_cents: 5_000_000, + c_discount_bps: 0, + c_balance_cents: -request.payment_amount_cents, + c_ytd_payment_cents: request.payment_amount_cents, + c_payment_cnt: 1, + c_delivery_cnt: 0, + c_data: "SIMULATED_REMOTE_PAYMENT".to_string(), + } +} + +fn simulated_customer_id_from_last_name(last_name: &str) -> u32 { + let hash = last_name + .bytes() + .fold(0_u32, |acc, byte| acc.wrapping_mul(31).wrapping_add(u32::from(byte))); + (hash % crate::CUSTOMERS_PER_DISTRICT) + 1 +} + fn update_customer(tx: &ReducerContext, request: &PaymentRequest, customer: Customer) -> Customer { let mut updated_customer = Customer { c_balance_cents: customer.c_balance_cents - request.payment_amount_cents, diff --git a/modules/tpcc/src/remote.rs b/modules/tpcc/src/remote.rs index e50ba08c1cd..79f7404e8ae 100644 --- a/modules/tpcc/src/remote.rs +++ b/modules/tpcc/src/remote.rs @@ -1,9 +1,10 @@ use spacetimedb::{ - reducer, remote_reducer::call_reducer_on_db, table, DeserializeOwned, Identity, ReducerContext, Serialize, + reducer, remote_reducer::call_reducer_on_db_2pc, table, DeserializeOwned, Identity, ReducerContext, Serialize, SpacetimeType, Table, }; use spacetimedb_sats::bsatn; +const SIMULATED_REMOTE_CALL_WORK: u32 = 1_000_000; /// For warehouses not managed by this database, stores the [`Identity`] of the remote database which manages that warehouse. /// @@ -42,6 +43,40 @@ pub fn remote_warehouse_home(ctx: &ReducerContext, warehouse_id: u32) -> Option< .map(|row| row.remote_database_home) } +pub fn simulate_remote_call( + _ctx: &ReducerContext, + database_ident: Identity, + reducer_name: &str, + args: &Args, +) -> Result<(), String> +where + Args: SpacetimeType + Serialize, +{ + let args = bsatn::to_vec(args).map_err(|e| { + format!( + "Failed to BSATN-serialize args for simulated remote reducer {reducer_name} on database {database_ident}: {e}" + ) + })?; + + // Hold the reducer busy to approximate a synchronous remote call while we debug the real remote-call path. + let mut state = mix_remote_work(0x9E37_79B9_7F4A_7C15 ^ u64::try_from(args.len()).unwrap_or(u64::MAX)); + for byte in format!("{database_ident}:{reducer_name}").bytes() { + state = mix_remote_work(state ^ u64::from(byte)); + } + for &byte in &args { + state = mix_remote_work(state ^ u64::from(byte)); + } + + let rounds = + SIMULATED_REMOTE_CALL_WORK.saturating_add(u32::try_from(args.len()).unwrap_or(u32::MAX).saturating_mul(128)); + for _ in 0..rounds { + state = mix_remote_work(state); + } + std::hint::black_box(state); + Ok(()) +} + +#[allow(dead_code)] pub fn call_remote_reducer( _ctx: &ReducerContext, database_ident: Identity, @@ -55,7 +90,7 @@ where let args = bsatn::to_vec(args).map_err(|e| { format!("Failed to BSATN-serialize args for remote reducer {reducer_name} on database {database_ident}: {e}") })?; - let out = call_reducer_on_db(database_ident, reducer_name, &args) + let out = call_reducer_on_db_2pc(database_ident, reducer_name, &args) .map_err(|e| format!("Failed to call remote reducer {reducer_name} on database {database_ident}: {e}"))?; bsatn::from_slice(&out).map_err(|e| { format!( @@ -63,3 +98,11 @@ where ) }) } + +fn mix_remote_work(mut state: u64) -> u64 { + state ^= state >> 33; + state = state.wrapping_mul(0xFF51_AFD7_ED55_8CCD); + state ^= state >> 33; + state = state.wrapping_mul(0xC4CE_B9FE_1A85_EC53); + state ^ (state >> 33) +} diff --git a/sdks/rust/src/db_connection.rs b/sdks/rust/src/db_connection.rs index 4a3ee02a3e9..07ac4138c69 100644 --- a/sdks/rust/src/db_connection.rs +++ b/sdks/rust/src/db_connection.rs @@ -198,9 +198,13 @@ impl DbContextImpl { } => { let (reducer, callback) = { let mut inner = self.inner.lock().unwrap(); - inner.reducer_callbacks.pop_call_info(request_id).ok_or_else(|| { - InternalError::new(format!("Reducer result for unknown request_id {request_id}")) - })? + match inner.reducer_callbacks.pop_call_info(request_id) { + Some((reducer, callback)) => (reducer, callback), + None => { + log::error!("Reducer result for unknown request_id {request_id}"); + return Ok(()); + } + } }; let reducer_event = ReducerEvent { reducer, @@ -233,9 +237,13 @@ impl DbContextImpl { }; let (reducer, callback) = { let mut inner = self.inner.lock().unwrap(); - inner.reducer_callbacks.pop_call_info(request_id).ok_or_else(|| { - InternalError::new(format!("Reducer result for unknown request_id {request_id}")) - })? + match inner.reducer_callbacks.pop_call_info(request_id) { + Some((reducer, callback)) => (reducer, callback), + None => { + log::error!("Reducer result for unknown request_id {request_id}"); + return Ok(()); + } + } }; let reducer_event = ReducerEvent { diff --git a/tools/tpcc-dashboard/.editorconfig b/tools/tpcc-dashboard/.editorconfig index e5c16dc00d9..2686256015d 100644 --- a/tools/tpcc-dashboard/.editorconfig +++ b/tools/tpcc-dashboard/.editorconfig @@ -18,6 +18,10 @@ charset = utf-8 trim_trailing_whitespace = true max_line_length = 120 +[*.{html,css}] +indent_style = space +indent_size = 2 + [*.json] indent_style = space indent_size = 2 diff --git a/tools/tpcc-dashboard/index.html b/tools/tpcc-dashboard/index.html index bda8cbb76f6..4090fec48a9 100644 --- a/tools/tpcc-dashboard/index.html +++ b/tools/tpcc-dashboard/index.html @@ -4,6 +4,29 @@ tpcc-dashboard + + + + +
diff --git a/tools/tpcc-dashboard/package-lock.json b/tools/tpcc-dashboard/package-lock.json index 0f85fb53c44..8c8a636c4ce 100644 --- a/tools/tpcc-dashboard/package-lock.json +++ b/tools/tpcc-dashboard/package-lock.json @@ -8,8 +8,10 @@ "name": "tpcc-dashboard", "version": "0.0.0", "dependencies": { + "@reduxjs/toolkit": "^2.11.2", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-redux": "^9.2.0", "recharts": "^3.8.1", "spacetimedb": "^2.1.0" }, diff --git a/tools/tpcc-dashboard/package.json b/tools/tpcc-dashboard/package.json index 072598ebecc..9d1a3b7177a 100644 --- a/tools/tpcc-dashboard/package.json +++ b/tools/tpcc-dashboard/package.json @@ -10,8 +10,10 @@ "preview": "vite preview" }, "dependencies": { + "@reduxjs/toolkit": "^2.11.2", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-redux": "^9.2.0", "recharts": "^3.8.1", "spacetimedb": "^2.1.0" }, diff --git a/tools/tpcc-dashboard/public/ibm-plex-mono-latin-600-normal.woff2 b/tools/tpcc-dashboard/public/ibm-plex-mono-latin-600-normal.woff2 new file mode 100644 index 00000000000..67aeeb0104a Binary files /dev/null and b/tools/tpcc-dashboard/public/ibm-plex-mono-latin-600-normal.woff2 differ diff --git a/tools/tpcc-dashboard/public/inter-latin-wght-normal.woff2 b/tools/tpcc-dashboard/public/inter-latin-wght-normal.woff2 new file mode 100644 index 00000000000..d15208de03c Binary files /dev/null and b/tools/tpcc-dashboard/public/inter-latin-wght-normal.woff2 differ diff --git a/tools/tpcc-dashboard/public/source-code-pro-latin-wght-normal.woff2 b/tools/tpcc-dashboard/public/source-code-pro-latin-wght-normal.woff2 new file mode 100644 index 00000000000..bc303f50c5d Binary files /dev/null and b/tools/tpcc-dashboard/public/source-code-pro-latin-wght-normal.woff2 differ diff --git a/tools/tpcc-dashboard/src/App.css b/tools/tpcc-dashboard/src/App.css new file mode 100644 index 00000000000..99fdaad2412 --- /dev/null +++ b/tools/tpcc-dashboard/src/App.css @@ -0,0 +1,5 @@ +.app { + margin: 80px; + background-color: var(--surface-color); + border-radius: 16px; +} diff --git a/tools/tpcc-dashboard/src/App.tsx b/tools/tpcc-dashboard/src/App.tsx index a708b5525fb..204dc9a764e 100644 --- a/tools/tpcc-dashboard/src/App.tsx +++ b/tools/tpcc-dashboard/src/App.tsx @@ -1,83 +1,80 @@ -/* eslint-disable react-hooks/purity */ -import { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect } from 'react'; import { SpacetimeDBContext } from './context'; -import type { NewOrderThroughtputChartData } from './NewOrderThroughtputChart'; +import { deleteState, insertState, removeTxnBucket, upsertTxnBucket } from './features/globalState'; +import { useAppDispatch, useAppSelector } from './hooks'; import NewOrderThroughtputChart from './NewOrderThroughtputChart'; -import type { State } from './module_bindings/types'; +import StatsCards from './StatsCards'; +import './App.css'; function App() { const conn = useContext(SpacetimeDBContext); - const [state, setState] = useState(null); - const [throughputData, setThroughputData] = useState< - NewOrderThroughtputChartData[] - >([]); + const isReady = useAppSelector(state => state.globalState.isReady); + const dispatch = useAppDispatch(); useEffect(() => { - conn?.db.state.onInsert((_, state) => { - setState(state); - setThroughputData([]); - }); - conn?.db.state.onUpdate((_, old, state) => { - setState(state); - - setThroughputData(prevData => { - const next = [ - ...prevData, - { - transactionCount: Number(state.orderCount - old.orderCount), - timestamp: new Date(Number(state.measurementTimeMs)), - }, - ]; + console.log('App useEffect - setting up subscriptions'); + if (!conn) return; - return next; - }); + conn.db.state.onInsert((_, state) => { + console.log('State inserted - dispatching insertState', state); + dispatch( + insertState({ + warehouseCount: Number(state.warehouseCount), + measureStartMs: Number(state.measureStartMs), + measureEndMs: Number(state.measureEndMs), + runStartMs: Number(state.runStartMs), + runEndMs: Number(state.runEndMs), + }) + ); + }); + conn.db.state.onDelete(() => { + console.log('State deleted - dispatching deleteState'); + dispatch(deleteState()); }); - conn?.db.state.onDelete(() => { - setState(null); - setThroughputData([]); + conn.db.txn_bucket.onInsert((_, bucket) => { + dispatch( + upsertTxnBucket({ + bucketStartMs: Number(bucket.bucketStartMs), + count: Number(bucket.count), + }) + ); + }); + conn.db.txn_bucket.onUpdate((_, _oldBucket, bucket) => { + dispatch( + upsertTxnBucket({ + bucketStartMs: Number(bucket.bucketStartMs), + count: Number(bucket.count), + }) + ); + }); + conn.db.txn_bucket.onDelete((_, bucket) => { + dispatch( + removeTxnBucket({ + bucketStartMs: Number(bucket.bucketStartMs), + }) + ); }); const subscription = conn - ?.subscriptionBuilder() + .subscriptionBuilder() .onError(err => console.error('Subscription error:', err)) - .subscribe('SELECT * FROM state'); + .subscribe(['SELECT * FROM state', 'SELECT * FROM txn_bucket']); return () => { - subscription?.unsubscribe(); + subscription.unsubscribe(); }; - }, [conn]); + }, [conn, dispatch]); - if (!state) { - return
Waiting for data...
; + if (!isReady) { + return
Waiting for data...
; } - const measureStartDate = new Date(Number(state.measureStartMs)); - const measureEndDate = new Date(Number(state.measureEndMs)); - - // If the is in progress we calculate the ellapsed time based on the current time, - // otherwise we calculate it based on the measure end date - const ellapsedTimeSec = - Date.now() > measureEndDate.getTime() - ? (measureEndDate.getTime() - measureStartDate.getTime()) / 1000 - : (Date.now() - measureStartDate.getTime()) / 1000; - const tpmC = (Number(state.orderCount) / ellapsedTimeSec) * 60; - return ( - <> -

measureStartMs: {measureStartDate.toLocaleTimeString()}

-

measureEndMs: {measureEndDate.toLocaleTimeString()}

-

total transactions: {state.orderCount}

-

MQTh: {Math.trunc(tpmC)} tpmC

- - - +
+ + +
); } diff --git a/tools/tpcc-dashboard/src/ConnectedGuard.tsx b/tools/tpcc-dashboard/src/ConnectedGuard.tsx index daf75de0e75..bca511e10f3 100644 --- a/tools/tpcc-dashboard/src/ConnectedGuard.tsx +++ b/tools/tpcc-dashboard/src/ConnectedGuard.tsx @@ -4,14 +4,14 @@ import { SpacetimeDBContext } from './context'; export function ConnectedGuard({ children }: { children: React.ReactNode }) { const [conn, setConn] = useState(null); - useEffect(() => { if (conn) { return; } DbConnection.builder() - .withUri('http://127.0.0.1:3000') + .withUri('https://tpc-c-benchmark.spacetimedb.com') + .withUri('http://localhost:3000') .withDatabaseName('tpcc-metrics') .onConnect(conn => { console.log('Connected to SpacetimeDB'); @@ -21,7 +21,7 @@ export function ConnectedGuard({ children }: { children: React.ReactNode }) { }, [conn]); if (!conn || !conn.isActive) { - return
Connecting to SpacetimeDB...
; + return
Connecting to SpacetimeDB...
; } return ( diff --git a/tools/tpcc-dashboard/src/Icons.tsx b/tools/tpcc-dashboard/src/Icons.tsx new file mode 100644 index 00000000000..5b4af5ca696 --- /dev/null +++ b/tools/tpcc-dashboard/src/Icons.tsx @@ -0,0 +1,143 @@ +export const ClockIcon = () => ( + + + + +); + +export const SchemaIcon = () => ( + + + +); + +export const UploadIcon = () => ( + + + + +); + +export const PercentIcon = () => ( + + + + + +); + +export const RefreshIcon = () => ( + + + + + + + + + + + +); + +export const DataIcon = () => ( + + + +); + +export const ConnectIcon = () => ( + + + +); diff --git a/tools/tpcc-dashboard/src/LatencyDistributionChart.tsx b/tools/tpcc-dashboard/src/LatencyDistributionChart.tsx new file mode 100644 index 00000000000..772dd9cbf79 --- /dev/null +++ b/tools/tpcc-dashboard/src/LatencyDistributionChart.tsx @@ -0,0 +1,53 @@ +import { + CartesianGrid, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { useAppSelector } from './hooks'; +import { useMemo } from 'react'; + +export default function LatencyDistributionChart() { + const latencyData = useAppSelector(state => state.globalState.bucketCounts); + + const chartData = useMemo(() => { + const sortedLatencies = Object.keys(latencyData) + .map(key => parseInt(key)) + .sort((a, b) => a - b); + + return sortedLatencies.map(latency => ({ + latency, + count: latencyData[latency], + })); + }, [latencyData]); + + return ( +
+ + + + + + + + + +
+ ); +} diff --git a/tools/tpcc-dashboard/src/NewOrderThroughputChart.css b/tools/tpcc-dashboard/src/NewOrderThroughputChart.css new file mode 100644 index 00000000000..f54c4025a95 --- /dev/null +++ b/tools/tpcc-dashboard/src/NewOrderThroughputChart.css @@ -0,0 +1,22 @@ +.chart { + padding: 64px 32px 32px 32px; + border-radius: 0 0 16px 16px; + border: 1px solid var(--shade-3); + background: var(--shade-6); + + --text-color: #f4f6fc; +} + +.reference-line-label { + display: flex; + padding: var(--Spacer-1, 4px) var(--Spacer-4, 16px); + flex-direction: column; + align-items: flex-start; + gap: var(--Spacer-2, 8px); + color: var(--color-green); + text-align: center; + fill: var(--color-green); + border-radius: 100px; + border: 1px solid var(--primary-colors-green-100, #4cf490); + background: var(--Shades-Ash, #141416); +} diff --git a/tools/tpcc-dashboard/src/NewOrderThroughtputChart.tsx b/tools/tpcc-dashboard/src/NewOrderThroughtputChart.tsx index 3842f586536..33f6bca73d7 100644 --- a/tools/tpcc-dashboard/src/NewOrderThroughtputChart.tsx +++ b/tools/tpcc-dashboard/src/NewOrderThroughtputChart.tsx @@ -9,19 +9,8 @@ import { XAxis, YAxis, } from 'recharts'; - -export interface NewOrderThroughtputChartData { - transactionCount: number; - timestamp: Date; -} - -interface Props { - data: NewOrderThroughtputChartData[]; - runStartMs: number; - runEndMs: number; - measurementStartMs: number; - measurementEndMs: number; -} +import { useAppSelector } from './hooks'; +import './NewOrderThroughputChart.css'; interface ThroughputBucketPoint { elapsedSec: number; @@ -31,7 +20,7 @@ interface ThroughputBucketPoint { } function buildTpccThroughputSeries( - samples: NewOrderThroughtputChartData[], + bucketCounts: Record, runStartMs: number, runEndMs: number, bucketSizeMs: number @@ -43,19 +32,9 @@ function buildTpccThroughputSeries( const buckets = Array.from({ length: bucketCount }, (_, i) => ({ bucketStartMs: runStartMs + i * bucketSizeMs, bucketEndMs: Math.min(runStartMs + (i + 1) * bucketSizeMs, runEndMs), - count: 0, + count: bucketCounts[runStartMs + i * bucketSizeMs] ?? 0, })); - for (const sample of samples) { - const ts = sample.timestamp.getTime(); - if (ts < runStartMs || ts >= runEndMs) continue; - - const index = Math.floor((ts - runStartMs) / bucketSizeMs); - if (index >= 0 && index < buckets.length) { - buckets[index].count += sample.transactionCount; - } - } - return buckets.map(bucket => ({ elapsedSec: (bucket.bucketStartMs - runStartMs) / 1000, tpmC: bucket.count * (60_000 / bucketSizeMs), @@ -64,59 +43,95 @@ function buildTpccThroughputSeries( })); } -export default function NewOrderThroughtputChart({ - data, - runStartMs, - runEndMs, - measurementStartMs, - measurementEndMs, -}: Props) { +export default function NewOrderThroughtputChart() { + const runStartMs = useAppSelector(state => state.globalState.runStartMs); + const runEndMs = useAppSelector(state => state.globalState.runEndMs); + const measurementStartMs = useAppSelector( + state => state.globalState.measureStartMs + ); + const measurementEndMs = useAppSelector( + state => state.globalState.measureEndMs + ); + const bucketCounts = useAppSelector(state => state.globalState.bucketCounts); + const chartData = useMemo(() => { - // const totalDurationMs = runEndMs - runStartMs; - const bucketSizeMs = 10_000; + const bucketSizeMs = 1_000; - return buildTpccThroughputSeries(data, runStartMs, runEndMs, bucketSizeMs); - }, [data, runStartMs, runEndMs]); + return buildTpccThroughputSeries( + bucketCounts, + runStartMs, + runEndMs, + bucketSizeMs + ); + }, [bucketCounts, runStartMs, runEndMs]); return ( - - - - `${Math.round(value)}s`} - label={{ - value: 'Elapsed time', - position: 'insideBottom', - offset: -5, - }} - /> - `${Math.round(value)}`} - label={{ value: 'tpmC', angle: -90, position: 'insideLeft' }} - domain={[0, 'dataMax']} - /> - `Elapsed: ${value.toFixed(0)}s`} - formatter={value => [ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - `${(value as any).toFixed(0)} tpmC`, - 'Throughput', - ]} - /> - - +
+ + + + `${Math.round(value)}s`} + label={{ + value: 'Elapsed time', + position: 'insideBottom', + offset: -5, + fill: 'var(--text-color)', + }} + stroke="var(--text-color)" + /> + `${Math.round(value)}`} + label={{ + value: 'tpmC', + angle: -90, + position: 'insideLeft', + fill: 'var(--text-color)', + }} + domain={[0, 'dataMax']} + stroke="var(--text-color)" + /> + `Elapsed: ${value.toFixed(0)}s`} + formatter={value => [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + `${(value as any).toFixed(0)} tpmC`, + 'Throughput', + ]} + /> + + - - - + + + +
); } diff --git a/tools/tpcc-dashboard/src/StatsCards.css b/tools/tpcc-dashboard/src/StatsCards.css new file mode 100644 index 00000000000..61269d276a5 --- /dev/null +++ b/tools/tpcc-dashboard/src/StatsCards.css @@ -0,0 +1,23 @@ +.cards { + display: flex; +} + +.card { + display: flex; + padding: 24px; + flex-direction: column; + align-items: flex-start; + gap: 32px; + flex: 1 0 0; + align-self: stretch; + border: 1px solid var(--shade-3); + background: var(--shade-6); +} + +.card:first-child { + border-radius: 16px 0 0 0; +} + +.card:last-child { + border-radius: 0 16px 0 0; +} diff --git a/tools/tpcc-dashboard/src/StatsCards.tsx b/tools/tpcc-dashboard/src/StatsCards.tsx new file mode 100644 index 00000000000..37b67921c0d --- /dev/null +++ b/tools/tpcc-dashboard/src/StatsCards.tsx @@ -0,0 +1,124 @@ +import { useAppSelector } from './hooks'; +import { + ClockIcon, + ConnectIcon, + DataIcon, + PercentIcon, + RefreshIcon, + SchemaIcon, + UploadIcon, +} from './Icons'; +import './StatsCards.css'; + +function getTpmC( + measureStartMs: number, + bucketCounts: Record +): number { + const measuredBucketStarts = Object.keys(bucketCounts) + .map(Number) + .filter(bucketStartMs => bucketStartMs >= measureStartMs) + .sort((a, b) => a - b); + + if (measuredBucketStarts.length === 0) { + return 0; + } + + const firstBucketStartMs = measuredBucketStarts[0]; + const latestBucketStartMs = + measuredBucketStarts[measuredBucketStarts.length - 1]; + const totalMeasuredTransactions = measuredBucketStarts.reduce( + (sum, bucketStartMs) => sum + (bucketCounts[bucketStartMs] ?? 0), + 0 + ); + const elapsedTimeSec = + (latestBucketStartMs + 1_000 - firstBucketStartMs) / 1000; + + if (elapsedTimeSec <= 0) { + return 0; + } + + return Math.trunc((totalMeasuredTransactions / elapsedTimeSec) * 60); +} + +function StatCard({ + icon, + label, + value, + unit, +}: { + icon: React.ReactNode; + label: string; + value: string | number; + unit?: string; +}) { + return ( +
+ {icon} +

{label}

+
+

{value}

+ {unit &&

{unit}

} +
+
+ ); +} + +export default function StatsCards() { + const warehouses = useAppSelector(state => state.globalState.warehouses); + const measureStartMs = useAppSelector( + state => state.globalState.measureStartMs + ); + const measureEndMs = useAppSelector(state => state.globalState.measureEndMs); + const totalTransactionCount = useAppSelector( + state => state.globalState.totalTransactionCount + ); + const measuredTransactionCount = useAppSelector( + state => state.globalState.measuredTransactionCount + ); + const bucketCounts = useAppSelector(state => state.globalState.bucketCounts); + + const tpmC = getTpmC(measureStartMs, bucketCounts); + const theoreticalMaxThroughput = warehouses * 12.86; + + return ( +
+ } + label="Measured Duration" + value={((measureEndMs - measureStartMs) / 1000 / 60).toFixed(2)} + unit="minutes" + /> + } label="Warehouses" value={warehouses} /> + } + label="Max. Theorical Throughput" + value={theoreticalMaxThroughput} + unit="tpmC" + /> + } + label="% Max. Theorical Throughput" + value={ + theoreticalMaxThroughput <= 0 + ? 'N/A' + : ((tpmC / theoreticalMaxThroughput) * 100).toFixed(2) + '%' + } + /> + } + label="Total Transactions" + value={totalTransactionCount} + /> + } + label="Measured Transactions" + value={measuredTransactionCount} + /> + } + label="MQTh" + value={tpmC + ' tpmC'} + /> +
+ ); +} diff --git a/tools/tpcc-dashboard/src/context.ts b/tools/tpcc-dashboard/src/context.ts index e136f2b9a8f..c98bcc22443 100644 --- a/tools/tpcc-dashboard/src/context.ts +++ b/tools/tpcc-dashboard/src/context.ts @@ -1,4 +1,6 @@ import { createContext } from 'react'; import type { DbConnection } from './module_bindings'; -export const SpacetimeDBContext = createContext(null); +export const SpacetimeDBContext = createContext( + null as unknown as DbConnection +); diff --git a/tools/tpcc-dashboard/src/features/globalState.ts b/tools/tpcc-dashboard/src/features/globalState.ts new file mode 100644 index 00000000000..a18efe22581 --- /dev/null +++ b/tools/tpcc-dashboard/src/features/globalState.ts @@ -0,0 +1,97 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; + +function createInitialState(): GlobalState { + return { + isReady: false, + warehouses: 0, + measureStartMs: 0, + measureEndMs: 0, + runStartMs: 0, + runEndMs: 0, + totalTransactionCount: 0, + measuredTransactionCount: 0, + bucketCounts: {}, + }; +} + +function recalculateCounts(state: GlobalState) { + let totalTransactionCount = 0; + let measuredTransactionCount = 0; + + for (const [bucketStartMs, count] of Object.entries(state.bucketCounts)) { + const bucketCount = Number(count); + totalTransactionCount += bucketCount; + + if (Number(bucketStartMs) >= state.measureStartMs) { + measuredTransactionCount += bucketCount; + } + } + + state.totalTransactionCount = totalTransactionCount; + state.measuredTransactionCount = measuredTransactionCount; +} + +export interface GlobalState { + isReady: boolean; + warehouses: number; + measureStartMs: number; + measureEndMs: number; + runStartMs: number; + runEndMs: number; + totalTransactionCount: number; + measuredTransactionCount: number; + bucketCounts: Record; +} + +const initialState: GlobalState = createInitialState(); + +export const globalStateSlice = createSlice({ + name: 'globalState', + initialState, + reducers: { + insertState: ( + state, + action: PayloadAction<{ + warehouseCount: number; + measureStartMs: number; + measureEndMs: number; + runStartMs: number; + runEndMs: number; + }> + ) => { + console.log('State inserted, updating global state'); + const payload = action.payload; + state.isReady = true; + state.warehouses = payload.warehouseCount; + state.measureStartMs = payload.measureStartMs; + state.measureEndMs = payload.measureEndMs; + state.runStartMs = payload.runStartMs; + state.runEndMs = payload.runEndMs; + recalculateCounts(state); + }, + deleteState: () => { + console.log('State deleted, resetting to initial state'); + return createInitialState(); + }, + upsertTxnBucket: ( + state, + action: PayloadAction<{ + bucketStartMs: number; + count: number; + }> + ) => { + const payload = action.payload; + state.bucketCounts[payload.bucketStartMs] = payload.count; + recalculateCounts(state); + }, + removeTxnBucket: (state, action: PayloadAction<{ bucketStartMs: number }>) => { + delete state.bucketCounts[action.payload.bucketStartMs]; + recalculateCounts(state); + }, + }, +}); + +export const { insertState, deleteState, upsertTxnBucket, removeTxnBucket } = + globalStateSlice.actions; + +export default globalStateSlice.reducer; diff --git a/tools/tpcc-dashboard/src/hooks.ts b/tools/tpcc-dashboard/src/hooks.ts new file mode 100644 index 00000000000..2cdf69cae23 --- /dev/null +++ b/tools/tpcc-dashboard/src/hooks.ts @@ -0,0 +1,5 @@ +import { useDispatch, useSelector } from 'react-redux'; +import type { RootState, AppDispatch } from './store'; + +export const useAppDispatch = useDispatch.withTypes(); +export const useAppSelector = useSelector.withTypes(); diff --git a/tools/tpcc-dashboard/src/main.tsx b/tools/tpcc-dashboard/src/main.tsx index 4143347ccb2..2cc90fd1824 100644 --- a/tools/tpcc-dashboard/src/main.tsx +++ b/tools/tpcc-dashboard/src/main.tsx @@ -1,12 +1,14 @@ -import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import App from './App.tsx'; import { ConnectedGuard } from './ConnectedGuard.tsx'; +import { Provider } from 'react-redux'; +import { store } from './store.ts'; +import './style.css'; createRoot(document.getElementById('root')!).render( - - + + - - + + ); diff --git a/tools/tpcc-dashboard/src/module_bindings/index.ts b/tools/tpcc-dashboard/src/module_bindings/index.ts index 0d0ce76fa10..5281ad76b8b 100644 --- a/tools/tpcc-dashboard/src/module_bindings/index.ts +++ b/tools/tpcc-dashboard/src/module_bindings/index.ts @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 2.1.0 (commit 6981f48b4bc1a71c8dd9bdfe5a2c343f6370243d). +// This was generated using spacetimedb cli version 2.1.0 (commit 7df4044d896459c2a6ca5b9e2c716052aa39e88a). /* eslint-disable */ /* tslint:disable */ @@ -35,13 +35,16 @@ import { // Import all reducer arg schemas import ClearStateReducer from "./clear_state_reducer"; -import RegisterCompletedOrderReducer from "./register_completed_order_reducer"; +import RecordTxnReducer from "./record_txn_reducer"; +import RecordTxnBucketReducer from "./record_txn_bucket_reducer"; import ResetReducer from "./reset_reducer"; // Import all procedure arg schemas // Import all table schema definitions import StateRow from "./state_table"; +import TxnRow from "./txn_table"; +import TxnBucketRow from "./txn_bucket_table"; /** Type-only namespace exports for generated type groups. */ @@ -58,12 +61,35 @@ const tablesSchema = __schema({ { name: 'state_id_key', constraint: 'unique', columns: ['id'] }, ], }, StateRow), + txn: __table({ + name: 'txn', + indexes: [ + { accessor: 'id', name: 'txn_id_idx_btree', algorithm: 'btree', columns: [ + 'id', + ] }, + ], + constraints: [ + { name: 'txn_id_key', constraint: 'unique', columns: ['id'] }, + ], + }, TxnRow), + txn_bucket: __table({ + name: 'txn_bucket', + indexes: [ + { accessor: 'bucket_start_ms', name: 'txn_bucket_bucket_start_ms_idx_btree', algorithm: 'btree', columns: [ + 'bucketStartMs', + ] }, + ], + constraints: [ + { name: 'txn_bucket_bucket_start_ms_key', constraint: 'unique', columns: ['bucketStartMs'] }, + ], + }, TxnBucketRow), }); /** The schema information for all reducers in this module. This is defined the same way as the reducers would have been defined in the server, except the body of the reducer is omitted in code generation. */ const reducersSchema = __reducers( __reducerSchema("clear_state", ClearStateReducer), - __reducerSchema("register_completed_order", RegisterCompletedOrderReducer), + __reducerSchema("record_txn", RecordTxnReducer), + __reducerSchema("record_txn_bucket", RecordTxnBucketReducer), __reducerSchema("reset", ResetReducer), ); diff --git a/tools/tpcc-dashboard/src/module_bindings/register_completed_order_reducer.ts b/tools/tpcc-dashboard/src/module_bindings/record_txn_bucket_reducer.ts similarity index 100% rename from tools/tpcc-dashboard/src/module_bindings/register_completed_order_reducer.ts rename to tools/tpcc-dashboard/src/module_bindings/record_txn_bucket_reducer.ts diff --git a/tools/tpcc-dashboard/src/module_bindings/record_txn_reducer.ts b/tools/tpcc-dashboard/src/module_bindings/record_txn_reducer.ts new file mode 100644 index 00000000000..a6195f0aa10 --- /dev/null +++ b/tools/tpcc-dashboard/src/module_bindings/record_txn_reducer.ts @@ -0,0 +1,15 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from "spacetimedb"; + +export default { + latencyMs: __t.u16(), +}; diff --git a/tools/tpcc-dashboard/src/module_bindings/reset_reducer.ts b/tools/tpcc-dashboard/src/module_bindings/reset_reducer.ts index 73dfa1c32e6..0b0d28699d8 100644 --- a/tools/tpcc-dashboard/src/module_bindings/reset_reducer.ts +++ b/tools/tpcc-dashboard/src/module_bindings/reset_reducer.ts @@ -11,6 +11,7 @@ import { } from "spacetimedb"; export default { + warehouseCount: __t.u64(), warmupDurationMs: __t.u64(), measureStartMs: __t.u64(), measureEndMs: __t.u64(), diff --git a/tools/tpcc-dashboard/src/module_bindings/state_table.ts b/tools/tpcc-dashboard/src/module_bindings/state_table.ts index 954c2bd105a..b4021f845e3 100644 --- a/tools/tpcc-dashboard/src/module_bindings/state_table.ts +++ b/tools/tpcc-dashboard/src/module_bindings/state_table.ts @@ -16,6 +16,5 @@ export default __t.row({ runEndMs: __t.u64().name("run_end_ms"), measureStartMs: __t.u64().name("measure_start_ms"), measureEndMs: __t.u64().name("measure_end_ms"), - orderCount: __t.u64().name("order_count"), - measurementTimeMs: __t.u64().name("measurement_time_ms"), + warehouseCount: __t.u64().name("warehouse_count"), }); diff --git a/tools/tpcc-dashboard/src/module_bindings/txn_bucket_table.ts b/tools/tpcc-dashboard/src/module_bindings/txn_bucket_table.ts new file mode 100644 index 00000000000..702c9a51d58 --- /dev/null +++ b/tools/tpcc-dashboard/src/module_bindings/txn_bucket_table.ts @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from "spacetimedb"; + +export default __t.row({ + bucketStartMs: __t.u64().primaryKey().name("bucket_start_ms"), + count: __t.u64(), +}); diff --git a/tools/tpcc-dashboard/src/module_bindings/txn_table.ts b/tools/tpcc-dashboard/src/module_bindings/txn_table.ts new file mode 100644 index 00000000000..aac60ff640c --- /dev/null +++ b/tools/tpcc-dashboard/src/module_bindings/txn_table.ts @@ -0,0 +1,17 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from "spacetimedb"; + +export default __t.row({ + id: __t.u64().primaryKey(), + measurementTimeMs: __t.u64().name("measurement_time_ms"), + latencyMs: __t.u16().name("latency_ms"), +}); diff --git a/tools/tpcc-dashboard/src/module_bindings/types.ts b/tools/tpcc-dashboard/src/module_bindings/types.ts index 94c205542c6..37421c0c85a 100644 --- a/tools/tpcc-dashboard/src/module_bindings/types.ts +++ b/tools/tpcc-dashboard/src/module_bindings/types.ts @@ -16,8 +16,20 @@ export const State = __t.object("State", { runEndMs: __t.u64(), measureStartMs: __t.u64(), measureEndMs: __t.u64(), - orderCount: __t.u64(), - measurementTimeMs: __t.u64(), + warehouseCount: __t.u64(), }); export type State = __Infer; +export const Txn = __t.object("Txn", { + id: __t.u64(), + measurementTimeMs: __t.u64(), + latencyMs: __t.u16(), +}); +export type Txn = __Infer; + +export const TxnBucket = __t.object("TxnBucket", { + bucketStartMs: __t.u64(), + count: __t.u64(), +}); +export type TxnBucket = __Infer; + diff --git a/tools/tpcc-dashboard/src/module_bindings/types/reducers.ts b/tools/tpcc-dashboard/src/module_bindings/types/reducers.ts index 81979b5b401..ee9bac76e78 100644 --- a/tools/tpcc-dashboard/src/module_bindings/types/reducers.ts +++ b/tools/tpcc-dashboard/src/module_bindings/types/reducers.ts @@ -7,10 +7,12 @@ import { type Infer as __Infer } from "spacetimedb"; // Import all reducer arg schemas import ClearStateReducer from "../clear_state_reducer"; -import RegisterCompletedOrderReducer from "../register_completed_order_reducer"; +import RecordTxnReducer from "../record_txn_reducer"; +import RecordTxnBucketReducer from "../record_txn_bucket_reducer"; import ResetReducer from "../reset_reducer"; export type ClearStateParams = __Infer; -export type RegisterCompletedOrderParams = __Infer; +export type RecordTxnParams = __Infer; +export type RecordTxnBucketParams = __Infer; export type ResetParams = __Infer; diff --git a/tools/tpcc-dashboard/src/store.ts b/tools/tpcc-dashboard/src/store.ts new file mode 100644 index 00000000000..58734d0ee73 --- /dev/null +++ b/tools/tpcc-dashboard/src/store.ts @@ -0,0 +1,11 @@ +import { configureStore } from '@reduxjs/toolkit'; +import globalStateReducer from './features/globalState'; + +export const store = configureStore({ + reducer: { + globalState: globalStateReducer, + }, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/tools/tpcc-dashboard/src/style.css b/tools/tpcc-dashboard/src/style.css new file mode 100644 index 00000000000..efe79c5b7d0 --- /dev/null +++ b/tools/tpcc-dashboard/src/style.css @@ -0,0 +1,71 @@ +:root { + --shade-3: #122129; + --shade-7: #0b1114; + --shade-6: #0e161a; + + --color-green: #4cf490; + --color-white: #d7d8d9; + + --color-neutral-4: #6f7987; + + --background-color: var(--shade-7); + --surface-color: var(--shade-6); + + --font-inter: 'Inter Variable', sans-serif; + --font-source: 'Source Code Pro Variable', monospace; + --font-ibm: 'IBM Plex Mono', monospace; +} + +html { + display: flex; + flex-direction: column; + height: 100vh; + background-color: var(--background-color); +} + +p { + margin: 0; + padding: 0; +} + +.heading-7 { + color: var(--color-white); + + font-family: var(--font-inter); + font-size: 16.8px; + font-style: normal; + font-weight: 500; + line-height: 20.16px; + letter-spacing: -0.672px; +} + +.value-1 { + color: var(--color-green); + + font-family: var(--font-source); + font-size: 24.2px; + font-style: normal; + font-weight: 400; + line-height: 24.2px; +} + +.value-3 { + color: var(--color-neutral-4); + + font-family: var(--font-inter); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 21px; + letter-spacing: -0.14px; +} + +.tagline-2 { + font-family: var(--font-source); + font-size: 11.7px; + font-style: normal; + font-weight: 400; + line-height: 11.7px; + letter-spacing: 0.936px; + text-transform: uppercase; +} diff --git a/tools/tpcc-runner/src/config.rs b/tools/tpcc-runner/src/config.rs index a624663bd8a..43652b7b83c 100644 --- a/tools/tpcc-runner/src/config.rs +++ b/tools/tpcc-runner/src/config.rs @@ -43,6 +43,8 @@ pub struct LoadConfig { pub load_parallelism: usize, pub batch_size: usize, pub reset: bool, + pub warehouse_id_offset: u32, + pub skip_items: bool, } #[derive(Debug, Clone)] @@ -90,6 +92,15 @@ pub struct LoadArgs { pub batch_size: Option, #[arg(long)] pub reset: Option, + /// Offset added to all warehouse IDs for this load. Use when adding warehouses + /// to a database that already has data (e.g. set to 70 to load warehouses 71-140 + /// into a database that already has warehouses 1-70). + #[arg(long)] + pub warehouse_id_offset: Option, + /// Skip loading the global Items table. Use together with --warehouse-id-offset + /// when adding warehouses to an existing database. + #[arg(long)] + pub skip_items: Option, } #[derive(Debug, Clone, Args)] @@ -188,6 +199,8 @@ struct FileLoadConfig { load_parallelism: Option, batch_size: Option, reset: Option, + warehouse_id_offset: Option, + skip_items: Option, } #[derive(Debug, Clone, Default, Deserialize)] @@ -284,6 +297,8 @@ impl LoadArgs { load_parallelism: load_parallelism.min(usize::try_from(num_databases).unwrap_or(usize::MAX)), batch_size, reset: self.reset.or(file.load.reset).unwrap_or(true), + warehouse_id_offset: self.warehouse_id_offset.or(file.load.warehouse_id_offset).unwrap_or(0), + skip_items: self.skip_items.or(file.load.skip_items).unwrap_or(false), }) } } diff --git a/tools/tpcc-runner/src/coordinator.rs b/tools/tpcc-runner/src/coordinator.rs index 5b1e5bb5ce1..6dc11630c2d 100644 --- a/tools/tpcc-runner/src/coordinator.rs +++ b/tools/tpcc-runner/src/coordinator.rs @@ -11,7 +11,7 @@ use std::time::Duration; use crate::config::CoordinatorConfig; use crate::metrics_module_bindings::reset_reducer::reset; -use crate::metrics_module_client::connect_metrics_module; +use crate::metrics_module_client::connect_metrics_module_async; use crate::protocol::{ DriverAssignment, RegisterDriverRequest, RegisterDriverResponse, RunSchedule, ScheduleResponse, SubmitSummaryRequest, @@ -68,37 +68,43 @@ async fn register_driver( State(state): State, Json(request): Json, ) -> Json { - let mut inner = state.inner.lock(); - let (assignment, is_new_registration) = match inner.registrations.get(&request.driver_id) { - Some(existing) => (existing.assignment.clone(), false), - None => { - if inner.registration_order.len() >= inner.config.expected_drivers { - return Json(RegisterDriverResponse { - accepted: false, - assignment: None, - }); + let (assignment, is_new_registration, registered, expected_drivers) = { + let mut inner = state.inner.lock(); + let expected_drivers = inner.config.expected_drivers; + match inner.registrations.get(&request.driver_id) { + Some(existing) => { + let registered = inner.registrations.len(); + (existing.assignment.clone(), false, registered, expected_drivers) + } + None => { + if inner.registration_order.len() >= expected_drivers { + return Json(RegisterDriverResponse { + accepted: false, + assignment: None, + }); + } + let index = inner.registration_order.len(); + let assignment = assignment_for_index(&inner.config, index); + inner.registration_order.push(request.driver_id.clone()); + inner.registrations.insert( + request.driver_id.clone(), + DriverRegistration { + assignment: assignment.clone(), + }, + ); + let registered = inner.registrations.len(); + (assignment, true, registered, expected_drivers) } - let index = inner.registration_order.len(); - let assignment = assignment_for_index(&inner.config, index); - inner.registration_order.push(request.driver_id.clone()); - inner.registrations.insert( - request.driver_id.clone(), - DriverRegistration { - assignment: assignment.clone(), - }, - ); - (assignment, true) } }; - maybe_create_schedule(&mut inner); - let registered = inner.registrations.len(); + maybe_create_schedule(&state).await; let warehouse_end = assignment_end(&assignment); if is_new_registration { log::info!( "driver {} registered and ready ({}/{}): warehouses {}..={} ({} warehouse(s))", request.driver_id, registered, - inner.config.expected_drivers, + expected_drivers, assignment.warehouse_start, warehouse_end, assignment.driver_warehouse_count @@ -168,35 +174,58 @@ async fn submit_summary( Ok(Json(aggregate)) } -fn maybe_create_schedule(inner: &mut CoordinatorState) { - if inner.schedule.is_some() || inner.registrations.len() < inner.config.expected_drivers { - return; - } +async fn maybe_create_schedule(state: &AppState) { + // Check whether schedule creation is needed, and grab config, without holding the lock + // during the async metrics module connection below. + let config = { + let inner = state.inner.lock(); + if inner.schedule.is_some() || inner.registrations.len() < inner.config.expected_drivers { + return; + } + inner.config.clone() + }; + let warmup_start_ms = now_millis() + 2_000; - let measure_start_ms = warmup_start_ms + (inner.config.warmup_secs * 1_000); - let measure_end_ms = measure_start_ms + (inner.config.measure_secs * 1_000); + let measure_start_ms = warmup_start_ms + (config.warmup_secs * 1_000); + let measure_end_ms = measure_start_ms + (config.measure_secs * 1_000); - let metrics_client = connect_metrics_module(&inner.config.connection).unwrap(); - let _ = metrics_client - .reducers - .reset(inner.config.warmup_secs * 1000, measure_start_ms, measure_end_ms); + let metrics_client = match connect_metrics_module_async(&config.connection).await { + Ok(client) => client, + Err(e) => { + log::error!("failed to connect to metrics module: {e:#}"); + return; + } + }; + let _ = metrics_client.reducers.reset( + config.warehouses as u64, + config.warmup_secs * 1000, + measure_start_ms, + measure_end_ms, + ); let schedule = RunSchedule { - run_id: inner.config.run_id.clone(), + run_id: config.run_id.clone(), warmup_start_ms, measure_start_ms, measure_end_ms, stop_ms: measure_end_ms, }; + + let mut inner = state.inner.lock(); + if inner.schedule.is_some() { + // Another concurrent registration call already created the schedule. + return; + } inner.schedule = Some(schedule.clone()); log::info!( "all {} driver(s) registered; schedule ready for run {} (warmup_start_ms={} measure_start_ms={} measure_end_ms={})", - inner.config.expected_drivers, - inner.config.run_id, + config.expected_drivers, + config.run_id, warmup_start_ms, measure_start_ms, measure_end_ms ); + drop(inner); tokio::spawn(log_schedule_events(schedule)); } diff --git a/tools/tpcc-runner/src/driver.rs b/tools/tpcc-runner/src/driver.rs index 13703f29846..e0e0bf084ff 100644 --- a/tools/tpcc-runner/src/driver.rs +++ b/tools/tpcc-runner/src/driver.rs @@ -10,8 +10,7 @@ use tokio::task::JoinSet; use crate::client::{expect_ok, ModuleClient}; use crate::config::{default_run_id, DriverConfig}; -use crate::metrics_module_bindings::register_completed_order; -use crate::metrics_module_bindings::DbConnection as MetricsDbConnection; +use crate::metrics_module_bindings::{record_txn_bucket, DbConnection as MetricsDbConnection}; use crate::metrics_module_client::connect_metrics_module_async; use crate::module_bindings::*; use crate::protocol::{ @@ -23,6 +22,8 @@ use crate::summary::{ use crate::topology::DatabaseTopology; use crate::tpcc::*; +const STARTUP_STAGGER_WINDOW_MS: u64 = 18_000; + struct TerminalRuntime { config: DriverConfig, client: Arc, @@ -227,6 +228,14 @@ async fn run_terminal(runtime: TerminalRuntime) -> Result<()> { ); } + let startup_stagger_ms = { + let mut startup_rng = rand::rng(); + startup_rng.random_range(0..=STARTUP_STAGGER_WINDOW_MS) + }; + if startup_stagger_ms > 0 && crate::summary::now_millis() < schedule.stop_ms { + tokio::time::sleep(Duration::from_millis(startup_stagger_ms)).await; + } + let mut rng = StdRng::seed_from_u64(seed); while !abort.load(Ordering::Relaxed) { if crate::summary::now_millis() >= schedule.stop_ms { @@ -234,6 +243,15 @@ async fn run_terminal(runtime: TerminalRuntime) -> Result<()> { } let kind = choose_transaction(&mut rng); + let keying_delay = keying_time(kind, config.keying_time_scale); + if !keying_delay.is_zero() && crate::summary::now_millis() < schedule.stop_ms { + tokio::time::sleep(keying_delay).await; + } + + if abort.load(Ordering::Relaxed) || crate::summary::now_millis() >= schedule.stop_ms { + break; + } + let started_ms = crate::summary::now_millis(); let context = TransactionContext { client: client.as_ref(), @@ -251,7 +269,7 @@ async fn run_terminal(runtime: TerminalRuntime) -> Result<()> { // Some metrics depend on knowing all completed orders, even outside the // measurement window if record.kind == TransactionKind::NewOrder && record.success { - let _ = metrics_client.reducers.register_completed_order(); + let _ = metrics_client.reducers.record_txn_bucket(); } if record.timestamp_ms >= schedule.measure_start_ms && record.timestamp_ms < schedule.measure_end_ms { @@ -259,14 +277,15 @@ async fn run_terminal(runtime: TerminalRuntime) -> Result<()> { } } Err(err) => { - abort.store(true, Ordering::Relaxed); - return Err(err); + log::error!( + "terminal task error: {err:#}", + ); } } - let delay = keying_time(kind, config.keying_time_scale) + think_time(kind, config.think_time_scale, &mut rng); - if !delay.is_zero() && crate::summary::now_millis() < schedule.stop_ms { - tokio::time::sleep(delay).await; + let think_delay = think_time(kind, config.think_time_scale, &mut rng); + if !think_delay.is_zero() && crate::summary::now_millis() < schedule.stop_ms { + tokio::time::sleep(think_delay).await; } } Ok(()) diff --git a/tools/tpcc-runner/src/loader.rs b/tools/tpcc-runner/src/loader.rs index 559aa819d79..3471ad89f9c 100644 --- a/tools/tpcc-runner/src/loader.rs +++ b/tools/tpcc-runner/src/loader.rs @@ -132,6 +132,8 @@ fn build_load_request( database_number, num_databases: config.num_databases, warehouses_per_database: config.warehouses_per_database, + warehouse_id_offset: config.warehouse_id_offset, + skip_items: config.skip_items, batch_size: u32::try_from(config.batch_size).context("batch_size exceeds u32")?, seed: LOAD_SEED, load_c_last, diff --git a/tools/tpcc-runner/src/metrics_module_bindings/clear_state_reducer.rs b/tools/tpcc-runner/src/metrics_module_bindings/clear_state_reducer.rs index fffd58d13d3..df42291348e 100644 --- a/tools/tpcc-runner/src/metrics_module_bindings/clear_state_reducer.rs +++ b/tools/tpcc-runner/src/metrics_module_bindings/clear_state_reducer.rs @@ -56,6 +56,7 @@ impl clear_state for super::RemoteReducers { + Send + 'static, ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(ClearStateArgs {}, callback) + self.imp + .invoke_reducer_with_callback::<_, ()>(ClearStateArgs {}, callback) } } diff --git a/tools/tpcc-runner/src/metrics_module_bindings/mod.rs b/tools/tpcc-runner/src/metrics_module_bindings/mod.rs index 6322519697f..0e35a1d7729 100644 --- a/tools/tpcc-runner/src/metrics_module_bindings/mod.rs +++ b/tools/tpcc-runner/src/metrics_module_bindings/mod.rs @@ -1,22 +1,32 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 2.1.0 (commit 6981f48b4bc1a71c8dd9bdfe5a2c343f6370243d). +// This was generated using spacetimedb cli version 2.1.0 (commit 7df4044d896459c2a6ca5b9e2c716052aa39e88a). #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; pub mod clear_state_reducer; -pub mod register_completed_order_reducer; +pub mod record_txn_bucket_reducer; +pub mod record_txn_reducer; pub mod reset_reducer; pub mod state_table; pub mod state_type; +pub mod txn_bucket_table; +pub mod txn_bucket_type; +pub mod txn_table; +pub mod txn_type; pub use clear_state_reducer::clear_state; -pub use register_completed_order_reducer::register_completed_order; +pub use record_txn_bucket_reducer::record_txn_bucket; +pub use record_txn_reducer::record_txn; pub use reset_reducer::reset; pub use state_table::*; pub use state_type::State; +pub use txn_bucket_table::*; +pub use txn_bucket_type::TxnBucket; +pub use txn_table::*; +pub use txn_type::Txn; #[derive(Clone, PartialEq, Debug)] @@ -27,8 +37,12 @@ pub use state_type::State; pub enum Reducer { ClearState, - RegisterCompletedOrder, + RecordTxn { + latency_ms: u16, + }, + RecordTxnBucket, Reset { + warehouse_count: u64, warmup_duration_ms: u64, measure_start_ms: u64, measure_end_ms: u64, @@ -43,7 +57,8 @@ impl __sdk::Reducer for Reducer { fn reducer_name(&self) -> &'static str { match self { Reducer::ClearState => "clear_state", - Reducer::RegisterCompletedOrder => "register_completed_order", + Reducer::RecordTxn { .. } => "record_txn", + Reducer::RecordTxnBucket => "record_txn_bucket", Reducer::Reset { .. } => "reset", _ => unreachable!(), } @@ -52,14 +67,17 @@ impl __sdk::Reducer for Reducer { fn args_bsatn(&self) -> Result, __sats::bsatn::EncodeError> { match self { Reducer::ClearState => __sats::bsatn::to_vec(&clear_state_reducer::ClearStateArgs {}), - Reducer::RegisterCompletedOrder => { - __sats::bsatn::to_vec(®ister_completed_order_reducer::RegisterCompletedOrderArgs {}) - } + Reducer::RecordTxn { latency_ms } => __sats::bsatn::to_vec(&record_txn_reducer::RecordTxnArgs { + latency_ms: latency_ms.clone(), + }), + Reducer::RecordTxnBucket => __sats::bsatn::to_vec(&record_txn_bucket_reducer::RecordTxnBucketArgs {}), Reducer::Reset { + warehouse_count, warmup_duration_ms, measure_start_ms, measure_end_ms, } => __sats::bsatn::to_vec(&reset_reducer::ResetArgs { + warehouse_count: warehouse_count.clone(), warmup_duration_ms: warmup_duration_ms.clone(), measure_start_ms: measure_start_ms.clone(), measure_end_ms: measure_end_ms.clone(), @@ -74,6 +92,8 @@ impl __sdk::Reducer for Reducer { #[doc(hidden)] pub struct DbUpdate { state: __sdk::TableUpdate, + txn: __sdk::TableUpdate, + txn_bucket: __sdk::TableUpdate, } impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { @@ -83,6 +103,10 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { for table_update in __sdk::transaction_update_iter_table_updates(raw) { match &table_update.table_name[..] { "state" => db_update.state.append(state_table::parse_table_update(table_update)?), + "txn" => db_update.txn.append(txn_table::parse_table_update(table_update)?), + "txn_bucket" => db_update + .txn_bucket + .append(txn_bucket_table::parse_table_update(table_update)?), unknown => { return Err(__sdk::InternalError::unknown_name("table", unknown, "DatabaseUpdate").into()); @@ -104,6 +128,12 @@ impl __sdk::DbUpdate for DbUpdate { diff.state = cache .apply_diff_to_table::("state", &self.state) .with_updates_by_pk(|row| &row.id); + diff.txn = cache + .apply_diff_to_table::("txn", &self.txn) + .with_updates_by_pk(|row| &row.id); + diff.txn_bucket = cache + .apply_diff_to_table::("txn_bucket", &self.txn_bucket) + .with_updates_by_pk(|row| &row.bucket_start_ms); diff } @@ -114,6 +144,10 @@ impl __sdk::DbUpdate for DbUpdate { "state" => db_update .state .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + "txn" => db_update.txn.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + "txn_bucket" => db_update + .txn_bucket + .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), unknown => { return Err(__sdk::InternalError::unknown_name("table", unknown, "QueryRows").into()); } @@ -128,6 +162,10 @@ impl __sdk::DbUpdate for DbUpdate { "state" => db_update .state .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + "txn" => db_update.txn.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + "txn_bucket" => db_update + .txn_bucket + .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), unknown => { return Err(__sdk::InternalError::unknown_name("table", unknown, "QueryRows").into()); } @@ -142,6 +180,8 @@ impl __sdk::DbUpdate for DbUpdate { #[doc(hidden)] pub struct AppliedDiff<'r> { state: __sdk::TableAppliedDiff<'r, State>, + txn: __sdk::TableAppliedDiff<'r, Txn>, + txn_bucket: __sdk::TableAppliedDiff<'r, TxnBucket>, __unused: std::marker::PhantomData<&'r ()>, } @@ -152,6 +192,8 @@ impl __sdk::InModule for AppliedDiff<'_> { impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { fn invoke_row_callbacks(&self, event: &EventContext, callbacks: &mut __sdk::DbCallbacks) { callbacks.invoke_table_row_callbacks::("state", &self.state, event); + callbacks.invoke_table_row_callbacks::("txn", &self.txn, event); + callbacks.invoke_table_row_callbacks::("txn_bucket", &self.txn_bucket, event); } } @@ -810,6 +852,8 @@ impl __sdk::SpacetimeModule for RemoteModule { fn register_tables(client_cache: &mut __sdk::ClientCache) { state_table::register_table(client_cache); + txn_table::register_table(client_cache); + txn_bucket_table::register_table(client_cache); } - const ALL_TABLE_NAMES: &'static [&'static str] = &["state"]; + const ALL_TABLE_NAMES: &'static [&'static str] = &["state", "txn", "txn_bucket"]; } diff --git a/tools/tpcc-runner/src/metrics_module_bindings/register_completed_order_reducer.rs b/tools/tpcc-runner/src/metrics_module_bindings/record_txn_bucket_reducer.rs similarity index 58% rename from tools/tpcc-runner/src/metrics_module_bindings/register_completed_order_reducer.rs rename to tools/tpcc-runner/src/metrics_module_bindings/record_txn_bucket_reducer.rs index 3cf5f048e5a..20c629a9720 100644 --- a/tools/tpcc-runner/src/metrics_module_bindings/register_completed_order_reducer.rs +++ b/tools/tpcc-runner/src/metrics_module_bindings/record_txn_bucket_reducer.rs @@ -6,40 +6,40 @@ use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; #[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] #[sats(crate = __lib)] -pub(super) struct RegisterCompletedOrderArgs {} +pub(super) struct RecordTxnBucketArgs {} -impl From for super::Reducer { - fn from(args: RegisterCompletedOrderArgs) -> Self { - Self::RegisterCompletedOrder +impl From for super::Reducer { + fn from(args: RecordTxnBucketArgs) -> Self { + Self::RecordTxnBucket } } -impl __sdk::InModule for RegisterCompletedOrderArgs { +impl __sdk::InModule for RecordTxnBucketArgs { type Module = super::RemoteModule; } #[allow(non_camel_case_types)] -/// Extension trait for access to the reducer `register_completed_order`. +/// Extension trait for access to the reducer `record_txn_bucket`. /// /// Implemented for [`super::RemoteReducers`]. -pub trait register_completed_order { - /// Request that the remote module invoke the reducer `register_completed_order` to run as soon as possible. +pub trait record_txn_bucket { + /// Request that the remote module invoke the reducer `record_txn_bucket` to run as soon as possible. /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, /// and this method provides no way to listen for its completion status. - /// /// Use [`register_completed_order:register_completed_order_then`] to run a callback after the reducer completes. - fn register_completed_order(&self) -> __sdk::Result<()> { - self.register_completed_order_then(|_, _| {}) + /// /// Use [`record_txn_bucket:record_txn_bucket_then`] to run a callback after the reducer completes. + fn record_txn_bucket(&self) -> __sdk::Result<()> { + self.record_txn_bucket_then(|_, _| {}) } - /// Request that the remote module invoke the reducer `register_completed_order` to run as soon as possible, + /// Request that the remote module invoke the reducer `record_txn_bucket` to run as soon as possible, /// registering `callback` to run when we are notified that the reducer completed. /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, /// and its status can be observed with the `callback`. - fn register_completed_order_then( + fn record_txn_bucket_then( &self, callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) @@ -48,8 +48,8 @@ pub trait register_completed_order { ) -> __sdk::Result<()>; } -impl register_completed_order for super::RemoteReducers { - fn register_completed_order_then( +impl record_txn_bucket for super::RemoteReducers { + fn record_txn_bucket_then( &self, callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) @@ -57,6 +57,6 @@ impl register_completed_order for super::RemoteReducers { + 'static, ) -> __sdk::Result<()> { self.imp - .invoke_reducer_with_callback(RegisterCompletedOrderArgs {}, callback) + .invoke_reducer_with_callback::<_, ()>(RecordTxnBucketArgs {}, callback) } } diff --git a/tools/tpcc-runner/src/metrics_module_bindings/record_txn_reducer.rs b/tools/tpcc-runner/src/metrics_module_bindings/record_txn_reducer.rs new file mode 100644 index 00000000000..3ccccd97476 --- /dev/null +++ b/tools/tpcc-runner/src/metrics_module_bindings/record_txn_reducer.rs @@ -0,0 +1,68 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct RecordTxnArgs { + pub latency_ms: u16, +} + +impl From for super::Reducer { + fn from(args: RecordTxnArgs) -> Self { + Self::RecordTxn { + latency_ms: args.latency_ms, + } + } +} + +impl __sdk::InModule for RecordTxnArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `record_txn`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait record_txn { + /// Request that the remote module invoke the reducer `record_txn` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and this method provides no way to listen for its completion status. + /// /// Use [`record_txn:record_txn_then`] to run a callback after the reducer completes. + fn record_txn(&self, latency_ms: u16) -> __sdk::Result<()> { + self.record_txn_then(latency_ms, |_, _| {}) + } + + /// Request that the remote module invoke the reducer `record_txn` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn record_txn_then( + &self, + latency_ms: u16, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; +} + +impl record_txn for super::RemoteReducers { + fn record_txn_then( + &self, + latency_ms: u16, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp + .invoke_reducer_with_callback::<_, ()>(RecordTxnArgs { latency_ms }, callback) + } +} diff --git a/tools/tpcc-runner/src/metrics_module_bindings/reset_reducer.rs b/tools/tpcc-runner/src/metrics_module_bindings/reset_reducer.rs index 7c63e35f429..f74c3cd3662 100644 --- a/tools/tpcc-runner/src/metrics_module_bindings/reset_reducer.rs +++ b/tools/tpcc-runner/src/metrics_module_bindings/reset_reducer.rs @@ -7,6 +7,7 @@ use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; #[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] #[sats(crate = __lib)] pub(super) struct ResetArgs { + pub warehouse_count: u64, pub warmup_duration_ms: u64, pub measure_start_ms: u64, pub measure_end_ms: u64, @@ -15,6 +16,7 @@ pub(super) struct ResetArgs { impl From for super::Reducer { fn from(args: ResetArgs) -> Self { Self::Reset { + warehouse_count: args.warehouse_count, warmup_duration_ms: args.warmup_duration_ms, measure_start_ms: args.measure_start_ms, measure_end_ms: args.measure_end_ms, @@ -37,8 +39,20 @@ pub trait reset { /// The reducer will run asynchronously in the future, /// and this method provides no way to listen for its completion status. /// /// Use [`reset:reset_then`] to run a callback after the reducer completes. - fn reset(&self, warmup_duration_ms: u64, measure_start_ms: u64, measure_end_ms: u64) -> __sdk::Result<()> { - self.reset_then(warmup_duration_ms, measure_start_ms, measure_end_ms, |_, _| {}) + fn reset( + &self, + warehouse_count: u64, + warmup_duration_ms: u64, + measure_start_ms: u64, + measure_end_ms: u64, + ) -> __sdk::Result<()> { + self.reset_then( + warehouse_count, + warmup_duration_ms, + measure_start_ms, + measure_end_ms, + |_, _| {}, + ) } /// Request that the remote module invoke the reducer `reset` to run as soon as possible, @@ -49,6 +63,7 @@ pub trait reset { /// and its status can be observed with the `callback`. fn reset_then( &self, + warehouse_count: u64, warmup_duration_ms: u64, measure_start_ms: u64, measure_end_ms: u64, @@ -62,6 +77,7 @@ pub trait reset { impl reset for super::RemoteReducers { fn reset_then( &self, + warehouse_count: u64, warmup_duration_ms: u64, measure_start_ms: u64, measure_end_ms: u64, @@ -70,8 +86,9 @@ impl reset for super::RemoteReducers { + Send + 'static, ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback( + self.imp.invoke_reducer_with_callback::<_, ()>( ResetArgs { + warehouse_count, warmup_duration_ms, measure_start_ms, measure_end_ms, diff --git a/tools/tpcc-runner/src/metrics_module_bindings/state_type.rs b/tools/tpcc-runner/src/metrics_module_bindings/state_type.rs index 98d8a762262..307bdcaaef9 100644 --- a/tools/tpcc-runner/src/metrics_module_bindings/state_type.rs +++ b/tools/tpcc-runner/src/metrics_module_bindings/state_type.rs @@ -12,8 +12,7 @@ pub struct State { pub run_end_ms: u64, pub measure_start_ms: u64, pub measure_end_ms: u64, - pub order_count: u64, - pub measurement_time_ms: u64, + pub warehouse_count: u64, } impl __sdk::InModule for State { @@ -29,8 +28,7 @@ pub struct StateCols { pub run_end_ms: __sdk::__query_builder::Col, pub measure_start_ms: __sdk::__query_builder::Col, pub measure_end_ms: __sdk::__query_builder::Col, - pub order_count: __sdk::__query_builder::Col, - pub measurement_time_ms: __sdk::__query_builder::Col, + pub warehouse_count: __sdk::__query_builder::Col, } impl __sdk::__query_builder::HasCols for State { @@ -42,8 +40,7 @@ impl __sdk::__query_builder::HasCols for State { run_end_ms: __sdk::__query_builder::Col::new(table_name, "run_end_ms"), measure_start_ms: __sdk::__query_builder::Col::new(table_name, "measure_start_ms"), measure_end_ms: __sdk::__query_builder::Col::new(table_name, "measure_end_ms"), - order_count: __sdk::__query_builder::Col::new(table_name, "order_count"), - measurement_time_ms: __sdk::__query_builder::Col::new(table_name, "measurement_time_ms"), + warehouse_count: __sdk::__query_builder::Col::new(table_name, "warehouse_count"), } } } diff --git a/tools/tpcc-runner/src/metrics_module_bindings/txn_bucket_table.rs b/tools/tpcc-runner/src/metrics_module_bindings/txn_bucket_table.rs new file mode 100644 index 00000000000..14b3d56c3cf --- /dev/null +++ b/tools/tpcc-runner/src/metrics_module_bindings/txn_bucket_table.rs @@ -0,0 +1,157 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::txn_bucket_type::TxnBucket; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `txn_bucket`. +/// +/// Obtain a handle from the [`TxnBucketTableAccess::txn_bucket`] method on [`super::RemoteTables`], +/// like `ctx.db.txn_bucket()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.txn_bucket().on_insert(...)`. +pub struct TxnBucketTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `txn_bucket`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait TxnBucketTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`TxnBucketTableHandle`], which mediates access to the table `txn_bucket`. + fn txn_bucket(&self) -> TxnBucketTableHandle<'_>; +} + +impl TxnBucketTableAccess for super::RemoteTables { + fn txn_bucket(&self) -> TxnBucketTableHandle<'_> { + TxnBucketTableHandle { + imp: self.imp.get_table::("txn_bucket"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct TxnBucketInsertCallbackId(__sdk::CallbackId); +pub struct TxnBucketDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for TxnBucketTableHandle<'ctx> { + type Row = TxnBucket; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = TxnBucketInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> TxnBucketInsertCallbackId { + TxnBucketInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: TxnBucketInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = TxnBucketDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> TxnBucketDeleteCallbackId { + TxnBucketDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: TxnBucketDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +pub struct TxnBucketUpdateCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::TableWithPrimaryKey for TxnBucketTableHandle<'ctx> { + type UpdateCallbackId = TxnBucketUpdateCallbackId; + + fn on_update( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, + ) -> TxnBucketUpdateCallbackId { + TxnBucketUpdateCallbackId(self.imp.on_update(Box::new(callback))) + } + + fn remove_on_update(&self, callback: TxnBucketUpdateCallbackId) { + self.imp.remove_on_update(callback.0) + } +} + +/// Access to the `bucket_start_ms` unique index on the table `txn_bucket`, +/// which allows point queries on the field of the same name +/// via the [`TxnBucketBucketStartMsUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.txn_bucket().bucket_start_ms().find(...)`. +pub struct TxnBucketBucketStartMsUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> TxnBucketTableHandle<'ctx> { + /// Get a handle on the `bucket_start_ms` unique index on the table `txn_bucket`. + pub fn bucket_start_ms(&self) -> TxnBucketBucketStartMsUnique<'ctx> { + TxnBucketBucketStartMsUnique { + imp: self.imp.get_unique_constraint::("bucket_start_ms"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> TxnBucketBucketStartMsUnique<'ctx> { + /// Find the subscribed row whose `bucket_start_ms` column value is equal to `col_val`, + /// if such a row is present in the client cache. + pub fn find(&self, col_val: &u64) -> Option { + self.imp.find(col_val) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("txn_bucket"); + _table.add_unique_constraint::("bucket_start_ms", |row| &row.bucket_start_ms); +} + +#[doc(hidden)] +pub(super) fn parse_table_update(raw_updates: __ws::v2::TableUpdate) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `TxnBucket`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait txn_bucketQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `TxnBucket`. + fn txn_bucket(&self) -> __sdk::__query_builder::Table; +} + +impl txn_bucketQueryTableAccess for __sdk::QueryTableAccessor { + fn txn_bucket(&self) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("txn_bucket") + } +} diff --git a/tools/tpcc-runner/src/metrics_module_bindings/txn_bucket_type.rs b/tools/tpcc-runner/src/metrics_module_bindings/txn_bucket_type.rs new file mode 100644 index 00000000000..774dafd2063 --- /dev/null +++ b/tools/tpcc-runner/src/metrics_module_bindings/txn_bucket_type.rs @@ -0,0 +1,52 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct TxnBucket { + pub bucket_start_ms: u64, + pub count: u64, +} + +impl __sdk::InModule for TxnBucket { + type Module = super::RemoteModule; +} + +/// Column accessor struct for the table `TxnBucket`. +/// +/// Provides typed access to columns for query building. +pub struct TxnBucketCols { + pub bucket_start_ms: __sdk::__query_builder::Col, + pub count: __sdk::__query_builder::Col, +} + +impl __sdk::__query_builder::HasCols for TxnBucket { + type Cols = TxnBucketCols; + fn cols(table_name: &'static str) -> Self::Cols { + TxnBucketCols { + bucket_start_ms: __sdk::__query_builder::Col::new(table_name, "bucket_start_ms"), + count: __sdk::__query_builder::Col::new(table_name, "count"), + } + } +} + +/// Indexed column accessor struct for the table `TxnBucket`. +/// +/// Provides typed access to indexed columns for query building. +pub struct TxnBucketIxCols { + pub bucket_start_ms: __sdk::__query_builder::IxCol, +} + +impl __sdk::__query_builder::HasIxCols for TxnBucket { + type IxCols = TxnBucketIxCols; + fn ix_cols(table_name: &'static str) -> Self::IxCols { + TxnBucketIxCols { + bucket_start_ms: __sdk::__query_builder::IxCol::new(table_name, "bucket_start_ms"), + } + } +} + +impl __sdk::__query_builder::CanBeLookupTable for TxnBucket {} diff --git a/tools/tpcc-runner/src/metrics_module_bindings/txn_table.rs b/tools/tpcc-runner/src/metrics_module_bindings/txn_table.rs new file mode 100644 index 00000000000..8d867b3001d --- /dev/null +++ b/tools/tpcc-runner/src/metrics_module_bindings/txn_table.rs @@ -0,0 +1,151 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::txn_type::Txn; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `txn`. +/// +/// Obtain a handle from the [`TxnTableAccess::txn`] method on [`super::RemoteTables`], +/// like `ctx.db.txn()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.txn().on_insert(...)`. +pub struct TxnTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `txn`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait TxnTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`TxnTableHandle`], which mediates access to the table `txn`. + fn txn(&self) -> TxnTableHandle<'_>; +} + +impl TxnTableAccess for super::RemoteTables { + fn txn(&self) -> TxnTableHandle<'_> { + TxnTableHandle { + imp: self.imp.get_table::("txn"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct TxnInsertCallbackId(__sdk::CallbackId); +pub struct TxnDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for TxnTableHandle<'ctx> { + type Row = Txn; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = TxnInsertCallbackId; + + fn on_insert(&self, callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static) -> TxnInsertCallbackId { + TxnInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: TxnInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = TxnDeleteCallbackId; + + fn on_delete(&self, callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static) -> TxnDeleteCallbackId { + TxnDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: TxnDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +pub struct TxnUpdateCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::TableWithPrimaryKey for TxnTableHandle<'ctx> { + type UpdateCallbackId = TxnUpdateCallbackId; + + fn on_update( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, + ) -> TxnUpdateCallbackId { + TxnUpdateCallbackId(self.imp.on_update(Box::new(callback))) + } + + fn remove_on_update(&self, callback: TxnUpdateCallbackId) { + self.imp.remove_on_update(callback.0) + } +} + +/// Access to the `id` unique index on the table `txn`, +/// which allows point queries on the field of the same name +/// via the [`TxnIdUnique::find`] method. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.txn().id().find(...)`. +pub struct TxnIdUnique<'ctx> { + imp: __sdk::UniqueConstraintHandle, + phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +impl<'ctx> TxnTableHandle<'ctx> { + /// Get a handle on the `id` unique index on the table `txn`. + pub fn id(&self) -> TxnIdUnique<'ctx> { + TxnIdUnique { + imp: self.imp.get_unique_constraint::("id"), + phantom: std::marker::PhantomData, + } + } +} + +impl<'ctx> TxnIdUnique<'ctx> { + /// Find the subscribed row whose `id` column value is equal to `col_val`, + /// if such a row is present in the client cache. + pub fn find(&self, col_val: &u64) -> Option { + self.imp.find(col_val) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("txn"); + _table.add_unique_constraint::("id", |row| &row.id); +} + +#[doc(hidden)] +pub(super) fn parse_table_update(raw_updates: __ws::v2::TableUpdate) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `Txn`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait txnQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `Txn`. + fn txn(&self) -> __sdk::__query_builder::Table; +} + +impl txnQueryTableAccess for __sdk::QueryTableAccessor { + fn txn(&self) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("txn") + } +} diff --git a/tools/tpcc-runner/src/metrics_module_bindings/txn_type.rs b/tools/tpcc-runner/src/metrics_module_bindings/txn_type.rs new file mode 100644 index 00000000000..f8a8ca08b78 --- /dev/null +++ b/tools/tpcc-runner/src/metrics_module_bindings/txn_type.rs @@ -0,0 +1,55 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct Txn { + pub id: u64, + pub measurement_time_ms: u64, + pub latency_ms: u16, +} + +impl __sdk::InModule for Txn { + type Module = super::RemoteModule; +} + +/// Column accessor struct for the table `Txn`. +/// +/// Provides typed access to columns for query building. +pub struct TxnCols { + pub id: __sdk::__query_builder::Col, + pub measurement_time_ms: __sdk::__query_builder::Col, + pub latency_ms: __sdk::__query_builder::Col, +} + +impl __sdk::__query_builder::HasCols for Txn { + type Cols = TxnCols; + fn cols(table_name: &'static str) -> Self::Cols { + TxnCols { + id: __sdk::__query_builder::Col::new(table_name, "id"), + measurement_time_ms: __sdk::__query_builder::Col::new(table_name, "measurement_time_ms"), + latency_ms: __sdk::__query_builder::Col::new(table_name, "latency_ms"), + } + } +} + +/// Indexed column accessor struct for the table `Txn`. +/// +/// Provides typed access to indexed columns for query building. +pub struct TxnIxCols { + pub id: __sdk::__query_builder::IxCol, +} + +impl __sdk::__query_builder::HasIxCols for Txn { + type IxCols = TxnIxCols; + fn ix_cols(table_name: &'static str) -> Self::IxCols { + TxnIxCols { + id: __sdk::__query_builder::IxCol::new(table_name, "id"), + } + } +} + +impl __sdk::__query_builder::CanBeLookupTable for Txn {} diff --git a/tools/tpcc-runner/src/module_bindings/tpcc_load_config_request_type.rs b/tools/tpcc-runner/src/module_bindings/tpcc_load_config_request_type.rs index 0e38d4f9edd..7f43a508490 100644 --- a/tools/tpcc-runner/src/module_bindings/tpcc_load_config_request_type.rs +++ b/tools/tpcc-runner/src/module_bindings/tpcc_load_config_request_type.rs @@ -10,6 +10,8 @@ pub struct TpccLoadConfigRequest { pub database_number: u32, pub num_databases: u32, pub warehouses_per_database: u32, + pub warehouse_id_offset: u32, + pub skip_items: bool, pub batch_size: u32, pub seed: u64, pub load_c_last: u32,