Skip to content

Performance Benchmark

Fullfinity’s ORM was benchmarked against Odoo 18 and five popular Python ORMs across 11 standard operations covering inserts, reads, updates, and deletes.

ORM Benchmark

OperationFullfinityOdoo 18DjangopeeweeSA asyncTortoisePiccolo
Insert: Single2,1641,9227766515843,4553,240
Insert: Batch12,1339,5952,0692,4181,15414,03312,138
Insert: Bulk10,69310,2255,2245,0091,26418,80718,060
Filter: Large188,601133,46157,45162,00932,318143,810147,031
Filter: Small17,99116,76319,49118,0667,53260,69945,805
Get256,64917,1642,1832,5901,8946,4206,854
Filter: dict275,327134,45364,39481,65233,798209,380328,124
Filter: tuple257,209159,14855,08066,82737,933197,175332,310
Update: Whole18,9837,4012,8963,5551,74317,40713,491
Update: Partial28,55211,2723,2814,8121,52618,25516,180
Delete4,4785943,3865,7011,86921,01018,968
Geometric Mean32,45014,8387,1088,2583,81729,38129,514

All values are ops/sec or rows/sec — higher is better.

2.19x faster than Odoo 18 overall (geometric mean). Competitive with purpose-built async ORMs like Tortoise and Piccolo — while carrying the full weight of an ERP framework: system fields, access control, computed field cascades, identity map, and audit trail.

Fullfinity beats Odoo on every write operation:

  • Insert: Single — 2,164 vs 1,922 (1.13x)
  • Insert: Batch — 12,133 vs 9,595 (1.26x)
  • Insert: Bulk — 10,693 vs 10,225 (1.05x)
  • Update: Whole — 18,983 vs 7,401 (2.56x)
  • Update: Partial — 28,552 vs 11,272 (2.53x)
  • Delete — 4,478 vs 594 (7.54x)

Updates use deferred writes with batch flush — field changes are buffered in-memory and written to the database in a single batched SQL operation at flush time. Both Fullfinity and Odoo use this approach; the timing includes the flush.

  • Get (PK lookup) — 256,649 vs 17,164 (14.9x). The identity map returns cached instances for previously-fetched records without a database round-trip. In ERP workloads, the same records are accessed repeatedly during compute cascades, validation, and relational hydration — the identity map turns all of these into near-zero-cost lookups.
  • Filter: Large — 188,601 vs 133,461 (1.41x). Querying the same dataset repeatedly benefits from the identity map fast-path — cached instances are returned directly without per-field processing overhead.
  • Filter: dict — 275,327 vs 134,453 (2.05x). Raw dictionary results via .values(), no instance creation or caching — pure query-to-dict throughput.
  • Filter: tuple — 257,209 vs 159,148 (1.62x). Raw tuple results via .values_list(), same story.

The performance comes from architectural decisions, not tricks:

Every request maintains a single instance per record in the identity map. When the same record is accessed multiple times (common in ERP — compute cascades, validation, relational hydration), subsequent lookups are a Python dict access with no database round-trip.

Scalar field updates are buffered in-memory. Multiple updates to the same record merge into a single UPDATE statement. Multiple records with the same changed fields are batched via executemany. The flush happens automatically before reads on dirty models and at transaction commit.

Fullfinity uses asyncpg with the PostgreSQL binary protocol, providing 3-5x lower overhead per query compared to psycopg2 (used by Odoo and Django).

The _from_db_row() fast-path constructor bypasses __init__ validation, UUID generation, and datetime defaults for database rows. Combined with the identity map fast-path that skips all per-field processing for already-cached instances, the per-row overhead for bulk reads is minimal.

  • Model: BenchmarkJournal — 3 user fields (timestamp, level, text) + 4 system fields (id, identifier, created_date, updated_date, active). Comparable to the 4-field model used in the tortoise-orm-benchmark suite.
  • Database: PostgreSQL 17, local connection
  • Driver: asyncpg (Fullfinity, Tortoise, Piccolo, SA async), psycopg2 (Odoo, Django, peewee)
  • Seed data: 1,000 rows pre-populated for read benchmarks
  • Odoo 18: Tested locally on the same machine with the same methodology (deferred writes + cr.flush() for updates)
  • Reference numbers: Other ORM numbers from the tortoise-orm-benchmark suite (PostgreSQL 17, Docker, matching model shape)
Terminal window
./fullfinity-server -c config.yaml -db <dbname> test --module benchmark --verbose