Commit b137f8d7 by Don Mitchell

Explain query counts

Fix trivial excesses (2 splits configured & don't fetch courses if there are none)
LMS-11220, LMS-11391, LMS-11390
parent e18e18a8
...@@ -200,12 +200,14 @@ class TestCourseListing(ModuleStoreTestCase): ...@@ -200,12 +200,14 @@ class TestCourseListing(ModuleStoreTestCase):
self.assertGreaterEqual(iteration_over_courses_time_2.elapsed, iteration_over_groups_time_2.elapsed) self.assertGreaterEqual(iteration_over_courses_time_2.elapsed, iteration_over_groups_time_2.elapsed)
# Now count the db queries # Now count the db queries
store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo)
with check_mongo_calls(USER_COURSES_COUNT): with check_mongo_calls(USER_COURSES_COUNT):
_accessible_courses_list_from_groups(self.request) _accessible_courses_list_from_groups(self.request)
# TODO: LMS-11220: Document why this takes 6 calls # Calls:
with check_mongo_calls(6): # 1) query old mongo
# 2) get_more on old mongo
# 3) query split (but no courses so no fetching of data)
with check_mongo_calls(3):
_accessible_courses_list(self.request) _accessible_courses_list(self.request)
def test_get_course_list_with_same_course_id(self): def test_get_course_list_with_same_course_id(self):
...@@ -276,7 +278,7 @@ class TestCourseListing(ModuleStoreTestCase): ...@@ -276,7 +278,7 @@ class TestCourseListing(ModuleStoreTestCase):
course_location = SlashSeparatedCourseKey('testOrg', 'erroredCourse', 'RunBabyRun') course_location = SlashSeparatedCourseKey('testOrg', 'erroredCourse', 'RunBabyRun')
course = self._create_course_with_access_groups(course_location, self.user) course = self._create_course_with_access_groups(course_location, self.user)
course_db_record = store._find_one(course.location) course_db_record = store._find_one(course.location)
course_db_record.setdefault('metadata', {}).get('tabs', []).append({"type": "wiko", "name": "Wiki" }) course_db_record.setdefault('metadata', {}).get('tabs', []).append({"type": "wiko", "name": "Wiki"})
store.collection.update( store.collection.update(
{'_id': course.location.to_deprecated_son()}, {'_id': course.location.to_deprecated_son()},
{'$set': { {'$set': {
......
...@@ -73,17 +73,6 @@ STATICFILES_STORAGE='pipeline.storage.NonPackagingPipelineStorage' ...@@ -73,17 +73,6 @@ STATICFILES_STORAGE='pipeline.storage.NonPackagingPipelineStorage'
STATIC_URL = "/static/" STATIC_URL = "/static/"
PIPELINE_ENABLED=False PIPELINE_ENABLED=False
# Add split as another store for testing
MODULESTORE['default']['OPTIONS']['stores'].append(
{
'NAME': 'split',
'ENGINE': 'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore',
'DOC_STORE_CONFIG': DOC_STORE_CONFIG,
'OPTIONS': {
'render_template': 'edxmako.shortcuts.render_to_string',
}
},
)
# Update module store settings per defaults for tests # Update module store settings per defaults for tests
update_module_store_settings( update_module_store_settings(
MODULESTORE, MODULESTORE,
......
...@@ -681,6 +681,9 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase): ...@@ -681,6 +681,9 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
version_guids.append(version_guid) version_guids.append(version_guid)
id_version_map[version_guid] = course_index id_version_map[version_guid] = course_index
if not version_guids:
return []
matching_structures = self.find_structures_by_id(version_guids) matching_structures = self.find_structures_by_id(version_guids)
# get the blocks for each course index (s/b the root) # get the blocks for each course index (s/b the root)
......
...@@ -290,11 +290,9 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -290,11 +290,9 @@ class TestMixedModuleStore(unittest.TestCase):
self.store.create_course('org_x', 'course_y', 'run_z', self.user_id) self.store.create_course('org_x', 'course_y', 'run_z', self.user_id)
# Draft: # Draft:
# - One lookup to locate an item that exists # problem: One lookup to locate an item that exists
# - Two lookups to determine an item doesn't exist (one to check mongo, one to check split) # fake: one w/ wildcard version
# split has one lookup for the course and then one for the course items # split has one lookup for the course and then one for the course items
# TODO: LMS-11220: Document why draft find count is [1, 1]
# TODO: LMS-11220: Document why split find count is [2, 2]
@ddt.data(('draft', [1, 1], 0), ('split', [2, 2], 0)) @ddt.data(('draft', [1, 1], 0), ('split', [2, 2], 0))
@ddt.unpack @ddt.unpack
def test_has_item(self, default_ms, max_find, max_send): def test_has_item(self, default_ms, max_find, max_send):
...@@ -317,10 +315,12 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -317,10 +315,12 @@ class TestMixedModuleStore(unittest.TestCase):
with self.assertRaises(UnsupportedRevisionError): with self.assertRaises(UnsupportedRevisionError):
self.store.has_item(self.fake_location, revision=ModuleStoreEnum.RevisionOption.draft_preferred) self.store.has_item(self.fake_location, revision=ModuleStoreEnum.RevisionOption.draft_preferred)
# draft is 2 to compute inheritance # draft queries:
# split is 2 (would be 3 on course b/c it looks up the wiki_slug in definitions) # problem: find draft item, find all items pertinent to inheritance computation
# TODO: LMS-11220: Document why draft find count is [2, 2] # non-existent problem: find draft, find published
# TODO: LMS-11220: Document why split find count is [3, 3] # split:
# problem: active_versions, structure, then active_versions again?
# non-existent problem: ditto
@ddt.data(('draft', [2, 2], 0), ('split', [3, 3], 0)) @ddt.data(('draft', [2, 2], 0), ('split', [3, 3], 0))
@ddt.unpack @ddt.unpack
def test_get_item(self, default_ms, max_find, max_send): def test_get_item(self, default_ms, max_find, max_send):
...@@ -345,8 +345,10 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -345,8 +345,10 @@ class TestMixedModuleStore(unittest.TestCase):
with self.assertRaises(UnsupportedRevisionError): with self.assertRaises(UnsupportedRevisionError):
self.store.get_item(self.fake_location, revision=ModuleStoreEnum.RevisionOption.draft_preferred) self.store.get_item(self.fake_location, revision=ModuleStoreEnum.RevisionOption.draft_preferred)
# compared to get_item for the course, draft asks for both draft and published # Draft:
# TODO: LMS-11220: Document why split find count is 3 # wildcard query, 6! load pertinent items for inheritance calls, course root fetch (why)
# Split:
# active_versions (with regex), structure, and spurious active_versions refetch
@ddt.data(('draft', 8, 0), ('split', 3, 0)) @ddt.data(('draft', 8, 0), ('split', 3, 0))
@ddt.unpack @ddt.unpack
def test_get_items(self, default_ms, max_find, max_send): def test_get_items(self, default_ms, max_find, max_send):
...@@ -372,10 +374,11 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -372,10 +374,11 @@ class TestMixedModuleStore(unittest.TestCase):
revision=ModuleStoreEnum.RevisionOption.draft_preferred revision=ModuleStoreEnum.RevisionOption.draft_preferred
) )
# draft: 2 to look in draft and then published and then 5 for updating ancestors. # draft: get draft, count parents, get parents, count & get grandparents, count & get greatgrand,
# split: 3 to get the course structure & the course definition (show_calculator is scope content) # count & get next ancestor (chapter's parent), count non-existent next ancestor, get inheritance
# before the change. 1 during change to refetch the definition. 3 afterward (b/c it calls get_item to return the "new" object). # sends: update problem and then each ancestor up to course (edit info)
# 2 sends to update index & structure (calculator is a setting field) # split: active_versions, definitions (calculator field), structures
# 2 sends to update index & structure (note, it would also be definition if a content field changed)
@ddt.data(('draft', 11, 5), ('split', 3, 2)) @ddt.data(('draft', 11, 5), ('split', 3, 2))
@ddt.unpack @ddt.unpack
def test_update_item(self, default_ms, max_find, max_send): def test_update_item(self, default_ms, max_find, max_send):
...@@ -638,8 +641,14 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -638,8 +641,14 @@ class TestMixedModuleStore(unittest.TestCase):
# Check the parent for changes should return True and not throw an exception # Check the parent for changes should return True and not throw an exception
self.assertTrue(self.store.has_changes(parent)) self.assertTrue(self.store.has_changes(parent))
# TODO: LMS-11220: Document why split find count is 4 # Draft
# TODO: LMS-11220: Document why split send count is 3 # Find: find parents (definition.children query), get parent, get course (fill in run?),
# find parents of the parent (course), get inheritance items,
# get errors, get item (to delete subtree), get inheritance again.
# Sends: delete item, update parent
# Split
# Find: active_versions, 2 structures (published & draft), definition (unnecessary)
# Sends: updated draft and published structures and active_versions
@ddt.data(('draft', 8, 2), ('split', 4, 3)) @ddt.data(('draft', 8, 2), ('split', 4, 3))
@ddt.unpack @ddt.unpack
def test_delete_item(self, default_ms, max_find, max_send): def test_delete_item(self, default_ms, max_find, max_send):
...@@ -656,11 +665,17 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -656,11 +665,17 @@ class TestMixedModuleStore(unittest.TestCase):
self.store.delete_item(self.writable_chapter_location, self.user_id) self.store.delete_item(self.writable_chapter_location, self.user_id)
# verify it's gone # verify it's gone
# FIXME check both published and draft branches
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
self.store.get_item(self.writable_chapter_location) self.store.get_item(self.writable_chapter_location)
# TODO: LMS-11220: Document why split find count is 4 # Draft:
# TODO: LMS-11220: Document why split send count is 3 # queries: find parent (definition.children), count versions of item, get parent, count grandparents,
# inheritance items, draft item, draft child, get errors, inheritance
# sends: delete draft vertical and update parent
# Split:
# queries: active_versions, draft and published structures, definition (unnecessary)
# sends: update published (why?), draft, and active_versions
@ddt.data(('draft', 9, 2), ('split', 4, 3)) @ddt.data(('draft', 9, 2), ('split', 4, 3))
@ddt.unpack @ddt.unpack
def test_delete_private_vertical(self, default_ms, max_find, max_send): def test_delete_private_vertical(self, default_ms, max_find, max_send):
...@@ -706,7 +721,12 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -706,7 +721,12 @@ class TestMixedModuleStore(unittest.TestCase):
self.assertFalse(self.store.has_item(leaf_loc)) self.assertFalse(self.store.has_item(leaf_loc))
self.assertNotIn(vert_loc, course.children) self.assertNotIn(vert_loc, course.children)
# TODO: LMS-11220: Document why split find count is 2 # Draft:
# find: find parent (definition.children) 2x, find draft item, check error state, get inheritance items
# send: one delete query for specific item
# Split:
# find: active_version & structure
# send: update structure and active_versions
@ddt.data(('draft', 5, 1), ('split', 2, 2)) @ddt.data(('draft', 5, 1), ('split', 2, 2))
@ddt.unpack @ddt.unpack
def test_delete_draft_vertical(self, default_ms, max_find, max_send): def test_delete_draft_vertical(self, default_ms, max_find, max_send):
...@@ -740,9 +760,15 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -740,9 +760,15 @@ class TestMixedModuleStore(unittest.TestCase):
with check_mongo_calls(max_find, max_send): with check_mongo_calls(max_find, max_send):
self.store.delete_item(private_leaf.location, self.user_id) self.store.delete_item(private_leaf.location, self.user_id)
# TODO: LMS-11220: Document why split find count is 5 # Draft:
# TODO: LMS-11220: Document why draft find count is 4 # 1) find all courses (wildcard),
@ddt.data(('draft', 4, 0), ('split', 5, 0)) # 2) get each course 1 at a time (1 course),
# 3) wildcard split if it has any (1) but it doesn't
# Split:
# 1) wildcard split search,
# 2-4) active_versions, structure, definition (s/b lazy; so, unnecessary)
# 5) wildcard draft mongo which has none
@ddt.data(('draft', 3, 0), ('split', 5, 0))
@ddt.unpack @ddt.unpack
def test_get_courses(self, default_ms, max_find, max_send): def test_get_courses(self, default_ms, max_find, max_send):
self.initdb(default_ms) self.initdb(default_ms)
...@@ -785,9 +811,8 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -785,9 +811,8 @@ class TestMixedModuleStore(unittest.TestCase):
with self.assertRaises(AttributeError): with self.assertRaises(AttributeError):
xml_store.create_course("org", "course", "run", self.user_id) xml_store.create_course("org", "course", "run", self.user_id)
# draft is 2 to compute inheritance # draft is 2: find out which ms owns course, get item
# split is 3 (one for the index, one for the definition to check if the wiki is set, and one for the course structure # split: find out which ms owns course, active_versions, structure, definition (definition s/b unnecessary unless lazy is false)
# TODO: LMS-11220: Document why split find count is 4
@ddt.data(('draft', 2, 0), ('split', 4, 0)) @ddt.data(('draft', 2, 0), ('split', 4, 0))
@ddt.unpack @ddt.unpack
def test_get_course(self, default_ms, max_find, max_send): def test_get_course(self, default_ms, max_find, max_send):
...@@ -805,7 +830,8 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -805,7 +830,8 @@ class TestMixedModuleStore(unittest.TestCase):
# notice this doesn't test getting a public item via draft_preferred which draft would have 2 hits (split # notice this doesn't test getting a public item via draft_preferred which draft would have 2 hits (split
# still only 2) # still only 2)
# TODO: LMS-11220: Document why draft find count is 2 # Draft: count via definition.children query, then fetch via that query
# Split: active_versions, structure
@ddt.data(('draft', 2, 0), ('split', 2, 0)) @ddt.data(('draft', 2, 0), ('split', 2, 0))
@ddt.unpack @ddt.unpack
def test_get_parent_locations(self, default_ms, max_find, max_send): def test_get_parent_locations(self, default_ms, max_find, max_send):
...@@ -902,20 +928,22 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -902,20 +928,22 @@ class TestMixedModuleStore(unittest.TestCase):
(child_to_delete_location, None, ModuleStoreEnum.RevisionOption.published_only), (child_to_delete_location, None, ModuleStoreEnum.RevisionOption.published_only),
]) ])
# Mongo reads: # Draft:
# First location: # Problem path:
# - count problem (1) # 1. Get problem
# - For each level of ancestors: (5) # 2-3. count matches definition.children called 2x?
# - Count ancestor # 4. get parent via definition.children query
# - retrieve ancestor # 5-7. 2 counts and 1 get grandparent via definition.children
# - compute inheritable data # 8-10. ditto for great-grandparent
# Second location: # 11-13. ditto for next ancestor
# - load vertical # 14. fail count query looking for parent of course (unnecessary)
# - load inheritance data # 15. get course record direct query (not via definition.children) (already fetched in 13)
# 16. get items for inheritance computation
# TODO: LMS-11220: Document why draft send count is 5 # 17. get vertical (parent of problem)
# TODO: LMS-11220: Document why draft find count is [19, 6] # 18. get items for inheritance computation (why? caching should handle)
# TODO: LMS-11220: Document why split find count is [2, 2] # 19-20. get vertical_x1b (? why? this is the only ref in trace) & items for inheritance computation
# Chapter path: get chapter, count parents 2x, get parents, count non-existent grandparents
# Split: active_versions & structure
@ddt.data(('draft', [20, 5], 0), ('split', [2, 2], 0)) @ddt.data(('draft', [20, 5], 0), ('split', [2, 2], 0))
@ddt.unpack @ddt.unpack
def test_path_to_location(self, default_ms, num_finds, num_sends): def test_path_to_location(self, default_ms, num_finds, num_sends):
...@@ -936,6 +964,7 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -936,6 +964,7 @@ class TestMixedModuleStore(unittest.TestCase):
) )
for location, expected in should_work: for location, expected in should_work:
# each iteration has different find count, pop this iter's find count
with check_mongo_calls(num_finds.pop(0), num_sends): with check_mongo_calls(num_finds.pop(0), num_sends):
self.assertEqual(path_to_location(self.store, location), expected) self.assertEqual(path_to_location(self.store, location), expected)
...@@ -1074,6 +1103,8 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -1074,6 +1103,8 @@ class TestMixedModuleStore(unittest.TestCase):
# It does not discard the child vertical, even though that child is a draft (with no published version) # It does not discard the child vertical, even though that child is a draft (with no published version)
self.assertEqual(num_children, len(reverted_parent.children)) self.assertEqual(num_children, len(reverted_parent.children))
# Draft: get all items which can be or should have parents
# Split: active_versions, structure
@ddt.data(('draft', 1, 0), ('split', 2, 0)) @ddt.data(('draft', 1, 0), ('split', 2, 0))
@ddt.unpack @ddt.unpack
def test_get_orphans(self, default_ms, max_find, max_send): def test_get_orphans(self, default_ms, max_find, max_send):
...@@ -1212,8 +1243,8 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -1212,8 +1243,8 @@ class TestMixedModuleStore(unittest.TestCase):
self.assertEqual(self.user_id, block.subtree_edited_by) self.assertEqual(self.user_id, block.subtree_edited_by)
self.assertGreater(datetime.datetime.now(UTC), block.subtree_edited_on) self.assertGreater(datetime.datetime.now(UTC), block.subtree_edited_on)
# TODO: LMS-11220: Document why split find count is 2 # Draft: wildcard search of draft and split
# TODO: LMS-11220: Document why draft find count is 2 # Split: wildcard search of draft and split
@ddt.data(('draft', 2, 0), ('split', 2, 0)) @ddt.data(('draft', 2, 0), ('split', 2, 0))
@ddt.unpack @ddt.unpack
def test_get_courses_for_wiki(self, default_ms, max_find, max_send): def test_get_courses_for_wiki(self, default_ms, max_find, max_send):
...@@ -1242,15 +1273,16 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -1242,15 +1273,16 @@ class TestMixedModuleStore(unittest.TestCase):
self.assertEqual(len(self.store.get_courses_for_wiki('edX.simple.2012_Fall')), 0) self.assertEqual(len(self.store.get_courses_for_wiki('edX.simple.2012_Fall')), 0)
self.assertEqual(len(self.store.get_courses_for_wiki('no_such_wiki')), 0) self.assertEqual(len(self.store.get_courses_for_wiki('no_such_wiki')), 0)
# Mongo reads: # Draft:
# - load vertical # Find: find vertical, find children, get last error
# - load vertical children # Sends:
# - get last error # 1. delete all of the published nodes in subtree
# Split takes 1 query to read the course structure, deletes all of the entries in memory, and loads the module from an in-memory cache # 2. insert vertical as published (deleted in step 1) w/ the deleted problems as children
# 3-6. insert the 3 problems and 1 html as published
# Split: active_versions, 2 structures (pre & post published?)
# Sends: # Sends:
# - insert structure # - insert structure
# - write index entry # - write index entry
# TODO: LMS-11220: Document why split find count is 3
@ddt.data(('draft', 3, 6), ('split', 3, 2)) @ddt.data(('draft', 3, 6), ('split', 3, 2))
@ddt.unpack @ddt.unpack
def test_unpublish(self, default_ms, max_find, max_send): def test_unpublish(self, default_ms, max_find, max_send):
...@@ -1285,6 +1317,8 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -1285,6 +1317,8 @@ class TestMixedModuleStore(unittest.TestCase):
) )
self.assertIsNotNone(draft_xblock) self.assertIsNotNone(draft_xblock)
# Draft: specific query for revision None
# Split: active_versions, structure
@ddt.data(('draft', 1, 0), ('split', 2, 0)) @ddt.data(('draft', 1, 0), ('split', 2, 0))
@ddt.unpack @ddt.unpack
def test_has_published_version(self, default_ms, max_find, max_send): def test_has_published_version(self, default_ms, max_find, max_send):
...@@ -1727,9 +1761,9 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -1727,9 +1761,9 @@ class TestMixedModuleStore(unittest.TestCase):
with self.store.default_store(fake_store): with self.store.default_store(fake_store):
pass # pragma: no cover pass # pragma: no cover
#============================================================================================================= # ============================================================================================================
# General utils for not using django settings # General utils for not using django settings
#============================================================================================================= # ============================================================================================================
def load_function(path): def load_function(path):
......
...@@ -89,11 +89,21 @@ class TestPublish(SplitWMongoCourseBoostrapper): ...@@ -89,11 +89,21 @@ class TestPublish(SplitWMongoCourseBoostrapper):
""" """
vert_location = self.old_course_key.make_usage_key('vertical', block_id='Vert1') vert_location = self.old_course_key.make_usage_key('vertical', block_id='Vert1')
item = self.draft_mongo.get_item(vert_location, 2) item = self.draft_mongo.get_item(vert_location, 2)
# Vert1 has 3 children; so, publishes 4 nodes which may mean 4 inserts & 1 bulk remove # Finds:
# TODO: LMS-11220: Document why find count is 25 # 1 get draft vert,
# 25-June-2014 find calls are 19. Probably due to inheritance recomputation? # 2-10 for each child: (3 children x 3 queries each)
# 02-July-2014 send calls are 7. 5 from above, plus 2 for updating subtree edit info for Chapter1 and course # get draft and then published child
# find calls are 22. 19 from above, plus 3 for finding the parent of Vert1, Chapter1, and course # compute inheritance
# 11 get published vert
# 12-15 get each ancestor (count then get): (2 x 2),
# 16 then fail count of course parent (1)
# 17 compute inheritance
# 18 get last error
# 19-20 get draft and published vert
# Sends:
# delete the subtree of drafts (1 call),
# update the published version of each node in subtree (4 calls),
# update the ancestors up to course (2 calls)
with check_mongo_calls(20, 7): with check_mongo_calls(20, 7):
self.draft_mongo.publish(item.location, self.user_id) self.draft_mongo.publish(item.location, self.user_id)
......
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