Performance¶
Yara ORM is built to be a fast async Python ORM: the per-query hot path (parameter binding, row decoding, pooling) runs in compiled Rust, so steady-state overhead is far lower than pure-Python ORMs. The numbers below compare Yara ORM against Tortoise ORM, async SQLAlchemy 2.0 and Pony ORM on identical workloads.
Methodology
Each ORM gets its own table and the same workload and data. Every operation is timed
BENCH_REPEAT times and the median is reported, so warm steady-state (driver and
prepared-statement caches hot) dominates over cold-start noise. Treat the numbers as
indicative throughput, not a micro-benchmark. Full methodology and the runnable script
live in benchmarks/.
PostgreSQL¶
PostgreSQL 18, Apple Silicon, Python 3.12, N=5000, median of 5 (ms, lower is better).
| operation | yara-orm | tortoise | sqlalchemy | pony |
|---|---|---|---|---|
| bulk_insert | 11.5 | 23.1 | 67.5 | 208.4 |
| single_insert | 32.8 | 80.4 | 153.3 | 59.1 |
| fetch_all | 3.5 | 16.0 | 12.2 | 30.4 |
| count | 0.4 | 0.5 | 1.2 | 0.5 |
| filter | 2.2 | 8.5 | 20.5 | 15.4 |
| get_by_pk | 63.2 | 194.4 | 292.8 | 82.7 |
| update | 3.3 | 3.4 | 4.1 | 117.8 |
Speedup vs Yara ORM (competitor time ÷ yara-orm time; >1 means Yara ORM is faster):
| operation | tortoise | sqlalchemy | pony |
|---|---|---|---|
| bulk_insert | 2.0× | 5.9× | 18.1× |
| single_insert | 2.4× | 4.7× | 1.8× |
| fetch_all | 4.5× | 3.5× | 8.6× |
| count | 1.5× | 3.1× | 1.3× |
| filter | 3.9× | 9.5× | 7.2× |
| get_by_pk | 3.1× | 4.6× | 1.3× |
| update | 1.0× | 1.3× | 35.7× |
Yara ORM is fastest on every operation in this configuration. get_by_pk and
single_insert are latency-bound (one sequential round-trip per call) and sit near the raw
client⇄PostgreSQL round-trip floor.
SQLite¶
Python 3.12, N=5000, median of 5 (ms, lower is better).
| operation | yara-orm | tortoise | sqlalchemy | pony |
|---|---|---|---|---|
| bulk_insert | 7.5 | 13.2 | 607.7 | 47.2 |
| single_insert | 35.1 | 27.6 | 235.2 | 117.2 |
| fetch_all | 4.9 | 38.2 | 11.0 | 48.7 |
| count | 0.1 | 0.3 | 0.6 | 0.2 |
| filter | 2.6 | 19.5 | 17.6 | 24.9 |
| get_by_pk | 54.1 | 79.2 | 329.3 | 30.1 |
| update | 0.5 | 0.5 | 1.8 | 41.5 |
Yara ORM wins the throughput-bound operations decisively (bulk, fetch_all, filter). It
trails on the two latency-bound point operations: in-process Pony edges get_by_pk,
and Tortoise edges single_insert — because the SQLite backend bridges synchronous
rusqlite to async by hopping to a blocking thread per call, which costs a few microseconds
that an in-process driver avoids on sequential point queries. Real workloads rarely fire
thousands of sequential point reads, and everything throughput-shaped is far ahead.
Why it's fast¶
- Rust hot path — parameter binding and row decoding happen in compiled code; the async bridge (PyO3 + tokio) keeps the event loop free.
- Positional row decoding — for SELECTs the engine returns column values with no per-row column-name allocation and no dict; Python fills instances by index using a precomputed decode plan.
- Compiled-SQL caching — the SELECT column list, single-row INSERT and a fast-path
simple
get()are built once per model and reused, andprepare_cachedskips re-parse. - Connection pooling — deadpool keeps warm connections, so steady-state latency excludes connect cost.
Faster reads with projections
For pure projections, values_list() / values() select only the
requested columns and skip model construction (~1.7–2.2× faster than a full fetch).
Run it yourself¶
See benchmarks/README.md
for setup and tuning knobs (BENCH_N, BENCH_REPEAT, …).