Skip to content

Performance

Ormdantic's performance strategy is to keep Python ergonomic and move repeated runtime work into Rust.

Use this page when you want to run benchmarks or understand where Ormdantic spends runtime work. It is not a promise that every query is faster than every alternative. Query shape, indexes, network latency, and database behavior still matter.

What is measured

The benchmark suite covers:

  • Python-facing serialization and hydration
  • table-handle CRUD
  • query-expression paths
  • joined and select-in relationship loading
  • nested loader graphs
  • reflection and migration flows
  • Rust SQL and DML compilation
  • dialect rendering
  • schema diffing
  • hydration planning
  • select-in merging
  • native driver execution

ORM comparison report

The repository also includes a reproducible comparison report under benchmark/. It compares Ormdantic, SQLAlchemy, and SQLModel across SQLite, PostgreSQL, and MySQL. SQLite runs use temporary local files; PostgreSQL and MySQL runs use the Docker Compose services under docker/databases/. The charts show every measured case so improvements and regressions stay visible instead of being hidden behind a single aggregate number.

Ormdantic speedup over SQLAlchemy and SQLModel

Ormdantic, SQLAlchemy, and SQLModel median latency

Regenerate the report and SVGs:

uv run --group dev maturin develop --release
uv run --group benchmark python -m benchmark.run --backend sqlite --profile default

Run the pull-request profile or a targeted case:

make benchmark-ci
make benchmark-target CASE="orm insert models"

The CI profile uses 10,000 read and write rows, two warmups, seven measured rounds, and 500-row write batches. ORM order rotates between rounds. Results retain raw samples, median absolute deviation, order position, and deterministic bootstrap confidence intervals for comparisons.

Use the release native extension for report artifacts. Debug Rust builds are for development feedback and can make native write paths look artificially slow.

Run server-backed smoke profiles:

docker compose -p ormdantic-benchmark -f docker/databases/docker-compose.yaml up -d --wait postgres mysql
uv run --group benchmark python -m benchmark.run --backend postgres --profile smoke
uv run --group benchmark python -m benchmark.run --backend mysql --profile smoke

Run the materialized million-row profile:

uv run --group dev maturin develop --release
uv run --group benchmark python -m benchmark.run --backend sqlite --profile million

The billion profile requires --i-understand-this-may-be-expensive. Use --planner-scale for planner-scale artifacts. Those artifacts can discuss query shape and database estimates, but they must not be merged into materialized latency charts.

The command writes JSON, CSV, and SVG outputs under benchmark/, plus docs-ready SVG copies under docs/assets/benchmarks/.

Pull requests run base and head on the same unprivileged runner. A separate trusted workflow validates the artifact identity and ZIP structure, regenerates the Markdown, CSV, JSON, and SVG from default-branch code, and publishes the image at an immutable head-SHA path. Serialization cases with different semantics are labeled diagnostic and do not affect aggregate speedups or the regression gate.

Run Python benchmarks

Run the CodSpeed-enabled Python benchmarks locally:

uv run pytest tests/benchmarks --codspeed

The same benchmark tests also work with the local pytest-benchmark plugin:

uv run pytest tests/benchmarks

Run Rust benchmarks

Install the CodSpeed cargo subcommand once:

cargo install cargo-codspeed --locked

Run Rust benchmarks:

cargo codspeed build
cargo codspeed run

Without CodSpeed, use Criterion compatibility locally:

cargo bench --workspace

Read benchmark results

Look for regressions in:

  • query compilation
  • relationship loading
  • hydration
  • driver execution
  • migration reflection

Small benchmark wins are less important than keeping the Python API predictable and the Rust boundary stable.