[IMP] core: make compute order deterministic

CRM install query count can vary from one execution to another, leading
to difficulties when analysing performances evolution.
The main reason for this is that some compute methods were called in
different order. Even if compute order shouldn't have any effect on the
final result, making it well defined will help finding other causes of
non-determinism.

The initial observation was that sorting Environment.fields_to_compute
leads to a fixed number of query when installing crm.

The main cause of non-determinisim is the usage of `set` impacting
Field.compute_value and BaseModel._modified_triggers.
Transforming all these `set` to `OrderedSet` solves the problem.

The query count is now deterministic when installing a database from
scratch, but not when updating a database with -i crm.

OrderedSet is also slightly optimised by using a dict instead of an

closes odoo/odoo#68692

Ordereddict: dict order is deterministic since python3.6
Signed-off-by: Xavier Dollé (xdo) <xdo@odoo.com>
This commit is contained in:
Xavier-Do
2021-04-02 15:29:12 +00:00
parent 69c512542b
commit 4044e46861
3 changed files with 24 additions and 7 deletions
+4 -4
View File
@@ -139,7 +139,7 @@ def trigger_tree_merge(node1, node2):
""" Merge two trigger trees. """
for key, val in node2.items():
if key is None:
node1.setdefault(None, set())
node1.setdefault(None, OrderedSet())
node1[None].update(val)
else:
node1.setdefault(key, {})
@@ -3924,9 +3924,9 @@ Fields:
quote = '"{}"'.format
# insert rows
ids = [] # ids of created records
other_fields = set() # non-column fields
translated_fields = set() # translated fields
ids = [] # ids of created records
other_fields = OrderedSet() # non-column fields
translated_fields = OrderedSet() # translated fields
for data in data_list:
# determine column values
+2 -2
View File
@@ -312,7 +312,7 @@ class Registry(Mapping):
# dependencies of custom fields may not exist; ignore that case
exceptions = (Exception,) if field.base_field.manual else ()
with ignore(*exceptions):
dependencies[field] = set(field.resolve_depends(self))
dependencies[field] = OrderedSet(field.resolve_depends(self))
# determine transitive dependencies
def transitive_dependencies(field, seen=[]):
@@ -339,7 +339,7 @@ class Registry(Mapping):
tree = triggers
for label in reversed(path):
tree = tree.setdefault(label, {})
tree.setdefault(None, set()).add(field)
tree.setdefault(None, OrderedSet()).add(field)
return triggers
+18 -1
View File
@@ -11,6 +11,7 @@ import datetime
import hmac as hmac_lib
import hashlib
import io
import itertools
import os
import pickle as pickle_
import re
@@ -1013,19 +1014,35 @@ class StackMap(MutableMapping):
class OrderedSet(MutableSet):
""" A set collection that remembers the elements first insertion order. """
__slots__ = ['_map']
def __init__(self, elems=()):
self._map = OrderedDict((elem, None) for elem in elems)
self._map = dict.fromkeys(elems)
def __contains__(self, elem):
return elem in self._map
def __iter__(self):
return iter(self._map)
def __len__(self):
return len(self._map)
def add(self, elem):
self._map[elem] = None
def discard(self, elem):
self._map.pop(elem, None)
def update(self, elems):
self._map.update(zip(elems, itertools.repeat(None)))
def difference_update(self, elems):
for elem in elems:
self.discard(elem)
def __repr__(self):
return f'{type(self).__name__}({list(self)!r})'
class LastOrderedSet(OrderedSet):
""" A set collection that remembers the elements last insertion order. """