summaryrefslogtreecommitdiff
path: root/odoo/api.py
diff options
context:
space:
mode:
authorRaphael Collet <rco@odoo.com>2017-09-05 14:36:20 +0200
committerRaphael Collet <rco@odoo.com>2017-09-08 13:21:00 +0200
commitd7190a3fd043639ec21e91ddb30481b2989842c5 (patch)
tree9bda71f7db4dc71d461ab42ac7ba364a4c02ba01 /odoo/api.py
parent32a58c0db3c0d666e704a4c8af33b0be0a9608e4 (diff)
[IMP] api: reindex the record cache to improve cache hit
The record cache is indexed by `(field, record_id, key)`, where `key` depends on the environment. The key is either `(cr, uid, context)` or `(cr, uid)`, depending on whether the field's value is context-dependent. As most fields are not context-dependent, this should avoid some cache prefetching when swiching context.
Diffstat (limited to 'odoo/api.py')
-rw-r--r--odoo/api.py67
1 files changed, 41 insertions, 26 deletions
diff --git a/odoo/api.py b/odoo/api.py
index 89f58b613b7..506c344da30 100644
--- a/odoo/api.py
+++ b/odoo/api.py
@@ -740,7 +740,7 @@ class Environment(Mapping):
self = object.__new__(cls)
self.cr, self.uid, self.context = self.args = (cr, uid, frozendict(context))
self.registry = Registry(cr.dbname)
- self.cache = Cache()
+ self.cache = envs.cache
self._protected = defaultdict(frozenset) # {field: ids, ...}
self.dirty = defaultdict(set) # {record: set(field_name), ...}
self.all = envs
@@ -929,6 +929,7 @@ class Environments(object):
""" A common object for all environments in a request. """
def __init__(self):
self.envs = WeakSet() # weak set of environments
+ self.cache = Cache() # cache for all records
self.todo = {} # recomputations {field: [records]}
self.mode = False # flag for draft/onchange
self.recompute = True
@@ -945,38 +946,46 @@ class Environments(object):
class Cache(object):
""" Implementation of the cache of records. """
def __init__(self):
- self._data = defaultdict(dict) # {field: {id: value}}
+ # {field: {record_id: {key: value}}}
+ self._data = defaultdict(lambda: defaultdict(dict))
def contains(self, record, field):
""" Return whether ``record`` has a value for ``field``. """
- return record.id in self._data[field]
+ key = field.cache_key(record)
+ return key in self._data[field].get(record.id, ())
def get(self, record, field):
""" Return the value of ``field`` for ``record``. """
- value = self._data[field][record.id]
+ key = field.cache_key(record)
+ value = self._data[field][record.id][key]
return value.get() if isinstance(value, SpecialValue) else value
def set(self, record, field, value):
""" Set the value of ``field`` for ``record``. """
- self._data[field][record.id] = value
+ key = field.cache_key(record)
+ self._data[field][record.id][key] = value
def remove(self, record, field):
""" Remove the value of ``field`` for ``record``. """
- del self._data[field][record.id]
+ key = field.cache_key(record)
+ del self._data[field][record.id][key]
def contains_value(self, record, field):
""" Return whether ``record`` has a regular value for ``field``. """
- value = self._data[field].get(record.id, SpecialValue(None))
+ key = field.cache_key(record)
+ value = self._data[field][record.id].get(key, SpecialValue(None))
return not isinstance(value, SpecialValue)
def get_value(self, record, field, default=None):
""" Return the regular value of ``field`` for ``record``. """
- value = self._data[field].get(record.id, SpecialValue(None))
+ key = field.cache_key(record)
+ value = self._data[field][record.id].get(key, SpecialValue(None))
return default if isinstance(value, SpecialValue) else value
def set_special(self, record, field, getter):
""" Set the value of ``field`` for ``record`` to return ``getter()``. """
- self._data[field][record.id] = SpecialValue(getter)
+ key = field.cache_key(record)
+ self._data[field][record.id][key] = SpecialValue(getter)
def set_failed(self, records, fields, exception):
""" Mark ``fields`` on ``records`` with the given exception. """
@@ -989,38 +998,44 @@ class Cache(object):
def get_fields(self, record):
""" Return the fields with a value for ``record``. """
for name, field in record._fields.items():
- if name != 'id' and record.id in self._data[field]:
+ key = field.cache_key(record)
+ if name != 'id' and key in self._data[field].get(record.id, ()):
yield field
def get_records(self, model, field):
""" Return the records of ``model`` that have a value for ``field``. """
- return model.browse(self._data[field])
+ browse = model.browse
+ ids = [record_id
+ for record_id, field_record_cache in self._data[field].items()
+ if field.cache_key(browse(record_id)) in field_record_cache]
+ return browse(ids)
def invalidate(self, spec=None):
""" Invalidate the cache, partially or totally depending on ``spec``. """
if spec is None:
- for env in list(Environment.envs):
- env.cache._data.clear()
+ self._data.clear()
elif spec:
- for env in list(Environment.envs):
- data = env.cache._data
- for field, ids in spec:
- if ids is None:
- if field in data:
- del data[field]
- else:
- field_cache = data[field]
- for id in ids:
- field_cache.pop(id, None)
+ data = self._data
+ for field, ids in spec:
+ if ids is None:
+ data.pop(field, None)
+ else:
+ field_cache = data[field]
+ for id in ids:
+ field_cache.pop(id, None)
def check(self, env):
""" Check the consistency of the cache for the given environment. """
# make a full copy of the cache, and invalidate it
dump = defaultdict(dict)
- for field, field_cache in env.cache._data.items():
- for record_id, value in field_cache.items():
+ for field, field_cache in self._data.items():
+ browse = env[field.model_name].browse
+ for record_id, field_record_cache in field_cache.items():
if record_id:
- dump[field][record_id] = value
+ key = field.cache_key(browse(record_id))
+ if key in field_record_cache:
+ dump[field][record_id] = field_record_cache[key]
+
self.invalidate()
# re-fetch the records, and compare with their former cache