[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:
Raphael Collet
2015-09-16 11:56:44 +02:00
parent 5805b87005
commit bd092e9ce7
2 changed files with 27 additions and 15 deletions
+14 -14
View File
@@ -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
View File
@@ -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