Odoo Performance · intermediate · 11 min read
ORM Performance Best Practices
Odoo ORM patterns that scale: batch operations, prefetch, domain design, computed fields, and avoiding N+1 on sale, stock, and accounting paths.
Last reviewed 2026-07-30 · Estimated effort: 2–6 weeks typical implementation slice
Table of contents
Odoo's ORM rewards batch-oriented code: one search_read with explicit fields, prefetch-friendly browse chains, and grouped writes beat thousands of single-record round trips.
N+1 patterns hide in computed fields, onchange methods, constraint checks, and integration loops that call browse() per line. They explode under real catalog and order volume.
ORM choices drive database load. Domains on unindexed fields, non-stored computes recalculated on every read, and sudo() searches across huge tables are fixable in module design — see Development for implementation standards.
Custom modules often work fine on demo data then collapse when a pick list touches five hundred moves or a connector imports ten thousand order lines. The ORM is rarely the villain — usage patterns are.
This article collects Odoo-specific ORM practices for production modules. Pair with database optimization when domains need indexes and with API optimization when RPC payloads repeat the same anti-patterns.
Use search_read or read with a minimal field list instead of browsing large recordsets and accessing fields lazily. For list views and exports, fetch only columns you render or compute downstream.
When iterating records, reuse prefetched recordsets: browse(ids) once, then map related data with read_group or a single related search rather than per-row search calls.
- ▸search_read with fields=[] for integrations and reports
- ▸Avoid len(recordset) triggers that fetch entire tables
- ▸Use read_group for aggregates instead of Python loops
- ▸Split giant exports into chunked jobs with queue workers
create multi-record dicts in one call where business rules allow. For updates, group by identical vals and use write on recordsets rather than looping single-record writes that retrigger full constraint stacks.
Heavy side effects belong in deferred jobs — not in create overrides on high-volume models like stock.move or account.move.line.
Prefer stored computed fields with explicit @api.depends when values are read often and change infrequently. Non-stored computes on list views force recalculation for every row on every load.
Inverse methods and related fields that chain across many2many relations can trigger cascade recomputes. Scope depends carefully and test with production-sized recordsets on staging.
Every frequent domain filter should map to a indexed column or a stored related field. ILIKE scans on name fields without trigram support do not scale for autocomplete on large partners or products.
Avoid OR domains across unrelated columns when a single stored flag or category field can narrow the set. The database optimization article covers PostgreSQL indexes aligned to Odoo models.
Flag loops containing search, browse, or env.ref inside integrations, picking validation, and invoice posting. Require proof of query count or profiling output for changes touching sale.order, stock.picking, or account.move.
Customizations requested through functional specs should include expected volume — orders per hour, lines per pick — so ORM design is sized intentionally. Link Customization when business rules demand expensive checks.
- ✓Set a module review gate for ORM patterns on high-volume models.
- ✓Require profiling evidence for any new compute on list-visible fields.
- ✓Use queue jobs for bulk creates and updates from integrations.
- ✓Store frequently filtered values instead of computing them on read.
- ✓Document expected transaction volume in customization specs.
- ✓Train integrators to use search_read with field lists in connectors.
- !Calling search([]) or browse loop per order line in connectors.
- !Non-stored compute on fields shown in default list views.
- !Using name_search with leading wildcard on million-row tables.
- !Triggering full reconciliation logic on every write via broad @api.constrains.
- !Mixing sudo() with unbounded domains in cron jobs.
- !Assuming prefetch covers fields never loaded in the initial read.
ORM performance is a design discipline: batch reads and writes, stored computes where lists demand them, and domains that PostgreSQL can index.
Next, align database indexes and vacuum health with the domains and models your modules hit hardest.
Model
Python class
Fields
Stored / computed
SQL table
Persistence
Cache / prefetch
Performance
- modelfields
- fieldstable
- modelcache(prefetch)
- Owners named
- UAT scripts signed
- Rollback documented
- Hypercare roster ready
Frequently asked questions
Is browse() always slower than search_read()?
Not always — browse is efficient for known ids with prefetch. Problems appear when code accesses unloaded fields in loops or mixes browse with repeated searches. Choose based on access pattern and measure.
Should we always store computed fields?
Store when read-heavy and update-light. Non-stored is fine for rare form-only fields. List views and API payloads usually need stored or related fields with indexes.
How do we find N+1 in existing modules?
Profile with query logging on staging using production-like data. Watch for query count growing linearly with line count on the same action.
Where does Development guidance overlap?
Development covers module structure, testing, and upgrade-safe inheritance. This article focuses on runtime cost of ORM choices in production workloads.
Related articles
Database Optimization for Odoo
PostgreSQL tuning for Odoo workloads: indexes on ORM domains, bloat control, connection limits, lock avoidance, and post-migration validation — not generic DBA theory.
View →Diagnosing Slow Odoo Systems
A step-by-step audit for slow Odoo: reproduce symptoms, profile requests, inspect SQL, check workers and queues, and isolate custom module cost.
View →Common Odoo Performance Mistakes
Frequent causes of slow Odoo in production: hardware-first fixes, ORM anti-patterns, cron pile-ups, unindexed custom fields, and integration designs that ignore backpressure.
View →API Performance Optimization
Speed and stabilize Odoo XML-RPC, JSON-RPC, and REST endpoints: payload design, authentication cost, rate limits, batch endpoints, and integration-friendly ORM patterns.
View →Related services
Dedicated Odoo Developer
Reserved capacity for profiling, module fixes, and performance backlog.
View →Odoo Performance Optimization
Diagnosis and remediation for slow production Odoo — ORM, workers, and operations.
View →Odoo Support
Monitoring, incident response, and AMC for live performance-sensitive systems.
View →Reviewing custom modules for ORM cost?
Share your heaviest models and integration paths — we will prioritize review and profiling on the workflows that matter most.
