Skip to content

Odoo @ormcache Decorator and Caching: Complete Guide

DeployMonkey Team · March 23, 2026 10 min read

Why Cache in Odoo?

Some computations are expensive and produce the same result for the same inputs — configuration lookups, permission checks, formatting rules. Odoo provides the @ormcache decorator family to cache method results at the registry level (shared across all requests in the same worker).

The @ormcache Decorator

The basic @ormcache caches method results based on the method arguments:

from odoo.tools import ormcache
from odoo import models, api

class IrConfigParameter(models.Model):
    _inherit = 'ir.config_parameter'

    @api.model
    @ormcache('key')
    def get_param(self, key, default=False):
        params = self.search_read(
            [('key', '=', key)],
            fields=['value'],
            limit=1
        )
        return params[0]['value'] if params else default

Key points:

  • The decorator argument 'key' is the parameter name used as cache key
  • The cache persists across requests within the same worker process
  • The cache is per-database (Odoo includes the database name automatically)
  • Results are stored in a dictionary keyed by the argument values

@ormcache_context

When the result depends on context values (like language or company), use @ormcache_context:

from odoo.tools import ormcache_context

class IrTranslation(models.Model):
    _inherit = 'ir.translation'

    @api.model
    @ormcache_context('model_name', keys=('lang',))
    def get_field_string(self, model_name):
        # Result varies by language
        return self._get_field_translations(model_name)

The keys parameter specifies which context keys are included in the cache key. Different languages get different cache entries.

@ormcache_multi

For methods that accept multiple IDs and return results per ID:

from odoo.tools import ormcache_multi

class ResCompany(models.Model):
    _inherit = 'res.company'

    @ormcache_multi('self', 'ids')
    def _get_company_settings(self, ids):
        result = {}
        for company in self.browse(ids):
            result[company.id] = {
                'currency': company.currency_id.name,
                'country': company.country_id.code,
            }
        return result

This caches results per individual ID. If you request IDs [1, 2, 3] and ID 1 is cached, it only computes for [2, 3].

Cache Invalidation

The most critical aspect of caching. Odoo provides explicit cache clearing:

class IrConfigParameter(models.Model):
    _inherit = 'ir.config_parameter'

    def write(self, vals):
        res = super().write(vals)
        # Clear the cache after writing
        self.env.registry.clear_cache()
        return res

    def create(self, vals):
        res = super().create(vals)
        self.env.registry.clear_cache()
        return res

    def unlink(self):
        res = super().unlink()
        self.env.registry.clear_cache()
        return res

self.env.registry.clear_cache() clears ALL ormcache entries for the current database. This is a nuclear option — use it when you cannot selectively invalidate.

Selective Invalidation

For more targeted invalidation, you can clear specific caches:

# Clear cache for a specific method
type(self).get_param.clear_cache(self)

# Or clear the entire registry cache
self.env.registry.clear_cache()

When to Use @ormcache

Good candidates for caching:

  • Configuration parameters: ir.config_parameter values rarely change
  • Permission lookups: Group membership checks
  • Format strings: Date/number formatting per locale
  • Static reference data: Country codes, currency symbols
  • Computed constants: Values derived from settings that change infrequently

Poor candidates:

  • Transactional data: Order totals, stock levels — change frequently
  • User-specific data: Unless you include the user ID in the cache key
  • Large return values: Caching large objects wastes memory
  • Methods with side effects: Caching skips the method body on cache hit

Cache Scope and Lifetime

Cache TypeScopeLifetimeCleared By
@ormcacheRegistry (per worker)Until clear or restartclear_cache(), worker restart
ORM prefetchEnvironment (per request)Single requestinvalidate_all(), end of request
Python lru_cacheProcessUntil process restartcache_clear()

Important: @ormcache is per-worker. In a multi-worker setup, clearing cache on one worker does not clear it on others. Changes to ir.config_parameter signal all workers to clear via the bus, but custom caches may not have this mechanism.

Multi-Worker Cache Consistency

Odoo uses a registry signaling mechanism to propagate cache clears across workers. When you call registry.clear_cache(), it sets a signal that other workers detect on their next request. However, there can be a brief window where different workers have different cache states.

For critical consistency, consider using ir.config_parameter (which has built-in cross-worker signaling) instead of building your own caching layer.

Debugging Cache Issues

# Check cache statistics
cache = type(self).get_param.cache
print(f'Cache hits: {cache.hits}, misses: {cache.misses}')
print(f'Cache size: {len(cache.data)}')

# Force cache miss for testing
type(self).get_param.clear_cache(self)
result = self.get_param('my.key')  # guaranteed fresh

Common Pitfalls

  • Forgetting invalidation: Cached values become stale if you do not clear the cache when underlying data changes
  • Caching mutable objects: If you cache a list or dict, callers can modify the cached object. Return copies or use frozen types
  • Cache keys with recordsets: Never use recordsets as cache keys — they are mutable and unhashable. Use self.id or tuple(self.ids)
  • Memory leaks: Unbounded caches grow forever. Odoo ormcache has size limits, but custom caches may not
  • Testing: Tests may see stale cache from previous test methods. Clear caches in setUp