From 4c3fd769041d8d2fee93676510861b10029a20cd Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Thu, 18 Feb 2021 12:36:01 -0500 Subject: [PATCH] fix: add index to courseware_studentmodule for report performance. = IMPORTANT WARNING = This can be a VERY EXPENSIVE MIGRATION which may take hours or days to run depending on the size of the courseware_studentmodule table on your site. Depending on your database, it may also lock this table, causing courseware to be non-functional during that time. If you want to run this migration manually in a more controlled way (separate from your release pipeline), the SQL needed is: CREATE INDEX `courseware_stats` ON `courseware_studentmodule` (`module_id`, `grade`, `student_id`); You can then fake the migration: https://docs.djangoproject.com/en/2.2/ref/django-admin/#cmdoption-migrate-fake = Motivation and Background = TLDR: This adds an index that will speed up reports like the Problem Grade Report. This fixes a performance regression that was unintentionally introduced in 25da206c. I'm capturing the entire saga below, in case Open edX operators need to dig into it. The tale begins in November of 2012 (yes, seriously). We had an inline analytics feature that would display a histogram to course staff by each problem in the LMS, detailing how students did on that problem (e.g. 80% got 2 points, 10% got 1 point, 10% got 0 points). The courseware_studentmodule table already had an index on the module_id (a.k.a. module_state_key), but because there were 100K+ students that had student state for some problems, the generation of those histograms was still extremely expensive. During U.S. Thanksgiving weekend in late November of 2012, that load started causing operational failures on edx.org. As an emergency measure, I manually added a composite index for (module_id, grade, student_id) on courseware_studentmodule in order to stabilize the courseware on edx.org. I did _not_ follow up properly and add it in a migration file. Later on, the inline analytics feature was removed entirely, so the index was considered redundant (but again, it was not properly cleaned up). Various reports were created over the years, some of which relied on having an index for module_id. These ran fine because there had long been an index for that field specifically. In 2018, the courseware_studentmodule table for edx.org ran into the 2 TB size limit that our old RDS instance had. We had a fair amount of monitoring for various limits that we thought we might run into, but the per-table limit took us by surprise. The Devops/ SRE person fielding that issue needed to free up space in a hurry in order to make the courseware functional again. Examining the database itself, he noticed that we had a module_id index that was technically redundant because the composite index of (module_id, grade, student_id) would cover queries that would otherwise use it. Again, as an emergency measure, he dropped the index on module_id in order to free up a little space and buy enough time to do a proper move of the database to Aurora. Devops-of-2018 being more disciplined than me-of-2012, the index on module_id was removed in 25da206c. The intention was to make it so that the state of the code would match what was live on edx.org. But because the composite index was added in an ad hoc way, what that really meant was that now queries involving module_id were _only_ indexed by the (module_id, grade, student_id) composite index that existed only on edx.org and no other Open edX instances. We didn't realize this issue until months later. @blarghmatey created an index to re-add the index for module_id: https://github.com/edx/edx-platform/pull/20885 The reason why we didn't accept this immediately is because migrations for this table are very operationally risky and take days to run. Faking this migration would have put edx.org even more out of sync with the Open edX repo. Complicating this somewhat was the fact that some folks still seem to be running a variant of the inline analytics on their fork. So in the end, we're going forward with this migration that brings the code fully into sync with indexes on edx.org and covers the obscure inline analytics histogram use case, while still covering the module_id index needed for the fast generation of certain reports that focus on a single problem. Sorry folks. --- .../0015_add_courseware_stats_index.py | 17 +++++++++++++++++ lms/djangoapps/courseware/models.py | 3 +++ 2 files changed, 20 insertions(+) create mode 100644 lms/djangoapps/courseware/migrations/0015_add_courseware_stats_index.py diff --git a/lms/djangoapps/courseware/migrations/0015_add_courseware_stats_index.py b/lms/djangoapps/courseware/migrations/0015_add_courseware_stats_index.py new file mode 100644 index 0000000000..f92418c9a9 --- /dev/null +++ b/lms/djangoapps/courseware/migrations/0015_add_courseware_stats_index.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.18 on 2021-02-18 17:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('courseware', '0014_fix_nan_value_for_global_speed'), + ] + + operations = [ + migrations.AddIndex( + model_name='studentmodule', + index=models.Index(fields=['module_state_key', 'grade', 'student'], name='courseware_stats'), + ), + ] diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py index 714d2f2afc..8d2087b137 100644 --- a/lms/djangoapps/courseware/models.py +++ b/lms/djangoapps/courseware/models.py @@ -103,6 +103,9 @@ class StudentModule(models.Model): class Meta(object): app_label = "courseware" unique_together = (('student', 'module_state_key', 'course_id'),) + indexes = [ + models.Index(fields=['module_state_key', 'grade', 'student'], name="courseware_stats") + ] # Internal state of the object state = models.TextField(null=True, blank=True)