Skip to main content
Version: Next

Transactions & Errors

Transactional workloads in Stroppy v6 are implemented in Go. Users choose a registered variant, driver, executor, and optional isolation override; workload code owns transaction boundaries and retry policy.

Transaction variants​

WorkloadModelDrivers
tpcb/txOrdered DML inside a driver transactionPostgreSQL, MySQL, Picodata (none), YDB
tpcb/procsOne server-side procedure call per iterationPostgreSQL, MySQL
tpcc/txFive TPC-C transaction bodies as ordered DMLPostgreSQL, MySQL, Picodata (none), YDB
tpcc/procsOne stored procedure per selected TPC-C transactionPostgreSQL, MySQL
stroppy run tpcc/tx -d pg \
--executor constant-vus --vus 32 --duration 5m

stroppy run tpcc/procs -d mysql \
--executor shared-iterations --iterations 100

Isolation levels​

NameMeaning
read_uncommittedLowest standard SQL isolation.
read_committedEach statement sees committed rows.
repeatable_readStable transaction snapshot where supported.
serializableHighest standard isolation.
db_defaultBackend default.
connDedicated connection without issuing BEGIN; commit/rollback release it.
noneNo transaction and no pinned connection; queries use pool directly.

Override a workload default:

stroppy run tpcb/tx -d pg --tx-isolation serializable
stroppy run tpcc/tx -d pico --tx-isolation none

V6 defaults:

Workload familyPostgreSQLMySQLPicodataYDB
TPC-Bread_committedread_committednoneserializable
TPC-Crepeatable_readrepeatable_readnoneserializable

Picodata's driver does not implement Begin, so none is required for its portable transaction-shaped workload paths.

conn and none​

conn is useful when several statements need one session without database transaction semantics. Drivers acquire one connection and release it on commit or rollback.

none keeps the workload's transaction-shaped control flow but sends each query through the pool independently. Commit and rollback are no-ops. It does not provide atomicity.

Retry policy​

Drivers classify backend errors into shared facts. Transactional workloads build a policy from those facts rather than matching backend messages.

Default retry categories:

  • serialization conflict;
  • deadlock;
  • lock timeout;
  • unconditional transient failure.

Conditional transient failures are retried only for operations marked idempotent. Unknown errors are returned.

--retry-attempts sets the maximum total attempts for TPC-B and TPC-C:

stroppy run tpcc/tx --retry-attempts 5

A scheduled retry increments retry metrics. If a later attempt succeeds, the iteration succeeds and the run is not marked as completed with errors.

Terminal nonfatal errors​

When retries are exhausted or policy returns a nonfatal error:

  • one transaction iteration fails;
  • its VU continues with the next iteration;
  • bounded WARN records identify operation and error class;
  • counters record terminal error and failed iteration;
  • final output includes bench completed with errors;
  • process exits 0.

Query-set workloads (TPC-H, TPC-DS, and direct SQL) similarly count a failed query and continue with the next query when the error is nonfatal.

This exit behavior lets a long stress run finish and report aggregate failure rates. CI that requires zero terminal errors should gate on summary counters or the final marker, not exit status alone.

Fatal and structural errors​

These remain nonzero:

  • invalid command/config/parameter input;
  • setup or validation failure;
  • workload teardown or driver teardown failure;
  • explicit fatal workload policy;
  • unrecoverable engine error.

Fatal policy stops the scenario and reports the error once.

TPC-H/TPC-DS reference-answer differences remain diagnostic. They are logged without changing exit status.

Cancellation​

First SIGINT or SIGTERM cancels the scenario, propagates context cancellation to queries and load workers, then runs teardown.

EventExit status
Graceful SIGINT130
Graceful SIGTERM143
Second signal, forced exit2

Cancellation is not counted as a terminal benchmark error.

Transaction metrics​

Core native metrics include:

MetricInstrumentMeaning
transactions_totalcounterTransactions observed.
tx_total_durationhistogram (ms)Wall-clock transaction duration.
tx_commits_totalcounterSuccessful commits.
tx_errors_totalcounterTransaction errors.
tx_queries_per_txhistogramQuery count per transaction.
retry_attempts_totalcounterScheduled retries.
failed_iterations_totalcounterTerminal failed iterations.
terminal_errors_totalcounterTerminal transaction/query errors.

Individual query operations also update run_query_operations_total, run_query_errors_total, and run_query_duration.

Metrics carry bounded attributes such as step, transaction name, isolation, action, error class, table, and progress event where relevant. OTLP export adds the configured prefix (stroppy_ by default).

Two-pass benchmark workflow​

Load once:

stroppy run tpcc/tx -d pg --scale-factor 50 --load-workers 16 \
--no-steps workload

Measure only:

stroppy run tpcc/tx -d pg --scale-factor 50 \
--executor constant-vus --vus 64 --duration 1h \
--steps workload

This keeps load time out of the transaction throughput interval.

Driver authoring interface​

Go workloads call Bench.Begin with a BeginOpts isolation/name and use the returned query API. Drivers implement:

type Tx interface {
RunQuery(context.Context, string, map[string]any) (*QueryResult, error)
Commit(context.Context) error
Rollback(context.Context) error
Isolation() config.TxIsolationLevel
}

Drivers also implement ClassifyError(error) ErrorFacts; workloads decide what to retry, return, ignore, or mark fatal. See Extensibility.