[IMP] models: optimize recomputations by saving values in record batches
The recomputation of a field `name` on `recs` is done roughly like this:
for rec in recs:
rec._write({name: rec[name]})
The effect of this patch is to group records by value `rec[name]`, and to
update records in batches like:
recs.write({name: val}) # rec[name] == val for all rec in recs
As a consequence, less queries are issued to the database for updating records.
This improvement is very effective in the case of migrations!
This commit is contained in:
+14
-14
@@ -5762,20 +5762,20 @@ class BaseModel(object):
|
||||
"""
|
||||
while self.env.has_todo():
|
||||
field, recs = self.env.get_todo()
|
||||
# evaluate the fields to recompute, and save them to database
|
||||
computed = self.env[field.model_name]._field_computed[field]
|
||||
names = [f.name for f in computed if f.store and self.env.field_todo(f)]
|
||||
for rec in recs:
|
||||
try:
|
||||
values = rec._convert_to_write({
|
||||
name: rec[name] for name in names
|
||||
})
|
||||
with rec.env.norecompute():
|
||||
rec._write(values)
|
||||
except MissingError:
|
||||
pass
|
||||
# mark the computed fields as done
|
||||
map(recs._recompute_done, computed)
|
||||
# determine the fields to recompute
|
||||
fs = self.env[field.model_name]._field_computed[field]
|
||||
ns = [f.name for f in fs if f.store]
|
||||
# evaluate fields, and group record ids by update
|
||||
updates = defaultdict(set)
|
||||
for rec in recs.exists():
|
||||
vals = rec._convert_to_write({n: rec[n] for n in ns})
|
||||
updates[frozendict(vals)].add(rec.id)
|
||||
# update records in batch when possible
|
||||
with recs.env.norecompute():
|
||||
for vals, ids in updates.iteritems():
|
||||
recs.browse(ids)._write(dict(vals))
|
||||
# mark computed fields as done
|
||||
map(recs._recompute_done, fs)
|
||||
|
||||
#
|
||||
# Generic onchange method
|
||||
|
||||
+13
-1
@@ -18,7 +18,7 @@ import threading
|
||||
import time
|
||||
import werkzeug.utils
|
||||
import zipfile
|
||||
from collections import defaultdict, Mapping, OrderedDict
|
||||
from collections import defaultdict, Hashable, Iterable, Mapping, OrderedDict
|
||||
from itertools import islice, izip, groupby
|
||||
from lxml import etree
|
||||
from which import which
|
||||
@@ -1047,6 +1047,16 @@ def dumpstacks(sig=None, frame=None):
|
||||
|
||||
_logger.info("\n".join(code))
|
||||
|
||||
def freehash(arg):
|
||||
if isinstance(arg, Mapping):
|
||||
return hash(frozendict(arg))
|
||||
elif isinstance(arg, Iterable):
|
||||
return hash(frozenset(arg))
|
||||
elif isinstance(arg, Hashable):
|
||||
return hash(arg)
|
||||
else:
|
||||
return id(arg)
|
||||
|
||||
class frozendict(dict):
|
||||
""" An implementation of an immutable dictionary. """
|
||||
def __delitem__(self, key):
|
||||
@@ -1063,6 +1073,8 @@ class frozendict(dict):
|
||||
raise NotImplementedError("'setdefault' not supported on frozendict")
|
||||
def update(self, *args, **kwargs):
|
||||
raise NotImplementedError("'update' not supported on frozendict")
|
||||
def __hash__(self):
|
||||
return hash(frozenset((key, freehash(val)) for key, val in self.iteritems()))
|
||||
|
||||
class Collector(Mapping):
|
||||
""" A mapping from keys to lists. This is essentially a space optimization
|
||||
|
||||
Reference in New Issue
Block a user