Commit 413aeed1 by Chris Dodge

use django caching to hold computed metadata inheritence. Also invalidate the…

use django caching to hold computed metadata inheritence. Also invalidate the cache entries on writes (insert, updates)
parent a1c375b4
......@@ -96,6 +96,13 @@ CACHES = {
'KEY_PREFIX': 'general',
'VERSION': 4,
'KEY_FUNCTION': 'util.memcache.safe_key',
},
'mongo_metadata_inheritance': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/mongo_metadata_inheritance',
'TIMEOUT': 300,
'KEY_FUNCTION': 'util.memcache.safe_key',
}
}
......
......@@ -98,6 +98,13 @@ CACHES = {
'KEY_PREFIX': 'general',
'VERSION': 4,
'KEY_FUNCTION': 'util.memcache.safe_key',
},
'mongo_metadata_inheritance': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': '/var/tmp/mongo_metadata_inheritance',
'TIMEOUT': 300,
'KEY_FUNCTION': 'util.memcache.safe_key',
}
}
......
......@@ -8,6 +8,14 @@ from __future__ import absolute_import
from importlib import import_module
from os import environ
# cdodge: ICK! Sorry about this but I'm not sure how to resolve. We have some unit tests which do not setup a
# Django runtime. This import expects to find a "CACHE =" in a configuration, which doesn't exist in the
# test environment
try:
from django.core.cache import get_cache, InvalidCacheBackendError
except:
pass
from django.conf import settings
_MODULESTORES = {}
......@@ -33,11 +41,16 @@ def modulestore(name='default'):
class_ = load_function(settings.MODULESTORE[name]['ENGINE'])
options = {}
try:
options = {'metadata_inheritance_cache': get_cache('mongo_metadata_inheritance')}
except InvalidCacheBackendError:
pass
options.update(settings.MODULESTORE[name]['OPTIONS'])
for key in FUNCTION_KEYS:
if key in options:
options[key] = load_function(options[key])
_MODULESTORES[name] = class_(
**options
)
......
......@@ -24,7 +24,6 @@ from .exceptions import (ItemNotFoundError,
DuplicateItemError)
from .inheritance import own_metadata, INHERITABLE_METADATA, inherit_metadata
log = logging.getLogger(__name__)
# TODO (cpennington): This code currently operates under the assumption that
......@@ -216,7 +215,7 @@ class MongoModuleStore(ModuleStoreBase):
def __init__(self, host, db, collection, fs_root, render_template,
port=27017, default_class=None,
error_tracker=null_error_tracker,
user=None, password=None, **kwargs):
user=None, password=None, metadata_inheritance_cache=None, **kwargs):
ModuleStoreBase.__init__(self)
......@@ -247,7 +246,10 @@ class MongoModuleStore(ModuleStoreBase):
self.fs_root = path(fs_root)
self.error_tracker = error_tracker
self.render_template = render_template
self.metadata_inheritance_cache = {}
if metadata_inheritance_cache is None:
logging.warning('metadata_inheritance_cache is None. Should be defined (unless running unit tests). Check config files....')
self.metadata_inheritance_cache = metadata_inheritance_cache
def get_metadata_inheritance_tree(self, location):
'''
......@@ -255,18 +257,14 @@ class MongoModuleStore(ModuleStoreBase):
'''
# get all collections in the course, this query should not return any leaf nodes
# note this is a bit ugly as when we add new categories of containers, we have to add it here
query = {
'_id.org': location.org,
'_id.course': location.course,
'$or': [
{"_id.category":"course"},
{"_id.category":"chapter"},
{"_id.category":"sequential"},
{"_id.category":"vertical"}
]
'_id.category': {'$in': [ 'course', 'chapter', 'sequential', 'vertical']}
}
# we just want the Location, children, and metadata
record_filter = {'_id':1,'definition.children':1,'metadata':1}
record_filter = {'_id': 1, 'definition.children': 1, 'metadata': 1}
# call out to the DB
resultset = self.collection.find(query, record_filter)
......@@ -284,7 +282,13 @@ class MongoModuleStore(ModuleStoreBase):
# now traverse the tree and compute down the inherited metadata
metadata_to_inherit = {}
def _compute_inherited_metadata(url):
my_metadata = results_by_url[url]['metadata']
my_metadata = {}
# check for presence of metadata key. Note that a given module may not yet be fully formed.
# example: update_item -> update_children -> update_metadata sequence on new item create
# if we get called here without update_metadata called first then 'metadata' hasn't been set
# as we're not fully transactional at the DB layer. Same comment applies to below key name
# check
my_metadata = results_by_url[url].get('metadata', {})
for key in my_metadata.keys():
if key not in INHERITABLE_METADATA:
del my_metadata[key]
......@@ -295,7 +299,7 @@ class MongoModuleStore(ModuleStoreBase):
for child in results_by_url[url].get('definition',{}).get('children',[]):
if child in results_by_url:
new_child_metadata = copy.deepcopy(my_metadata)
new_child_metadata.update(results_by_url[child]['metadata'])
new_child_metadata.update(results_by_url[child].get('metadata', {}))
results_by_url[child]['metadata'] = new_child_metadata
metadata_to_inherit[child] = new_child_metadata
_compute_inherited_metadata(child)
......@@ -306,28 +310,30 @@ class MongoModuleStore(ModuleStoreBase):
if root is not None:
_compute_inherited_metadata(root)
cache = {'parent_metadata': metadata_to_inherit,
return {'parent_metadata': metadata_to_inherit,
'timestamp' : datetime.now()}
return cache
def get_cached_metadata_inheritance_tree(self, location, max_age_allowed):
def get_cached_metadata_inheritance_tree(self, location, force_refresh=False):
'''
TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
'''
cache_name = '{0}/{1}'.format(location.org, location.course)
cache = self.metadata_inheritance_cache.get(cache_name,{'parent_metadata': {},
'timestamp': datetime.now() - timedelta(hours=1)})
age = (datetime.now() - cache['timestamp'])
key_name = '{0}/{1}'.format(location.org, location.course)
if age.seconds >= max_age_allowed:
logging.debug('loading entire inheritance tree for {0}'.format(cache_name))
cache = self.get_metadata_inheritance_tree(location)
self.metadata_inheritance_cache[cache_name] = cache
tree = None
if self.metadata_inheritance_cache is not None:
tree = self.metadata_inheritance_cache.get(key_name)
return cache
if tree is None or force_refresh:
tree = self.get_metadata_inheritance_tree(location)
if self.metadata_inheritance_cache is not None:
self.metadata_inheritance_cache.set(key_name, tree)
return tree
def clear_cached_metadata_inheritance_tree(self, location):
key_name = '{0}/{1}'.format(location.org, location.course)
if self.metadata_inheritance_cache is not None:
self.metadata_inheritance_cache.delete(key_name)
def _clean_item_data(self, item):
"""
......@@ -387,7 +393,7 @@ class MongoModuleStore(ModuleStoreBase):
# if we are loading a course object, there is no parent to inherit the metadata from
# so don't bother getting it
if item['location']['category'] != 'course':
metadata_inheritance_tree = self.get_cached_metadata_inheritance_tree(Location(item['location']), 300)
metadata_inheritance_tree = self.get_cached_metadata_inheritance_tree(Location(item['location']))
# TODO (cdodge): When the 'split module store' work has been completed, we should remove
# the 'metadata_inheritance_tree' parameter
......@@ -513,6 +519,9 @@ class MongoModuleStore(ModuleStoreBase):
except pymongo.errors.DuplicateKeyError:
raise DuplicateItemError(location)
# recompute (and update) the metadata inheritance tree which is cached
self.get_cached_metadata_inheritance_tree(Location(location), force_refresh = True)
def get_course_for_item(self, location, depth=0):
'''
VS[compat]
......@@ -577,6 +586,8 @@ class MongoModuleStore(ModuleStoreBase):
"""
self._update_single_item(location, {'definition.children': children})
# recompute (and update) the metadata inheritance tree which is cached
self.get_cached_metadata_inheritance_tree(Location(location), force_refresh = True)
def update_metadata(self, location, metadata):
"""
......@@ -601,7 +612,8 @@ class MongoModuleStore(ModuleStoreBase):
self.update_metadata(course.location, own_metadata(course))
self._update_single_item(location, {'metadata': metadata})
# recompute (and update) the metadata inheritance tree which is cached
self.get_cached_metadata_inheritance_tree(loc, force_refresh = True)
def delete_item(self, location):
"""
......@@ -620,6 +632,8 @@ class MongoModuleStore(ModuleStoreBase):
self.update_metadata(course.location, own_metadata(course))
self.collection.remove({'_id': Location(location).dict()})
# recompute (and update) the metadata inheritance tree which is cached
self.get_cached_metadata_inheritance_tree(Location(location), force_refresh = True)
def get_parent_locations(self, location, course_id):
......
......@@ -253,7 +253,7 @@ class XMLModuleStore(ModuleStoreBase):
"""
An XML backed ModuleStore
"""
def __init__(self, data_dir, default_class=None, course_dirs=None, load_error_modules=True):
def __init__(self, data_dir, default_class=None, course_dirs=None, load_error_modules=True, metadata_inheritance_cache=None):
"""
Initialize an XMLModuleStore from data_dir
......
......@@ -115,7 +115,14 @@ CACHES = {
'KEY_PREFIX': 'general',
'VERSION': 4,
'KEY_FUNCTION': 'util.memcache.safe_key',
}
},
'mongo_metadata_inheritance': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': '/var/tmp/mongo_metadata_inheritance',
'TIMEOUT': 300,
'KEY_FUNCTION': 'util.memcache.safe_key',
}
}
# Dummy secret key for dev
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment