Repository navigation
Cache/points counting refactoring + related bug fixes - #1231
Conversation
|
Already did this as part of additional optimizations. |
3199a1b to
80b7244
Compare
Performance changesI'm a little worried about the large variances in the load times. Sometimes it seems to be very slow. I'm not sure if this only happens with the new changes but it does seem like that. It might be caused by the garbage collector activating during the load. Synopsis
O1 benchmarks (on minus)The decimal point numbers are in seconds. BEFORE (pymemcache):
BEFORE (pylibmc):
AFTER (pymemcache):
AFTER (pylibmc):
|
ae2eab7 to
4758eb7
Compare
9c13725 to
815b18d
Compare
markkuriekkinen
left a comment
There was a problem hiding this comment.
Unfortunately, I'm not able to review this properly since we have run out of time. This pull request is extremely complex and it is scary. A thorough review would take a lot of time. There are major risks in making very complex changes in the core of an old system. If something were broken, would we ever be able to locate and fix the issue? Do we even notice the malfunction before it has caused too much damage?
It is hard to evaluate whether the pull request is really good and well-designed or fundamentally broken. It is so complex that I can't quickly make much sense of it.
Anyway, you may finish this and do the final git-rebase and such. We will need to merge this soon so that it doesn't fall behind the master branch. Fixing git conflicts would be awful with such a massive pull request.
|
I forgot to add in the previous message that it is great that you have written long git commit messages. They help others to understand all this. |
7b44fe8 to
7f5d633
Compare
Renamed is_staff to show_unrevealed as that is what it does. Renamed check_reveal_rule to apply_reveal_rule to make it more apparent what it does.
Replace them with a direct mapping to the entry. Saves the need to traverse the tree to find the entries. The duplication is not a problem as they are references to the same objects and the pickle library used by the cache understands that.
They are easier to read and are simpler
Greatly simplifies the control flow, making it easier to follow.
Makes the structure clearer and adds better type hints. Also removes unconfirmed from CachedContent as that made no sense, it only ever was set to False, and only used in/through CachedPoints.
Certain attributes aren't type hinted automatically, like the id field and foreign key related fields.
- Add bunch of types - Replace .type == "..." check with isinstance checks. This is more rigorous, helps the type system, and should be about as fast as testing the type attribute - Replace .find() with simpler methods when appropriate
Behavioural changes: - course front page now shows "Not submitted" on confirm_the_level exercises without submissions or confirmable points - matches the exercise pages - earlier it showed nothing - exercise pages now show submission status as long as it hasn't been graded - earlier it wouldn't show status if confirm_the_level and status == Submission.STATUS.WAITING, in which case it showed "Submitted" - i.e. shows "In grading" instead of "Submitted" for submissions that have confirm_the_level and status == Submission.STATUS.WAITING - exercise box points badge now links to the best unofficial submission instead of first when all submissions are unofficial - seemed to be a bug in UserExerciseSummary - exercise is passed in cache only if best submission is READY but UserExerciseSummary checked that it is not UNOFFICIAL - a group is 'fixed' if there are any submission to an exercise - previously had to be at least one non-error submission - exercise boxes do not show unofficial submissions as part of the out of max submissions count - course front page no longer shows unofficial points for exercises if only unofficial submissions exist
It might take some time but I do think you will be able to fix any issues. In any case, there is always the option of reverting the changes. To note: if there ever comes a need to revert these changes, you might want to revert to c0f447c or 5778401 if the former commit has the issues. If even the latter commit has issues, you can just revert the whole thing. The idea is that the second linked commit contains the change from a dict to a class which is the big change in the cache interface. This makes reverting any changes made after this PR is merged back to the old cache much easier. The first linked commit contains some should-be harmless simplification of the code on top of the second linked commit which is why it should be left alone if possible.
The only way I can see this change causing serious damage is if the point calculation is wrong because then the actual points given by the all results page is different from the points shown on UI but I think this would be noticed very fast (and I didn't see any such issues in any of my testing). The thing about the cache is that it mostly just affects the UI. The most that can happen is that it shows wrong points, points that should be hidden, or pages are listed in the course front page that shouldn't be there (or aren't listed when should). The other option is that there is a bug that crashes a particular view (most likely would be because I forgot to change the dict access notation to the property access notation somewhere) but this isn't a huge issue as the fix should be simple, and all the major views seem to work based on my testing. Biggest concerns are exactly that I forgot to swap some dict access to a object property access or the cache TransactionManager somehow misbehaves (test_nested_transactions for it seems to sometimes randomly fail on github, however such nesting of transactions doesn't really happen in the code and other tests pass fine). The latter will at most fail to save (highly unlikely as this is never done inside a transaction anyway) or invalidate a cache entry, so nothing major. It is also easy to remove by just accessing the cache directly in lib/cache/cache.py instead of through the TransactionManager. This will, however, add back the possibility of the cache not being invalidated correctly after the transaction in course configuring.
I can confidentally say that it at least isn't fundamentally broken. I think the design is good given the constraints on performance. Most of the complexity comes from performance optimizations, so it could be "better" (i.e. simpler in this case) if we didn't care about performance. Therea are only two things I don't like about the code: the modifiers given to the cache objects and how the show_unrevealed is handled in the generation code (two attributes for each revealable value) but it was the best way I could think of implementing show_unrevealed without having to save the data twice in the cache. Though these are pretty minor things.
I have done the final rebase. |
|
Some basic benchmarks of fully loading (including exercises) a particular chapter page with multiple exercises on O1 on my pc:
|
|
Don't merge this yet. I noticed a module model answer related optimization that I should do before that |
Instead of loading the points for the whole instance and finding the exercise from there, load the cache entry for the exercise directly.
Unofficial submissions shouldn't be counted when determining submission limits. This also matches how the cache (and thus UI) counts them.
Earlier UI included points from hidden exercises and modules too.
It was yet another place where points are calculated but it was only used in an points export management command.
- Speeds up course configuring as the invalidation is done in a single access to the cache. - Prevents error situations where cache was generated between the invalidation being called and the transaction being committed. In such cases, the generated item would overwrite the invalidation but still use old data as the transaction hasn't been committed to the database. - Prevents unnecessary cache invalidation in the case that the transaction is rolled back. - Prevents non-committed transaction statements from affecting other requests. Currently, this was only an issue in tests because that is the only place that generates cache items inside a transaction.
Proxies are autoresolved using the manager that was used to create it (if one exists)
- Makes invalidation much easier to deal with and more reliable. As far as I can tell, this basically quarantees the validity of the cache data (as long as the database wasn't updated without the signals being run, which shouldn't happen) - The bad side is that the whole dependency tree must be loaded whenever a cache object is loaded. This makes the loading slower but doesn't seem to be so slow that it's a problem. - _generate_data can return dependencies that are checked for validity when the object is loaded from cache. The object is regenerated if the validity fails. Note that if the dependency is newer, the object is considered invalid as the dependency has changed after the object's generation. - Generally speaking, INVALIDATORS should be used if the model object is accessed directly inside _generate_data and dependencies are used when the data is accessed through a cache object or the object stores references to other cache objects. Note that dependency is not needed if the reference can only change when an invalidator fires. For example, the module reference (i.e. the module id) of an exercise can only change if the exercise model changes because the module id is stored in the exercise model. This means that the module does not need to be added to the dependencies (as long as the exercise cache is invalidated through the INVALIDATORS whenever the exercise model changes). - is_valid was removed as it would require building each dependency object to check if an object is valid. Instead, the expiry time is stored alongside the generated_on on value to allow invalidating using an expiry time without the need to build each dependency.
- Add database manager classes that deal with fetching data from the database for the cache entries to use during generation - Should be a bit more reliable, faster and easier to use than the old way - Move confirmable_points to parent entries to remove dependencies to sibling entries
Makes then easier to distinguish. - content cache entries to *Content - points cache entries to *Points - submittable exercises are called exercises and non-submittable ones are called learning objects to match the model naming.
- They contain the same things, and cached_points is only used where summary is used - self.feedback_revealed replaced with self.is_course_staff in summary loading: reveal rule is already checked by the cache generation code
- Add cached properties so that the operations are only done if needed. - Reuse already fetched cache data so that it is not fetched again.
Allows selecting whether the group sizes are taken from the cached points or content. In essence, this means that we can avoid loading cached content on the course instance page.
|
This is now done. |
Commit 0e74906 (#1273) introduced a crash when `GraderUser` accesses any course page or API. The commit added a `CachedPoints` instance variable to `CourseInstanceBaseMixin`, but `CachedPoints` crashes when it is initialized with the `GraderUser`. `CachedPoints` does not crash with `AnonymousUser`, thus it is used instead of `GraderUser` to work around the crash. In the master branch, the commit 0c0de05 is similar to 0e74906, but there is no crash due to the cache changes in #1231.
Commit 0e74906 (apluslms#1273) introduced a crash when `GraderUser` accesses any course page or API. The commit added a `CachedPoints` instance variable to `CourseInstanceBaseMixin`, but `CachedPoints` crashes when it is initialized with the `GraderUser`. `CachedPoints` does not crash with `AnonymousUser`, thus it is used instead of `GraderUser` to work around the crash. In the master branch, the commit 0c0de05 is similar to 0e74906, but there is no crash due to the cache changes in apluslms#1231.
Description
What?
BEHAVIOUR CHANGES:
Why?
How?
Introduces a new cache base class:
New ProxyManager class:
New DBDataManager class:
Fixes a part of #888
Testing
What type of test did you run?
Accessed most of the major pages while debugging,
Did you test the changes in
Translation
Programming style
Have you updated the README or other relevant documentation?
Is it Done?