Pre-load Definitions in Split Modulestore when accessing all blocks - #14213
Conversation
914188f to
ce2e699
Compare
|
jenkins run bokchoy |
7420466 to
8df9851
Compare
|
Just so I'm clear on this -- this addresses the excessive calls to |
|
@ormsbee Yes. Sorry if that wasn't clear. I'll update the description. |
There was a problem hiding this comment.
Note to reviewers: This is a bug fix (No 2 in the description). Otherwise, the Mongo collection was mistakenly "Inserted" with pre-fetched records at the end of the bulk-operation - since the code incorrectly calculated that those definitions didn't pre-exist in the database.
There was a problem hiding this comment.
Note to reviewers: This change allows callers to be agnostic to whether a course was previously prefetched into the runtime. This is No 2 in the description.
There was a problem hiding this comment.
Could you explain this more please? Why would we ever not want to cache_items? Or does cache_items imply pre-fetch here?
There was a problem hiding this comment.
Good question. My change was an iterative, conservative change - changing the behavior only when lazy=False.
Now that you ask the question, I do wonder why we aren't always caching? For some reason, the previous code chose to cache only when the runtime for the course wasn't cached. Since there are many calls to _load_items from within split.py, there may be a good reason. (?). Here's the precondition listed in cache_items:
Handles caching of items once inheritance and any other one time per course per fetch operations are done.
There was a problem hiding this comment.
Note to reviewers: This TODO was addressed by passing a request_cache object to the modulestore in these tests.
There was a problem hiding this comment.
Note to reviewers: I have verified that this test breaks on master (without the changes in this PR).
There was a problem hiding this comment.
Note to reviewers: Renaming from _data to data allows this stub cache object to be used as a request_cache in the modulestore.
There was a problem hiding this comment.
Just curious how the name affects how it's passed.
There was a problem hiding this comment.
It's because Split Modulestore expects the given request_cache object to store its dict in a data field. For example: https://github.com/edx/edx-platform/blob/master/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py#L792
There was a problem hiding this comment.
Note to reviewers: Adding kwargs parameters here allows test code to specify optional parameters to the Modulestore, including a request_cache for this PR.
There was a problem hiding this comment.
Some questions:
- I see it being passed through the layers with
**kwargs, but where does therequest_cacheactually get popped off and read? - Is it necessary to pass an explicitly created request cache all the way down? Could the the test case inherit from
CacheIsolationTestCase, do its setup, and then test in uncached vs. cached states?
There was a problem hiding this comment.
Actually, thinking on item 2, are we avoiding it because of the Django dependency...?
There was a problem hiding this comment.
yes - exactly. This test runs just under nosetools on Jenkins.
And as we both know, running with python manage.py adds much more overhead. So unless absolutely necessary, I thought about keeping it as a non-django test.
There was a problem hiding this comment.
Note to reviewers: The query counts here went up by 1 only because the cache is now cleared before measuring the mongo calls. Otherwise, it was resulting in discrepancies between CMS and LMS runtimes.
There was a problem hiding this comment.
Note to reviewers: On master, instead of 4, this results in 23 mongo calls for Split and grows linearly with the number of blocks in the course.
There was a problem hiding this comment.
This. Is. Awesome. (I'm on vacation, so I won't be giving a broader review.)
There was a problem hiding this comment.
Note to @sanfordstudent: When the query context manager is used in a conjunction, only the latter query count is checked. Not the former. So all the check_mongo_calls in this file were not being called. A surprise for me as well. :(
There was a problem hiding this comment.
Wow, that's good to know.
There was a problem hiding this comment.
store here instead of modulestore()?
There was a problem hiding this comment.
Good catch. Thanks.
|
Tiny nitpick, but 👍 on the PR either way. Thank you for the reviewer's notes -- they were most helpful. |
|
Like Dave said, thanks for the notes for reviewers. They made understanding what was going on much easier. This looks great! 👍 |
84f4ef1 to
99e6d53
Compare
|
This PR has been deployed to edx.org Staging in preparation for a release to production on Weds, 1/4/2017. If you feel you need to validate it manually, please do so by then, and contact me if there is an urgent enough issue to scrap the deployment. |
|
👍 Thanks, @nasthagiri ! |
|
This PR has been deployed to edx.org. |
TNL-6222
Description
TLDR;
This PR addresses the excessive calls to
modulestore.definitions find_oneandmodulestore.active_versions find_oneinlms.djangoapps.grades.tasks.recalculate_subsection_grade_v2. It also reduces the number of calls tomodulestore.definitions find_oneincourseware.views.index:CoursewareIndex.get.Details
When monitoring the staged rollout of Persistent Grades, we noticed that the celery task for updating grades was making unexpected number of mongo queries - some even up to 2,000 mongo queries!
We were able to reduce the extra calls to
modulestore.active_versions find_oneby surrounding the task with a transactionalbulk_operationscontext manager. Those grew linearly by the number of subsections in the course.However, there were still excessive calls to
modulestore.definitions find_one(growing linearly by the number of problems in the course). After further testing, we narrowed down the issue to: (1) cache misses on the Block Structure and (2) Split Modulestore courses.For Block Structures, we expected all course blocks to be pre-loaded in a few (4) queries since we loaded the course with
depth=None. Apparently, there is another parameter that we should have also set:lazy=False. So this PR corrects the Block Structure's call to the modulestore to pre-load all definitions since they are needed during the "collect" phase.Additionally, when investigating this issue, we noticed that loading the Sequential on the Courseware page had the same issue (since
lazy=Falsewas not specified along withdepth=None, although definitions of all descendant blocks of the Sequential need to be accessed). So this PR updates Courseware to do so, and adds query count tests to validate the change.Finally, the implementation of the Split Modulestore had to be updated to:
Support pre-loading of Definitions with the
lazy=Falseparameter even after the runtime for the course had been previously cached. This allows callers to be agnostic to whether a course is pre-fetched without thedepth=None, lazy=Falseparameters, but then later accessed with those parameters.Fix a bug in
get_definitionsso thebulk_write_record.definitions_in_dbobject is also updated. Otherwise, the Mongo collection was mistakenly "Inserted" with pre-fetched records at the end of the bulk-operation - since the code incorrectly calculated that those definitions didn't pre-exist in the database.Note: There are still linearly growing SQL queries that will be addressed by a separate ticket: https://openedx.atlassian.net/browse/TNL-6225
Reviewers
If you've been tagged for review, please check your corresponding box once you've given the 👍.
FYI: @jcdyer
Post-review