This is a maintainer guide for the cargo xtask writer-bench harness. For
curated user-facing results and the headline claim, read
Performance.
The harness is for SQL Server write-path comparisons only. It does not benchmark reads, exports, object storage, or general database query performance.
Benchmark results are local to the machine, container runtime, SQL Server image, network path, row count, batch size, and scenario used for the run. Treat local output as evidence for that run only, not as a portable performance claim.
Keep raw comparison output in target/ or another ignored path. Promote only
reviewed, reproducible results into the maintained performance explanation.
- Rust toolchain for this workspace.
- A container runtime such as
podmanordocker, or an existing SQL Server connection string. - For ODBC-backed comparisons, the managed runner image contains unixODBC, Microsoft ODBC Driver 18 for SQL Server, and Rust. The normal xtask path does not require the host to have unixODBC development libraries installed.
The examples below use podman. Replace it with docker when needed.
Run cargo xtask writer-bench --help to see the authoritative scenario list.
Current scenarios are:
narrow_numeric: primitive numeric throughput.extended_primitive: small integer and real primitive throughput.mixed_nullable: nullable primitives and short strings.fixed_size_binary: fixed-size binary values planned asbinary(n).wide_mixed: ingestion-style ids, event time, categories, text, and binary payloads.decimal_temporal: finance-style decimals, dates, and timestamps.fixed_width_128: one hundred twenty-eight small fixed-width primitive columns for encoder-path isolation.decimal_temporal_128: one hundred twenty-eight decimal and temporal columns for encoder-path isolation.string_heavy: large variable text and binary payload rows.wide_sparse: thirty-two mixed columns with sparse nullable values.tpch_lineitem_like: TPC-H lineitem-inspired transport workload without external dbgen.
Rows per second is only directly comparable for the same scenario and data volume. A narrow numeric row and a string-heavy row have very different payload sizes and conversion costs.
Use baseline to benchmark this crate's current TokenRow SQL Server writer:
cargo xtask writer-bench baseline \
--container-runtime podman \
--scenario narrow_numeric \
--rows 100000 \
--batch-size 8192 \
--repeat 3The harness starts a SQL Server container unless --connection-string is
provided. It creates a benchmark database and table, writes the generated Arrow
batches, validates the number of inserted rows, and cleans up managed resources
after the run.
Use arrow-odbc to benchmark the optional arrow-odbc SQL Server write path:
cargo xtask writer-bench arrow-odbc \
--container-runtime podman \
--scenario narrow_numeric \
--rows 10000 \
--batch-size 8192 \
--repeat 3This path builds and runs a managed runner image. The runner reads an Arrow IPC
dataset and writes it through arrow-odbc. Use --keep-runner-image only when
you want to keep that image for repeated local experiments.
Use odbc-bcp through compare to benchmark SQL Server's native ODBC bulk-copy
extension:
cargo xtask writer-bench compare \
--container-runtime podman \
--backends baseline,direct-raw,arrow-odbc,odbc-bcp \
--scenario narrow_numeric \
--rows 10000 \
--batch-size 8192 \
--repeat 3This path uses the Microsoft ODBC Driver 18 SQL Server-specific BCP extension.
It is not the same as arrow-odbc, which writes through generic ODBC parameter
arrays. The BCP runner is contained under xtask and is a benchmark-only
reference point for native SQL Server bulk copy. It does not add an ODBC
dependency or public API to the main crate.
Current odbc-bcp support covers every shared benchmark scenario. The runner
uses the shared IPC file and binds supported Arrow columns into SQL Server BCP
program variables. Arrow Utf8 values are encoded as UTF-16LE for
nvarchar(max) targets; decimal, date, and timestamp values are formatted as
text for SQL Server conversion; binary values are sent as varbinary(max).
Use compare for the fairest backend comparison. The command generates one
Arrow IPC dataset and has each selected backend write that same file:
cargo xtask writer-bench compare \
--container-runtime podman \
--backends baseline,arrow-odbc,odbc-bcp \
--scenario narrow_numeric \
--rows 10000 \
--batch-size 8192 \
--repeat 3The shared IPC file is the fairness boundary. It keeps data generation outside the backend timing and ensures every backend sees the same rows, null pattern, string values, binary values, and temporal values.
Use direct-raw in compare runs to measure this crate's raw TDS encoder through
the normal WriteBackend::DirectRawBulk writer path. Current direct benchmark
coverage includes primitive rows, variable-width text and binary rows, UInt64
policy rows, decimal and temporal rows, fixed-size binary rows, wide fixed-width
rows, wide decimal and temporal rows, and mixed direct-supported schemas.
For stable comparisons, prefer runs long enough that setup noise and timer resolution do not dominate the result. Very short runs are useful as smoke tests, but they are not enough for performance decisions.
Use ipc when you want to inspect or reuse a generated dataset:
cargo xtask writer-bench ipc \
--path target/bench.arrow \
--scenario mixed_nullable \
--rows 100000 \
--batch-size 8192Generated benchmark IPC files should stay under target/ or another ignored
local path.
Pass --connection-string to use an existing SQL Server instead of a managed
container:
cargo xtask writer-bench baseline \
--connection-string 'server=tcp:127.0.0.1,1433;user id=sa;password=REDACTED;TrustServerCertificate=true' \
--database arrow_sql_server_benchmark \
--scenario mixed_nullable \
--rows 100000 \
--batch-size 8192Avoid sharing command output that contains secrets. Prefer temporary benchmark credentials and a disposable database.
Human output includes:
- backend name.
- scenario name.
- rows per repeat.
- batch size.
- repeat count.
- rows written.
- batches written.
- write rows per second.
- validated rows.
- setup, write, finish, validate, cleanup, and total timings.
For backend comparison, focus first on write time and validated rows. Setup time includes container startup, image build, database creation, table creation, and other harness work. Cleanup time can include container and image removal.
Managed containers, networks, runner containers, generated IPC files, and the runner image are cleaned up by default. The flags below intentionally keep local resources:
--keep-container--keep-runner-image
If a process is interrupted, inspect and remove leftover local resources with your container runtime, for example:
podman ps -a
podman rm -f <container>
podman network ls
podman network rm <network>
podman images
podman rmi <image>Generated IPC files use target/arrow-sql-server-writer-bench/ during managed
compare runs.
The production writer backends are the baseline TokenRow path and the direct raw
TDS path for currently supported direct-encoder mappings. arrow-odbc and
odbc-bcp are benchmark references only. odbc-bcp is SQL Server-specific and
exists to measure Microsoft's native BCP extension against this crate's writer
paths.
The direct-raw compare backend is enabled only for scenarios whose schemas are
fully supported by the current direct encoder. Unsupported scenarios fail before
benchmark execution with a validation message instead of silently falling back.