Skip to main content
Version: Next

Changelog

[Unreleased]

Added

  • CockroachDB SQL variants for TPC-B and TPC-C (--sql-file tpcb/crdb.sql or tpcc/crdb.sql) retain native workload metrics. (#166)
  • Export native transaction, iteration and query throughput over the measured workload window, including explicit zero error counters. (#166)

Fixed

  • CockroachDB TPC-C loads ignore the PostgreSQL-only unlogged option instead of issuing unsupported durability changes. (#166)
  • Yandex Cloud dedicated connections load the internal CA before the first IAM-token handshake; explicit credentials are never replaced by VM metadata credentials after an error. (#166)
  • TPC-C population validation retries transient read failures within the configured attempt limit and reports setup retries separately; data mismatches still fail validation. (#166)
  • Retry YDB overload errors even when the SDK also reports cancellation of its query stream; explicit caller cancellation still stops the workload. (#166)
  • TPC-C population checks now log the underlying database query error, and setup failures report zero measurement time when execution never started. (#166)
  • The simple workload uses YDB-compatible table definitions and accepts unsigned row counts. (#166)
  • Picodata now honors pgx pool and query execution options supplied through config files or driver flags. (#166)
  • Picodata reads exact decimal values in text format to avoid a legacy numeric-zero decoding hang. (#166)
  • TPC-C population validation handles fractional Picodata percentages without decimal encoding errors. (#166)
  • Retry MySQL/MariaDB snapshot conflicts (error 1020) according to the workload's serialization retry policy. (#166)
  • Count the final partial SQL insert batch once in load progress, keeping generated and confirmed row counts consistent. (#166)
  • Keep concurrent benchmark metrics distinct and preserve run metadata when exporting through OTLP to Prometheus-compatible storage. (#166)
  • Count expected TPC-C procedure rollbacks as successful logical transactions on PostgreSQL and MySQL, while preserving rollback failures. (#166)

[6.0.0] - 2026-09-08

Added

  • New stroppy baseline command measures stroppy's own performance ceiling on the current machine without a database: a noop-driver framework tier and a pg-wire protocol tier against the pg-noop blackhole server, each reporting load throughput plus single-VU and parallel transaction rates. Verdicts check hardware-independent invariants (parallel scaling, loopback latency floor, measurement sanity), every run saves a versioned JSON report under ~/.stroppy/baselines/ with a delta versus the previous run, and the pg-noop server ships embedded in release builds or downloads with consent (sha256-verified) otherwise. (#162)
  • --query-timeout (also QUERY_TIMEOUT and config run.queryTimeout) bounds each executed statement with a per-statement deadline; 0 (the default) disables it. Timed-out statements are reported distinctly from a canceled run, and MySQL adds a server-side MAX_EXECUTION_TIME hint so a timed-out query keeps its pooled connection. (#153)
  • All TPC-C runs now emit text and JSON reports with per-transaction count, mix, throughput, and p50/p90/p95/p99 response times; paced runs additionally receive §5.2.5 response-time and transaction-mix verdicts, statistical-validity status, and a steady-state assessment, while unpaced runs mark compliance not applicable. (#147)
  • Restored the tpcb/procs workload: TPC-B ships both tx and procs variants again, with tpcb/procs running each transaction as one server-side stored-procedure call (tpcb_transaction) on PostgreSQL and MySQL. (#146)
  • stroppy probe now lists registered workload parameter flags, and its JSON output includes each workload's typed schema for tooling and discovery. (#128)
  • Typed scenario and workload parameters can be set with --name flags or native JSON values in the config file's run and params objects, and stroppy run <workload> --help lists the available parameters. (#128)
  • Go workloads can declare typed string, boolean, numeric, and duration parameters with defaults, descriptions, source tracking, and discoverable schemas. (#128)
  • Database errors are classified by each driver into shared facts, and Go workloads can override the default retry, error, ignore, or fatal action for each fact without matching backend-specific codes or messages. (#127)
  • Reusable generation primitives now live in pkg/gen, while pkg/datagen/source defines the row-production seam and pkg/datagen/tpchgen and pkg/datagen/tpcdsgen adapt the canonical TPC-H and TPC-DS generators. (#126)
  • pkg/gen now provides typed direct-output batches: a reusable columnar [Batch] with bound [Column] handles and an [IndexedSource] that fills rows through a plain Go row callback, so workload formulas write straight into prepared storage with zero generation-time allocations after preparation. (#126)
  • Insert-method ownership moves to the driver package: driver.InsertMethod is the Go-native enum for the typed insert path, with ParseInsertMethod for authoring strings (plain_query, plain_bulk, columnar, native). (#126)
  • Typed insert path: driver.InsertRequest + Driver.Insert + Bench.Insert stream rows from a workload-authored gen.BatchSource through every driver (postgres, mysql, picodata, ydb, noop, csv), with a typed parallel runner in pkg/driver/common. (#126)
  • The simple workload loads stroppy_demo through the typed insert path: a plain Go row formula (id, 8-char label, uniform value) over a versioned gen source replaces the relational InsertSpec struct literal. (#126)
  • TPC-B loads pgbench_branches, pgbench_tellers, and pgbench_accounts through the typed insert path: per-table gen sources preserve the bid fan-out arithmetic (floor(entity/perBranch)+1), fixed-width ASCII fillers, and the legacy per-table seeds. (#126)
  • TPC-C loads all eight tables (warehouse, district, customer, item, stock, orders, order_line, new_order) through typed plain-Go sources, preserving NURand surnames, per-district customer permutations, fixed-width fields, decimal scales, credit and delivery splits, and ORIGINAL markers. (#126)
  • TPC-H loads through the typed insert path: all eight tables (region, nation, part, supplier, partsupp, customer, orders, lineitem) stream from the canonical dbgen generator through pkg/datagen/tpchgen and gen.BatchSource. Canonical seeds, partition seeking, entity fan-out, and SF=1 output are unchanged; dbgen's Make* layer remains the documented internal allocation boundary. (#126)
  • TPC-DS loads through the typed insert path: all 24 tables (18 dimension tables, inventory, and 6 fan-out sales/returns fact tables) stream from the canonical dsdgen generator through pkg/datagen/tpcdsgen and gen.BatchSource, preserving text output, null semantics, ticket fan-out, partition seeking, and nominal fact-row reporting. (#126)
  • The legacy InsertSpec load path is gone. Driver.InsertSpec, Bench.InsertSpec, loadsource.Build, the per-driver InsertSpec methods, RunParallelByWorkers, and the dgproto↔driver MethodFromProto/MethodToProto boundary converters are removed; every workload now loads exclusively through the typed driver.Insert/Bench.Insert path over gen.BatchSource. The shared Chunk/SplitChunks helpers moved to pkg/driver/common/chunks.go; per-driver runInsertChunk/bulk/COPY helpers are unchanged. Insert-method strings (plain_query, plain_bulk, columnar, native), probe output, and progress/metrics semantics are preserved. (#126)
  • Metrics can again be exported to an OpenTelemetry collector through the existing global.exporter.otlpExport gRPC or HTTP configuration. (#125)

Changed

  • Benchmark duration histograms add sub-100µs buckets (5–50µs) so loopback-scale latencies are no longer clamped to the coarsest 100µs bucket. (#162)
  • Nonfatal transaction and query-set errors now keep virtual users running, exit successfully, and appear in bounded warnings, terminal-error metrics, and a prominent final summary; the unused driver errorMode option has been removed and is now rejected. (#156)
  • Logging now uses one safely replaceable process-wide logger, with configurable level and output mode precedence plus redacted database connection diagnostics. (#154)
  • Driver insert-method defaults now fill only load requests that leave their method unset, preserving methods selected by workloads. (#152)
  • SIGINT and SIGTERM now cancel the running workload and trigger graceful teardown; a second signal forces immediate exit. Exit status is 130 (SIGINT) or 143 (SIGTERM) after a graceful cancellation, 2 after a forced exit, and 1 for other errors. (#148)
  • Built-in workloads expose their tuning options as typed parameters while preserving the existing environment-variable names. (#128)
  • TPC-DS typed loads format common cell types directly into reusable buffers. (#126)
  • Benchmark metrics now use standard OpenTelemetry counters, gauges, and fixed-bucket histograms. Query throughput and error rates are derived from monotonic *_total counters instead of k6-style sampled rates. (#125)
  • The stroppy help <topic> topics (drivers, config-file, steps, resolution, sql, envs, datagen, probe) now describe the Go-native binary — the previous text still documented the removed TypeScript/k6 workflow (k6Args, declareDriverSetup, .ts script mode, the -- passthrough).
  • Stroppy configuration is now plain Go under pkg/config instead of frozen application protobuf types. Existing v5 lower-camel and snake_case JSON field names, nullable fields, int32 number forms, and logger enum names/ordinals remain compatible, while duplicate, colliding, mis-cased, unknown, malformed, and trailing input is rejected recursively across config files and raw driver JSON. global.seed is the sole accepted-form change and now requires a bare unsigned JSON integer. The generated schema documents the complete stroppy-config.json envelope, including run.queryTimeout and workload parameters. (#150)
  • Inputs now resolve once through typed parameters and pass directly to workloads and drivers instead of using process-environment bridges for step filters, execute-SQL sources, pool sizing, CSV workload identity, or serialized driver config. Process environment variables remain supported as typed input sources, space-separated --sql-body accepts query text beginning with a --= name marker, and SQL sources without named queries report a source-neutral error. The historical k6Args, k6Config, and driver defaultTxIsolation fields are now rejected as unknown; use typed executor settings and each workload's --tx-isolation parameter instead. (#151)

Fixed

  • Unknown run flags and arguments after -- now point to workload help and supported typed flags instead of recommending a removed passthrough. (#164)
  • Baseline runs now reject empty measurements and invalid negative inputs, isolate concurrent benchmark state, cancel and bound pg-noop downloads, use target-specific authoritative server caches, verify embedded release assets against Stroppy-pinned checksums, and preserve report output and history integrity. (#162)
  • CSV output now publishes shards, merged files, and manifests atomically across fresh and repeated loads, so canceled or failed loads retain recoverable shards and never expose partial output as complete. (#157)
  • Query helpers now return an empty result instead of panicking when a driver supplies no result set, and query timeouts that surface while closing result sets are reported once instead of repeating the same error. (#153)
  • Empty, whitespace-only, or comma-only step filters no longer conflict with a real opposite filter; --steps= still clears configured steps before --no-steps is applied. (#149)
  • The workload step is silent on the console again (no Start/End record per transaction), while setup/load/schema steps still log a single start/end and the simple workload now honors --steps/--no-steps like the other workloads. (#149)
  • --steps and --no-steps are rejected as mutually exclusive even when one is set in the config file and the other on the command line. (#149)
  • TPC-B fails fast with a named missing query/section instead of running a silent noop iteration when a custom SQL file omits required statements. (#145)
  • TPC-C treats absent customer, warehouse, item, and stock rows as transaction errors, propagates rollback failures instead of reporting an unknown outcome as success, and fails population-validation checks whose aggregate queries error. (#145)
  • stroppy version now reports the real build version for Docker images (pushed tag), nightly artifacts (nightly-<short-sha>), and release archives (release tag), instead of the generic 0.0.0 fallback. (#144)
  • Workload help and shell completion expose typed flags accurately, including explicit booleans and contextual defaults, and SQL override positionals reach registered workload bindings. (#128)
  • Typed run parameters and SQL sources keep CLI-over-environment precedence, config env names reject case-only collisions, shared driver pool settings remain active alongside driver-specific settings, and invalid pool fields fail clearly instead of being ignored. (#128)
  • Typed inserts reject malformed requests consistently, and generator ranges remain correct at integer boundaries. (#126)
  • Long high-throughput workloads keep bounded metric memory instead of retaining every latency observation until the final summary. (#125)

Removed

  • The relational data-generation expression framework is gone. pkg/datagen/{compile,expr,runtime,lookup,cohort,stdlib,seed} and the frozen pkg/datagen/dgproto protobuf types (InsertSpec, Expr, StreamDraw, …) are deleted. Reusable generation primitives now live in pkg/gen; pkg/datagen/source is the row-production seam, and pkg/datagen/tpchgen and pkg/datagen/tpcdsgen adapt the surviving canonical generators. The datagen-framework.md and proto.md guides were removed — docs/parallelism.md is the load-parallelism reference. (#126)
  • Stroppy no longer depends on k6, TypeScript, sobek, esbuild, or node/npm. The engine is now a single plain Go binary built with go build — authoring benchmarks in TypeScript, the -- k6-args passthrough, the gen scaffolding command, and the cloud status gRPC service are all gone. Configure runs primarily with --executor, --vus, --iterations, --duration, and --query-timeout, or the matching typed run config fields; environment variables remain compatibility inputs. Workloads are Go-native (tpcc/tx, tpcb/tx, tpch/tx, tpcds, simple, execute_sql); .sql files and inline SQL still work.

[5.7.3] - 2026-07-29

Fixed

  • PACING=true now applies keying and think-time delays to tpcc/procs as well as tpcc/tx. The pacing code lived only in the tx variant, so stored-procedure runs ignored it entirely and ran unpaced regardless of the flag. (#114)

Changed

  • The tpcc/tx and tpcc/procs workloads now share their driver setup, load/prepare lifecycle, retry policy, pacing, weighted dispatch, and post-run summary through tpcc_common.ts (matching the existing tpcb layout), instead of each carrying its own copy. Both variants now surface database errors as exceptions consistently (previously procs threw while tx only logged). (#114)

[5.7.2] - 2026-07-27

Changed

  • PostgreSQL data loads now keep tables LOGGED by default instead of flipping them to UNLOGGED for the bulk load. The UNLOGGED optimization (WAL-free load, then flip back) is now opt-in via -e PG_UNLOGGED=true. It traded load speed for a sharp footgun: a prepare that ran twice on the same schema failed with could not change table "warehouse" to unlogged because it references logged table "district" (42P16), because the foreign keys added at the end of the first prepare block the unlogged flip at the start of the second — so re-running a workload without dropping the schema aborted every iteration. Logged-by-default removes that failure mode entirely. (#111)
  • Stroppy allocates far less memory in the hot transaction loop. Every reference to a named import (Step, ENV, Rel, Draw, retry, DriverX, …) had k6 rebuild the full module exports table from scratch, which a 30s heap profile showed as the single largest allocator. The exports table is now built once per VU and reused, cutting that churn entirely. (#110)

[5.7.1] - 2026-07-23

Changed

  • Stroppy runs scale better at high VU counts. A single shared mutex guarded the active-step tag and was write-locked on every Step() plus read on every metric sample, so all VUs serialized on it — a 30s CPU profile of a tpcb run showed ~940 of ~1000 goroutines parked waiting for it while the database sat idle. The step tag now lives per-VU (lock-free), and the per-transaction metrics snapshot path no longer takes a mutex (pointers are immutable after one-time registration). Microbenchmarks of both paths drop ~140 ns/op to under 1 ns/op at 8 cores. (#109)

Fixed

  • TPC-B transactions now retry on serialization conflicts instead of failing the run. The tpcb/tx workload issued its transaction with no retry wrapper, so under concurrent VUs the first serializable abort — PostgreSQL 40001/40P01, MySQL 1213, or YDB Transaction locks invalidated — was thrown straight to the error log and aborted the whole run, even though every other transactional workload (tpcc/tpch/tpcds) already retries these. tpcb now applies the same retry policy, so transient contention is replayed instead of surfaced (visible as the new tpcb_retry_attempts counter). (#108)

[5.7.0] - 2026-07-22

Added

  • TPC-DS now runs on Picodata: stroppy run tpcds -d pico -D url=postgres://admin:...@host:1336/admin -e SCALE_FACTOR=0.1. Ships a typed sbroad schema (schema.pico.sql: charvarchar, datedatetime, a PRIMARY KEY per Tarantool space, no FK) and a picodata SQL port of the query suite (pico.sql) that rewrites sbroad-incompatible constructs (explicit JOIN ON instead of comma joins, bare date strings, etc.). 95 of the 103 queries run; the 8 that need rank/dense_rank/lag/lead (query_36, query_44, query_47, query_49, query_57, query_67, query_70, query_86) are skipped with a one-time log line, since sbroad has no window-function support. Answer-set validation stays PostgreSQL/MySQL-only. (#100)
  • The columnar insert method is now accepted by the YDB driver and redirected to the native BulkUpsert (already a struct-of-arrays, limit-free payload), logging a one-time warning, instead of being rejected. columnar is now listed for YDB in stroppy probe. MySQL and Picodata keep their existing insert methods: on MySQL columnar showed no throughput benefit over multi-row plain_bulk (measured against TPC-C/H/DS at SF 1), and Picodata's SQL has no array/JSON-expansion path. (#99)
  • TPC-DS now runs on YDB: stroppy run tpcds/tpcds -d ydb -D url=grpc://host:2136/database. Ships a typed YQL schema (schema.ydb.sql, column-store default with row-store as an option via -e YDB_STORE_MODE=row) and the 103-query suite ported to YQL (ydb.sql). The loader now feeds YDB's native bulk upsert directly from the generator. Answer-set validation and the in-process query-stream generator stay PostgreSQL/MySQL-only, so YDB runs the baked power test. (#97)
  • stroppy probe (no arguments) now also lists which insert methods each driver supports — plain_query, plain_bulk, columnar, native per database — as a DRIVERS block in the human output and a drivers key in -o json, so external tooling can discover valid defaultInsertMethod values per target without reading stroppy source. (#96)

Fixed

  • TPC-H queries now run on Picodata (stroppy run tpch/tx -d pico ...). Every one of the 22 queries failed to even parse against current picodata: sbroad rejects implicit comma joins (FROM a, b), the typed date '...' literal, interval arithmetic, extract(year FROM ...), NOT LIKE, and correlated subqueries. The picodata SQL port now uses explicit JOIN ON, bare date strings, substring(cast(... AS string) FROM 1 FOR 4) for year extraction, NOT (x LIKE ...) for negation, and JOIN-on-aggregate CTEs to decorrelate q2/q17/q20/q21 — each rewrite is answer-checked against PostgreSQL on identical data at SF=0.01 (all 22 result sets match row-for-row). The tmpfs-all compose init also raises sbroad's sql_vdbe_opcode_max and sql_motion_row_max, without which the wide multi-join aggregates (q3/q10/q21) blow past the default caps. (#105)
  • The Grafana dashboard (docs/dashboard.json) shows data again. Its panel queries were written for an older metric-naming scheme and no longer matched what stroppy exports through OpenTelemetry: the metric prefix is stroppy_ (the scenario is now a label, not baked into the name), counters carry a _total suffix, duration histograms a _milliseconds_bucket suffix, and data_received/data_sent are _bytes_total. It also filtered on a service.name label that the collector emits as job. All queries, template variables, and the default ${prefix} were updated to the current names. The load-phase "Insert rows/s" panels additionally filter event="progress" so they track the live row counter instead of the flat final-value series. (#103)
  • Pressing Ctrl-C twice during a data load now stops the run instead of leaving the process stuck and unkillable except by kill -9. Every InsertSpec drain loop (noop, mysql, postgres bulk/columnar, ydb, csv) never consulted its context at all, so a worker kept generating rows until the whole table drained before noticing the run was aborted — one long uninterruptible native call that k6 cannot preempt. The drain loops now check cancellation per row via a shared insertprogress.Canceled helper, so k6's abort (which cancels the VU context on Ctrl-C) unblocks the load promptly. (#102)
  • Wide-table bulk loads on Picodata (and the latent YDB plain_bulk path) no longer hit the bound-parameter limit. The batch-size-by-column-count clamp that kept multi-row INSERTs under 65535 bound parameters lived in the MySQL driver only; it now runs centrally in the shared sqldriver.RunBulkInsert, so every sql.DB-backed dialect is protected. Previously TPC-DS date_dim (28 cols), catalog_sales, and web_sales (34 cols) aborted with extended protocol limited to 65535 parameters and loaded zero rows. (#100)

[5.6.0] - 2026-07-01

Added

  • New PostgreSQL insert method columnar: pass one array per column and let the database expand it back to rows (unnest), so a batch binds as many parameters as there are columns instead of rows × columns. This clears PostgreSQL's 65535 bind-parameter limit that plain multi-row inserts hit on wide tables, and loads roughly 2.5–3× faster than plain_bulk — close to COPY while still being an ordinary INSERT. Select it with -D defaultInsertMethod=columnar (or "defaultInsertMethod": "columnar" in a driver config). (#93)
  • Each completed step now reports how long it took, e.g. End of 'create_schema' step (took 1.23s). (#83)
  • The create_indexes and set_logged steps now log one progress line per statement, with elapsed time, so you can see which index or table flip is slow instead of waiting on one opaque step boundary. (#83)

Changed

  • The per-iteration workload step no longer prints a Start/End of 'workload' step line on every transaction — that pair was flooding the log. The step still runs and reports its status as before; it is just silent on the console. (#83)

Fixed

  • A failed TPC-C validate_population check now makes the run exit non-zero instead of reporting success. The check detected a bad population and logged every failed assertion, but stroppy run still exited 0, so CI and matrix runs that gate on the exit code saw a false pass. The run now aborts with a dedicated exit code (108) on any population mismatch; a skipped check (--no-steps validate_population) still exits 0. (#92)

[5.5.2] - 2026-06-30

Fixed

  • Fixed-duration throughput runs (with DURATION set) no longer fail to start. The run selects k6's constant-VUs executor, which does not accept the maxDuration option the workload was still passing, so it aborted at startup with json: unknown field "maxDuration". maxDuration is now applied only to power tests, where it belongs. (#82)
  • Power tests with more than one VU (VUS>1) and the default iteration count no longer fail to start with the number of iterations can't be less than the number of VUs. The iteration count is now raised to at least VUS. (#82)
  • The TPC-DS workload can now be re-run against a database that still holds its schema from a previous run. drop_schema drops with CASCADE, so it no longer fails with cannot drop table item because other objects depend on it (SQLSTATE 2BP01). (#82)
  • The published Docker image (ghcr.io) builds again. Its build stage used Go 1.25 while the module requires Go 1.26, so image publishing had failed since v5.4.0.

[5.5.1] - 2026-06-29

Fixed

  • The default UNLOGGED fast bulk-load (PG_UNLOGGED=true) on PostgreSQL no longer fails while preparing TPC-C or TPC-B. PostgreSQL refuses to flip a table to UNLOGGED/LOGGED while it shares a foreign key with a table in the other persistence state (in either direction), so TPC-C errored on set_unlogged (could not change table … because it references logged table …, SQLSTATE 42P16) and TPC-B would hit the same on set_logged. Foreign keys are now created in a create_foreign_keys step that runs after set_logged, once every table is back to LOGGED. The unlogged fast-load path now works for all workloads; previously only PG_UNLOGGED=false succeeded. Runs that pass an explicit steps allowlist must add create_foreign_keys to it.

[5.5.0] - 2026-06-27

Added

  • All four TPC workloads (B, C, H, DS) now share one consistent lifecycle. Every workload builds its indexes in a dedicated create_indexes step after the bulk load and runs ANALYZE (analyze step) so the planner has fresh statistics — previously some workloads built indexes during schema creation, some not at all. On PostgreSQL the bulk load now runs against UNLOGGED tables and flips them back to LOGGED afterwards (set_unlogged/set_logged steps) for a much faster, WAL-free load; disable with PG_UNLOGGED=false.

  • TPC-C now defines the two spec-permitted secondary indexes (idx_customer_name, idx_order) on PostgreSQL and MySQL — they serve the mandatory by-last-name customer lookup and the customer's-latest-order path (TPC-C Clause 1.4 / §2.5.2.2 / §2.6.2.2). Previously only the YDB dialect had them.

  • Workloads accept unified run knobs: VUS, DURATION, ITER, and MAX_DURATION. Setting DURATION runs a fixed-duration throughput test (constant VUs); leaving it unset runs a power test (ITER iterations). MAX_DURATION (default 24h) lifts k6's 10-minute per-iteration cap so large loads never time out.

  • TPC-DS data can now be generated by a faithful Go port of the official dsdgen, validated byte-for-byte against the reference C generator across all 24 base tables. Generation is parallel and streaming — any table (including the multi-million-row sales/returns fact tables) can be produced in independent partitions with identical output. The tpcds workload now creates the schema and generates/loads all 24 tables itself (create_schema + load_data steps) before running the query set, mirroring the TPC-H workload; scale via SCALE_FACTOR.

  • The TPC-DS query set now runs on PostgreSQL and MySQL out of the box. The 99 queries ship as per-dialect SQL (pg.sql, mysql.sql) generated from the official query templates, replacing the old non-portable pre-baked blobs. After loading, the workload builds single-table indexes and runs ANALYZE so the heavy queries have usable plans; correlated subqueries that were O(n²) without indexes are pre-aggregated so they stay fast at scale.

  • TPC-DS query parameters can be regenerated as seed-reproducible streams of varied-but-valid values (QUERY_STREAM/QUERY_SEED), and the workload can drive several concurrent query streams (STREAMS), each running its own seeded permutation of the 99 queries — closer to the TPC-DS throughput test.

  • TPC-DS results can be validated for correctness: results are checked against the official SF1 answer set, and a cross-database diff tool (tpcds-diff) compares the same queries run on two engines (e.g. PostgreSQL vs MySQL) using a multiset comparator with numeric tolerance, so engine-specific null/tie ordering isn't flagged as a mismatch.

  • TPC-H data is now generated by a faithful port of the official dbgen, producing correct query answers (validated against the official SF1 answer set) and finalizing o_totalprice at generation time so no post-load fix-up step is needed. It is also markedly faster — lineitem generation runs several times quicker. Selectable via TPCH_GENERATOR (gotpc by default, relgen for the previous generator). (#75)

  • Release binaries are now published for arm64 (aarch64) in addition to x86-64.

Changed

  • Data loading moved out of k6's setup() and into the workload phase for every workload, so load progress now emits live metrics (k6 emits none during setup()). The measured workload is a single skippable workload step, which enables a clean two-run flow: load once with --no-steps workload, then measure against the loaded data with --steps workload (the throughput number is then uncontaminated by load time). A normal single run still loads and measures in one pass.

Fixed

  • TPC-B now declares the canonical pgbench --foreign-keys constraints (tellers/accounts/history → branches, history → tellers/accounts) on PostgreSQL and MySQL, added post-load in a create_foreign_keys step. They were missing — the schema had the bid indexes that exist to back those references but not the references themselves. (YDB/Picodata don't support foreign keys, so the step is a no-op there.)

  • Loading wide tables on MySQL no longer fails with Error 1390 (too many placeholders). Bulk-insert batches are now clamped by column count, so wide tables such as TPC-DS catalog_sales (34 columns) load correctly.

  • Generated data now loads on YDB. The bulk-upsert path coerces generated cells to the table's declared column types (ISO date strings → Timestamp, integral quantities → Double), so TPC-H loads on YDB with zero errors; previously these failed with SCHEME_ERROR.

Removed

  • The pre-baked TPC-DS query blobs (tpcds-scale-1.sqltpcds-scale-100000.sql) are gone — the workload now generates and loads its own data and ships per-dialect query files. Use stroppy run tpcds/tpcds -e SCALE_FACTOR=<n> instead of stroppy run tpcds tpcds-scale-<n>.

[5.4.0] - 2026-06-22

Added

  • stroppy probe with no script argument now lists the available preset catalog. (#73)

Fixed

  • Clearer error when a probed script has no options export. (#73)
  • Fatal log lines no longer dump a goroutine stacktrace. (#73)

[5.3.4] - 2026-06-16

Changed

  • Faster data generation and bulk inserts: TPC-H lineitem loading now runs significantly quicker and uses far less memory, with up to ~38% higher throughput on the insert path and large reductions in allocations during generation. (#72)

[5.3.3] - 2026-05-29

Changed

  • TPC-H now prepares the database once per run instead of repeating setup work.

Fixed

  • Insert throughput is now reported from live progress metrics for more accurate numbers.

[5.3.2] - 2026-05-27

Added

  • TPC-H now reports per-query timings for the workload.

Fixed

  • Worked around a YDB error that could interrupt TPC-H runs.

[5.3.1] - 2026-05-27

Fixed

  • TPC-H totals no longer apply YDB column coalescing, producing correct results.

[5.3.0] - 2026-05-27

Added

  • TPC-H can now run against YDB column-store tables.

[5.2.0] - 2026-05-26

Added

  • Live insert progress is now reported while data loads. (#71)
  • A single TPC-C test can now drive multiple tool instances at once.
  • k6 logger settings are now synced with the runner configuration.

Changed

  • Faster data generation through performance refactoring.
  • YDB data ingestion, table partitioning, and index partitioning settings improved for better load performance.
  • YDB now uses lazy transactions and parameter-based IN queries.
  • Query arguments now go through dialect conversion, with YDB list parameters still normalized to typed slices.
  • TPC-H finalize step is now dynamic.

Fixed

  • YDB now retries UNAVAILABLE errors instead of failing.

[5.1.3] - 2026-05-20

Fixed

  • TPC-H scale-factor 1 queries now pass on YDB.