[16.0][ADD] base_field_trigger_warmup: build compute dependency trees at boot - #3687
Open
mileo wants to merge 1 commit into
Open
[16.0][ADD] base_field_trigger_warmup: build compute dependency trees at boot#3687mileo wants to merge 1 commit into
mileo wants to merge 1 commit into
Conversation
The ORM resolves the transitive closure of compute triggers lazily, the first time a field is written. On models with many interdependent stored computed fields the first request served by each worker pays for the whole closure, and every restart brings the penalty back. This module builds those trees while the registry loads, where nobody is waiting. Behaviour is unchanged: it only decides when work the ORM would do anyway is done. The scope defaults to every model and can be narrowed with the base_field_trigger_warmup.models system parameter; ODOO_FIELD_TRIGGER_WARMUP=0 disables it without uninstalling. Registry.get_field_trigger_tree is private ORM API, so it is called defensively: a version that renames or drops it degrades to a no-op instead of breaking the registry load. Extracted from a production deployment whose invoice line model carries around 180 interdependent stored fields, where the first write on a fresh worker cost about five times what every following write cost.
mileo
force-pushed
the
16.0-base-field-trigger-warmup
branch
from
July 29, 2026 04:14
2bf0728 to
68f074c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
The ORM resolves the transitive closure of compute triggers lazily: the first time
a field is written, Odoo walks its
@api.dependsgraph and caches the resultingtrigger tree in the registry (
Registry.get_field_trigger_tree).On models with many interdependent stored computed fields, that first write pays
for the whole closure. The practical effect is that the first request served by
each worker is much slower than the ones that follow, and every restart, deploy or
autoscaling event brings the penalty back.
Where this was found: a production deployment of a localization whose invoice line
model carries around 180 interdependent stored fields. Writing the first invoice on a
fresh worker took about 5N seconds, where every following invoice took N.
Profiling attributed roughly 90% of that difference to the trigger tree construction,
not to the business computation itself.
What the module does
It builds those trees while the registry loads, where nobody is waiting. Behaviour
is unchanged: it only decides when work the ORM would do anyway is done. There is
no user interface, no data, and the only dependency is
base.It logs what it did, so the trade can be evaluated on any database:
After that, the first write costs the same as the following ones.
Design decisions worth reviewing
Scope is configurable. Warming up every model costs a few seconds of boot per
worker, which is usually a good trade but not always. The
base_field_trigger_warmup.modelssystem parameter takes a comma separated list ofmodels;
*(the default) means everything. Unknown model names are ignored with awarning rather than raising.
It can be disabled without uninstalling.
ODOO_FIELD_TRIGGER_WARMUP=0skips it,which is useful on development machines and in CI where boot time matters more than
the first request. It is always skipped while tests run.
The private ORM API is called defensively.
Registry.get_field_trigger_treeisnot public API. It is fetched with
getattrand, if a future version renames ordrops it, the module logs the fact and does nothing instead of breaking the registry
load. A failure on an individual field is logged at debug level and skipped, for the
same reason: a warmup must never be able to break a boot.
Honest scope
This module hides the symptom of a wide compute graph, it does not reduce the graph.
When the first request of a worker is slow enough to need it, the underlying model
is probably worth looking at too. That is written in the ROADMAP so nobody installs
it expecting a general speed-up: it moves a cost, it does not remove one. Warm
requests are exactly as fast as before.
Tests
9 tests, run against a real Odoo 16 database: scope defaults and narrowing, unknown
models ignored, a failing field not breaking the run, the missing-API path being a
no-op, and the enable/disable conditions.