diff --git a/cms/envs/aws_migrate.py b/cms/envs/aws_migrate.py
index e14834ec2d1e2335036c2f6a7c04a987c49ca71a..92b2d51c9e9d3e66ab13a7db602d7220a34ef30b 100644
--- a/cms/envs/aws_migrate.py
+++ b/cms/envs/aws_migrate.py
@@ -26,5 +26,5 @@ if DB_OVERRIDES['PASSWORD'] is None:
     raise ImproperlyConfigured("No database password was provided for running "
                                "migrations.  This is fatal.")
 
-for override, value in DB_OVERRIDES.iteritems():
-    DATABASES['default'][override] = value
+DATABASES['default'].update(DB_OVERRIDES)
+DATABASES['student_module_history'].update(DB_OVERRIDES)
diff --git a/cms/envs/common.py b/cms/envs/common.py
index bb99b01a984d5dcce145d8a32910a3f8665d6b62..40dd6c5ac0fc7ff667fff795f6c7c069c87478c3 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -1114,6 +1114,11 @@ PROCTORING_BACKEND_PROVIDER = {
 }
 PROCTORING_SETTINGS = {}
 
+############################ Global Database Configuration #####################
+
+DATABASE_ROUTERS = [
+    'openedx.core.lib.django_courseware_routers.StudentModuleHistoryExtendedRouter',
+]
 
 ############################ OAUTH2 Provider ###################################
 
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py
index 5ed4ce201e1fa8141f28e54d1d8a771cbb6f4745..8120590122a0da6e1924ebae7de9e9b27f1fa0ba 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py
@@ -265,6 +265,8 @@ class SharedModuleStoreTestCase(TestCase):
     for Django ORM models that will get cleaned up properly.
     """
     MODULESTORE = mixed_store_config(mkdtemp_clean(), {}, include_xml=False)
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
 
     @classmethod
     def setUpClass(cls):
@@ -392,6 +394,8 @@ class ModuleStoreTestCase(TestCase):
     """
 
     MODULESTORE = mixed_store_config(mkdtemp_clean(), {}, include_xml=False)
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
 
     def setUp(self, **kwargs):
         """
diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py
index ffad914bee5cc6d55574ab1ca80fd1adc2b55c9d..f8c58ff6c53aeb7715e3b4d935f2a14033b2cd47 100644
--- a/lms/djangoapps/ccx/tests/test_field_override_performance.py
+++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py
@@ -46,6 +46,8 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
     providers.
     """
     __test__ = False
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
 
     # TEST_DATA must be overridden by subclasses
     TEST_DATA = None
@@ -227,24 +229,24 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
         #     # of mongo queries,
         #     # of xblocks
         # )
-        ('no_overrides', 1, True, False): (23, 1, 6, 13),
-        ('no_overrides', 2, True, False): (53, 16, 6, 84),
-        ('no_overrides', 3, True, False): (183, 81, 6, 335),
-        ('ccx', 1, True, False): (23, 1, 6, 13),
-        ('ccx', 2, True, False): (53, 16, 6, 84),
-        ('ccx', 3, True, False): (183, 81, 6, 335),
-        ('ccx', 1, True, True): (23, 1, 6, 13),
-        ('ccx', 2, True, True): (53, 16, 6, 84),
-        ('ccx', 3, True, True): (183, 81, 6, 335),
-        ('no_overrides', 1, False, False): (23, 1, 6, 13),
-        ('no_overrides', 2, False, False): (53, 16, 6, 84),
-        ('no_overrides', 3, False, False): (183, 81, 6, 335),
-        ('ccx', 1, False, False): (23, 1, 6, 13),
-        ('ccx', 2, False, False): (53, 16, 6, 84),
-        ('ccx', 3, False, False): (183, 81, 6, 335),
-        ('ccx', 1, False, True): (23, 1, 6, 13),
-        ('ccx', 2, False, True): (53, 16, 6, 84),
-        ('ccx', 3, False, True): (183, 81, 6, 335),
+        ('no_overrides', 1, True, False): (47, 1, 6, 13),
+        ('no_overrides', 2, True, False): (119, 16, 6, 84),
+        ('no_overrides', 3, True, False): (399, 81, 6, 335),
+        ('ccx', 1, True, False): (47, 1, 6, 13),
+        ('ccx', 2, True, False): (119, 16, 6, 84),
+        ('ccx', 3, True, False): (399, 81, 6, 335),
+        ('ccx', 1, True, True): (47, 1, 6, 13),
+        ('ccx', 2, True, True): (119, 16, 6, 84),
+        ('ccx', 3, True, True): (399, 81, 6, 335),
+        ('no_overrides', 1, False, False): (47, 1, 6, 13),
+        ('no_overrides', 2, False, False): (119, 16, 6, 84),
+        ('no_overrides', 3, False, False): (399, 81, 6, 335),
+        ('ccx', 1, False, False): (47, 1, 6, 13),
+        ('ccx', 2, False, False): (119, 16, 6, 84),
+        ('ccx', 3, False, False): (399, 81, 6, 335),
+        ('ccx', 1, False, True): (47, 1, 6, 13),
+        ('ccx', 2, False, True): (119, 16, 6, 84),
+        ('ccx', 3, False, True): (399, 81, 6, 335),
     }
 
 
@@ -256,22 +258,22 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
     __test__ = True
 
     TEST_DATA = {
-        ('no_overrides', 1, True, False): (23, 1, 4, 9),
-        ('no_overrides', 2, True, False): (53, 16, 19, 54),
-        ('no_overrides', 3, True, False): (183, 81, 84, 215),
-        ('ccx', 1, True, False): (23, 1, 4, 9),
-        ('ccx', 2, True, False): (53, 16, 19, 54),
-        ('ccx', 3, True, False): (183, 81, 84, 215),
-        ('ccx', 1, True, True): (25, 1, 4, 13),
-        ('ccx', 2, True, True): (55, 16, 19, 84),
-        ('ccx', 3, True, True): (185, 81, 84, 335),
-        ('no_overrides', 1, False, False): (23, 1, 4, 9),
-        ('no_overrides', 2, False, False): (53, 16, 19, 54),
-        ('no_overrides', 3, False, False): (183, 81, 84, 215),
-        ('ccx', 1, False, False): (23, 1, 4, 9),
-        ('ccx', 2, False, False): (53, 16, 19, 54),
-        ('ccx', 3, False, False): (183, 81, 84, 215),
-        ('ccx', 1, False, True): (23, 1, 4, 9),
-        ('ccx', 2, False, True): (53, 16, 19, 54),
-        ('ccx', 3, False, True): (183, 81, 84, 215),
+        ('no_overrides', 1, True, False): (47, 1, 4, 9),
+        ('no_overrides', 2, True, False): (119, 16, 19, 54),
+        ('no_overrides', 3, True, False): (399, 81, 84, 215),
+        ('ccx', 1, True, False): (47, 1, 4, 9),
+        ('ccx', 2, True, False): (119, 16, 19, 54),
+        ('ccx', 3, True, False): (399, 81, 84, 215),
+        ('ccx', 1, True, True): (49, 1, 4, 13),
+        ('ccx', 2, True, True): (121, 16, 19, 84),
+        ('ccx', 3, True, True): (401, 81, 84, 335),
+        ('no_overrides', 1, False, False): (47, 1, 4, 9),
+        ('no_overrides', 2, False, False): (119, 16, 19, 54),
+        ('no_overrides', 3, False, False): (399, 81, 84, 215),
+        ('ccx', 1, False, False): (47, 1, 4, 9),
+        ('ccx', 2, False, False): (119, 16, 19, 54),
+        ('ccx', 3, False, False): (399, 81, 84, 215),
+        ('ccx', 1, False, True): (47, 1, 4, 9),
+        ('ccx', 2, False, True): (119, 16, 19, 54),
+        ('ccx', 3, False, True): (399, 81, 84, 215),
     }
diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py
index c67bb1fd16cc1147505b99347e502012f2894117..b6581f2817e5bf418dd681e301fee162d2441886 100644
--- a/lms/djangoapps/courseware/models.py
+++ b/lms/djangoapps/courseware/models.py
@@ -24,9 +24,9 @@ from django.dispatch import receiver, Signal
 from model_utils.models import TimeStampedModel
 from student.models import user_by_anonymous_id
 from submissions.models import score_set, score_reset
+import coursewarehistoryextended
 
 from xmodule_django.models import CourseKeyField, LocationKeyField, BlockTypeKeyField
-from courseware.fields import UnsignedBigIntAutoField
 
 log = logging.getLogger("edx.courseware")
 
@@ -149,18 +149,15 @@ class StudentModule(models.Model):
         return unicode(repr(self))
 
 
-class StudentModuleHistory(models.Model):
-    """Keeps a complete history of state changes for a given XModule for a given
-    Student. Right now, we restrict this to problems so that the table doesn't
-    explode in size."""
+class BaseStudentModuleHistory(models.Model):
+    """Abstract class containing most fields used by any class
+    storing Student Module History"""
     objects = ChunkingManager()
     HISTORY_SAVING_TYPES = {'problem'}
 
     class Meta(object):
-        app_label = "courseware"
-        get_latest_by = "created"
+        abstract = True
 
-    student_module = models.ForeignKey(StudentModule, db_index=True)
     version = models.CharField(max_length=255, null=True, blank=True, db_index=True)
 
     # This should be populated from the modified field in StudentModule
@@ -169,59 +166,63 @@ class StudentModuleHistory(models.Model):
     grade = models.FloatField(null=True, blank=True)
     max_grade = models.FloatField(null=True, blank=True)
 
-    @receiver(post_save, sender=StudentModule)
-    def save_history(sender, instance, **kwargs):  # pylint: disable=no-self-argument, unused-argument
+    @property
+    def csm(self):
         """
-        Checks the instance's module_type, and creates & saves a
-        StudentModuleHistory entry if the module_type is one that
-        we save.
+        Finds the StudentModule object for this history record, even if our data is split
+        across multiple data stores.  Django does not handle this correctly with the built-in
+        student_module property.
         """
-        if instance.module_type in StudentModuleHistory.HISTORY_SAVING_TYPES:
-            history_entry = StudentModuleHistory(student_module=instance,
-                                                 version=None,
-                                                 created=instance.modified,
-                                                 state=instance.state,
-                                                 grade=instance.grade,
-                                                 max_grade=instance.max_grade)
-            history_entry.save()
+        return StudentModule.objects.get(pk=self.student_module_id)
 
-    def __unicode__(self):
-        return unicode(repr(self))
+    @staticmethod
+    def get_history(student_modules):
+        """
+        Find history objects across multiple backend stores for a given StudentModule
+        """
+
+        history_entries = []
 
-class StudentModuleHistoryExtended(models.Model):
+        if settings.FEATURES.get('ENABLE_CSMH_EXTENDED'):
+            history_entries += coursewarehistoryextended.models.StudentModuleHistoryExtended.objects.filter(
+                # Django will sometimes try to join to courseware_studentmodule
+                # so just do an in query
+                student_module__in=[module.id for module in student_modules]
+            ).order_by('-id')
+
+        # If we turn off reading from multiple history tables, then we don't want to read from
+        # StudentModuleHistory anymore, we believe that all history is in the Extended table.
+        if settings.FEATURES.get('ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES'):
+            # we want to save later SQL queries on the model which allows us to prefetch
+            history_entries += StudentModuleHistory.objects.prefetch_related('student_module').filter(
+                student_module__in=student_modules
+            ).order_by('-id')
+
+        return history_entries
+
+
+class StudentModuleHistory(BaseStudentModuleHistory):
     """Keeps a complete history of state changes for a given XModule for a given
     Student. Right now, we restrict this to problems so that the table doesn't
-    explode in size.
-
-    This new extended CSMH has a larger primary key that won't run out of space
-    so quickly."""
-    objects = ChunkingManager()
-    HISTORY_SAVING_TYPES = {'problem'}
+    explode in size."""
 
     class Meta(object):
         app_label = "courseware"
         get_latest_by = "created"
 
-    id = UnsignedBigIntAutoField(primary_key=True)  # pylint: disable=invalid-name
-
-    student_module = models.ForeignKey(StudentModule, db_index=True, db_constraint=False)
-    version = models.CharField(max_length=255, null=True, blank=True, db_index=True)
+    student_module = models.ForeignKey(StudentModule, db_index=True)
 
-    # This should be populated from the modified field in StudentModule
-    created = models.DateTimeField(db_index=True)
-    state = models.TextField(null=True, blank=True)
-    grade = models.FloatField(null=True, blank=True)
-    max_grade = models.FloatField(null=True, blank=True)
+    def __unicode__(self):
+        return unicode(repr(self))
 
-    @receiver(post_save, sender=StudentModule)
     def save_history(sender, instance, **kwargs):  # pylint: disable=no-self-argument, unused-argument
         """
         Checks the instance's module_type, and creates & saves a
-        StudentModuleHistory entry if the module_type is one that
+        StudentModuleHistoryExtended entry if the module_type is one that
         we save.
         """
-        if instance.module_type in StudentModuleHistoryExtended.HISTORY_SAVING_TYPES:
-            history_entry = StudentModuleHistoryExtended(student_module=instance,
+        if instance.module_type in StudentModuleHistory.HISTORY_SAVING_TYPES:
+            history_entry = StudentModuleHistory(student_module=instance,
                                                  version=None,
                                                  created=instance.modified,
                                                  state=instance.state,
@@ -229,8 +230,11 @@ class StudentModuleHistoryExtended(models.Model):
                                                  max_grade=instance.max_grade)
             history_entry.save()
 
-    def __unicode__(self):
-        return unicode(repr(self))
+    # When the extended studentmodulehistory table exists, don't save
+    # duplicate history into courseware_studentmodulehistory, just retain
+    # data for reading.
+    if not settings.FEATURES.get('ENABLE_CSMH_EXTENDED'):
+        post_save.connect(save_history, sender=StudentModule)
 
 
 class XBlockFieldBase(models.Model):
diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py
index 67566fd3f111e4fa5fcf2c0b419e45fc02a4b237..add8c738ee740854b15f707d54655e3f1c92ea8d 100644
--- a/lms/djangoapps/courseware/tests/test_access.py
+++ b/lms/djangoapps/courseware/tests/test_access.py
@@ -520,6 +520,7 @@ class UserRoleTestCase(TestCase):
     """
     Tests for user roles.
     """
+
     def setUp(self):
         super(UserRoleTestCase, self).setUp()
         self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
diff --git a/lms/djangoapps/courseware/tests/test_grades.py b/lms/djangoapps/courseware/tests/test_grades.py
index 84ef2498f644785aec18053dbaba27cac6eb3383..5fce95728465a0b904937ccae50aa7c192ad39eb 100644
--- a/lms/djangoapps/courseware/tests/test_grades.py
+++ b/lms/djangoapps/courseware/tests/test_grades.py
@@ -240,6 +240,9 @@ class TestProgressSummary(TestCase):
                    (2/5) (3/5) (0/1)   -   (1/3)   -   (3/10)
 
     """
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
+
     def setUp(self):
         super(TestProgressSummary, self).setUp()
         self.course_key = CourseLocator(
diff --git a/lms/djangoapps/courseware/tests/test_i18n.py b/lms/djangoapps/courseware/tests/test_i18n.py
index e5ae1b6f194f7bb15d25e9412b1b0c678f7e5999..345dc21ddacc26a9f71d8c8bfcbc45c7860157c5 100644
--- a/lms/djangoapps/courseware/tests/test_i18n.py
+++ b/lms/djangoapps/courseware/tests/test_i18n.py
@@ -20,6 +20,7 @@ class BaseI18nTestCase(TestCase):
     """
     Base utilities for i18n test classes to derive from
     """
+
     def assert_tag_has_attr(self, content, tag, attname, value):
         """Assert that a tag in `content` has a certain value in a certain attribute."""
         regex = r"""<{tag} [^>]*\b{attname}=['"]([\w\d\- ]+)['"][^>]*>""".format(tag=tag, attname=attname)
diff --git a/lms/djangoapps/courseware/tests/test_model_data.py b/lms/djangoapps/courseware/tests/test_model_data.py
index b80f05d2a12612a5d2d74a02da87e1abaff4c47c..9f62de0875fde6d885edb1d96c09d96fbad2744a 100644
--- a/lms/djangoapps/courseware/tests/test_model_data.py
+++ b/lms/djangoapps/courseware/tests/test_model_data.py
@@ -103,6 +103,8 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
     """Tests for user_state storage via StudentModule"""
     other_key_factory = partial(DjangoKeyValueStore.Key, Scope.user_state, 2, location('usage_id'))  # user_id=2, not 1
     existing_field_name = "a_field"
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
 
     def setUp(self):
         super(TestStudentModuleStorage, self).setUp()
@@ -137,8 +139,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
         # to discover if something other than the DjangoXBlockUserStateClient
         # has written to the StudentModule (such as UserStateCache setting the score
         # on the StudentModule).
-        with self.assertNumQueries(3):
-            self.kvs.set(user_state_key('a_field'), 'new_value')
+        with self.assertNumQueries(2, using='default'):
+            with self.assertNumQueries(1, using='student_module_history'):
+                self.kvs.set(user_state_key('a_field'), 'new_value')
         self.assertEquals(1, StudentModule.objects.all().count())
         self.assertEquals({'b_field': 'b_value', 'a_field': 'new_value'}, json.loads(StudentModule.objects.all()[0].state))
 
@@ -149,8 +152,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
         # to discover if something other than the DjangoXBlockUserStateClient
         # has written to the StudentModule (such as UserStateCache setting the score
         # on the StudentModule).
-        with self.assertNumQueries(3):
-            self.kvs.set(user_state_key('not_a_field'), 'new_value')
+        with self.assertNumQueries(2, using='default'):
+            with self.assertNumQueries(1, using='student_module_history'):
+                self.kvs.set(user_state_key('not_a_field'), 'new_value')
         self.assertEquals(1, StudentModule.objects.all().count())
         self.assertEquals({'b_field': 'b_value', 'a_field': 'a_value', 'not_a_field': 'new_value'}, json.loads(StudentModule.objects.all()[0].state))
 
@@ -161,8 +165,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
         # to discover if something other than the DjangoXBlockUserStateClient
         # has written to the StudentModule (such as UserStateCache setting the score
         # on the StudentModule).
-        with self.assertNumQueries(3):
-            self.kvs.delete(user_state_key('a_field'))
+        with self.assertNumQueries(2, using='default'):
+            with self.assertNumQueries(1, using='student_module_history'):
+                self.kvs.delete(user_state_key('a_field'))
         self.assertEquals(1, StudentModule.objects.all().count())
         self.assertRaises(KeyError, self.kvs.get, user_state_key('not_a_field'))
 
@@ -201,8 +206,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
         # We also need to read the database to discover if something other than the
         # DjangoXBlockUserStateClient has written to the StudentModule (such as
         # UserStateCache setting the score on the StudentModule).
-        with self.assertNumQueries(3):
-            self.kvs.set_many(kv_dict)
+        with self.assertNumQueries(2, using="default"):
+            with self.assertNumQueries(1, using="student_module_history"):
+                self.kvs.set_many(kv_dict)
 
         for key in kv_dict:
             self.assertEquals(self.kvs.get(key), kv_dict[key])
@@ -223,6 +229,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
 
 @attr('shard_1')
 class TestMissingStudentModule(TestCase):
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
+
     def setUp(self):
         super(TestMissingStudentModule, self).setUp()
 
@@ -244,12 +253,13 @@ class TestMissingStudentModule(TestCase):
         self.assertEquals(0, len(self.field_data_cache))
         self.assertEquals(0, StudentModule.objects.all().count())
 
-        # We are updating a problem, so we write to courseware_studentmodulehistory
+        # We are updating a problem, so we write to courseware_studentmodulehistoryextended
         # as well as courseware_studentmodule. We also need to read the database
         # to discover if something other than the DjangoXBlockUserStateClient
         # has written to the StudentModule (such as UserStateCache setting the score
         # on the StudentModule).
-        with self.assertNumQueries(2, using='default'):
+        # Django 1.8 also has a number of other BEGIN and SAVESTATE queries.
+        with self.assertNumQueries(4, using='default'):
             with self.assertNumQueries(1, using='student_module_history'):
                 self.kvs.set(user_state_key('a_field'), 'a_value')
 
diff --git a/lms/djangoapps/courseware/tests/test_submitting_problems.py b/lms/djangoapps/courseware/tests/test_submitting_problems.py
index 23a7ae2aa60c736eea3a2c618adccef191ea4779..8a7761a322d2559bd3e5a9be74522c524531a316 100644
--- a/lms/djangoapps/courseware/tests/test_submitting_problems.py
+++ b/lms/djangoapps/courseware/tests/test_submitting_problems.py
@@ -19,7 +19,7 @@ from capa.tests.response_xml_factory import (
     CodeResponseXMLFactory,
 )
 from courseware import grades
-from courseware.models import StudentModule, StudentModuleHistory
+from courseware.models import StudentModule, BaseStudentModuleHistory
 from courseware.tests.helpers import LoginEnrollmentTestCase
 from lms.djangoapps.lms_xblock.runtime import quote_slashes
 from student.tests.factories import UserFactory
@@ -121,6 +121,8 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase, Probl
     Check that a course gets graded properly.
     """
 
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
     # arbitrary constant
     COURSE_SLUG = "100"
     COURSE_NAME = "test_course"
@@ -319,6 +321,9 @@ class TestCourseGrader(TestSubmittingProblems):
     """
     Suite of tests for the course grader.
     """
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
+
     def basic_setup(self, late=False, reset=False, showanswer=False):
         """
         Set up a simple course for testing basic grading functionality.
@@ -451,26 +456,20 @@ class TestCourseGrader(TestSubmittingProblems):
         self.submit_question_answer('p1', {'2_1': u'Correct'})
 
         # Now fetch the state entry for that problem.
-        student_module = StudentModule.objects.get(
+        student_module = StudentModule.objects.filter(
             course_id=self.course.id,
             student=self.student_user
         )
         # count how many state history entries there are
-        baseline = StudentModuleHistory.objects.filter(
-            student_module=student_module
-        )
-        baseline_count = baseline.count()
-        self.assertEqual(baseline_count, 3)
+        baseline = BaseStudentModuleHistory.get_history(student_module)
+        self.assertEqual(len(baseline), 3)
 
         # now click "show answer"
         self.show_question_answer('p1')
 
         # check that we don't have more state history entries
-        csmh = StudentModuleHistory.objects.filter(
-            student_module=student_module
-        )
-        current_count = csmh.count()
-        self.assertEqual(current_count, 3)
+        csmh = BaseStudentModuleHistory.get_history(student_module)
+        self.assertEqual(len(csmh), 3)
 
     def test_grade_with_max_score_cache(self):
         """
@@ -713,6 +712,8 @@ class TestCourseGrader(TestSubmittingProblems):
 @attr('shard_1')
 class ProblemWithUploadedFilesTest(TestSubmittingProblems):
     """Tests of problems with uploaded files."""
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
 
     def setUp(self):
         super(ProblemWithUploadedFilesTest, self).setUp()
@@ -768,6 +769,8 @@ class TestPythonGradedResponse(TestSubmittingProblems):
     """
     Check that we can submit a schematic and custom response, and it answers properly.
     """
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
 
     SCHEMATIC_SCRIPT = dedent("""
         # for a schematic response, submission[i] is the json representation
diff --git a/lms/djangoapps/courseware/tests/test_user_state_client.py b/lms/djangoapps/courseware/tests/test_user_state_client.py
index 5bb9a0682b2f6de0682f0a80e726dfd5fb8fc226..143bd7f644c477388f2e6181b72885bbfa152614 100644
--- a/lms/djangoapps/courseware/tests/test_user_state_client.py
+++ b/lms/djangoapps/courseware/tests/test_user_state_client.py
@@ -18,6 +18,8 @@ class TestDjangoUserStateClient(UserStateClientTestBase, TestCase):
     Tests of the DjangoUserStateClient backend.
     """
     __test__ = True
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
 
     def _user(self, user_idx):
         return self.users[user_idx].username
diff --git a/lms/djangoapps/courseware/user_state_client.py b/lms/djangoapps/courseware/user_state_client.py
index e90eeb8f474cf183b400498f191d9c2945979f72..b965a15ebe09ce59feb31c597222fde02da59d4a 100644
--- a/lms/djangoapps/courseware/user_state_client.py
+++ b/lms/djangoapps/courseware/user_state_client.py
@@ -15,7 +15,7 @@ except ImportError:
 import dogstats_wrapper as dog_stats_api
 from django.contrib.auth.models import User
 from xblock.fields import Scope, ScopeBase
-from courseware.models import StudentModule, StudentModuleHistory
+from courseware.models import StudentModule, BaseStudentModuleHistory
 from edx_user_state_client.interface import XBlockUserStateClient, XBlockUserState
 
 
@@ -312,9 +312,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
         if len(student_modules) == 0:
             raise self.DoesNotExist()
 
-        history_entries = StudentModuleHistory.objects.prefetch_related('student_module').filter(
-            student_module__in=student_modules
-        ).order_by('-id')
+        history_entries = BaseStudentModuleHistory.get_history(student_modules)
 
         # If no history records exist, raise an error
         if not history_entries:
@@ -332,9 +330,9 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
             if state == {}:
                 state = None
 
-            block_key = history_entry.student_module.module_state_key
+            block_key = history_entry.csm.module_state_key
             block_key = block_key.map_into_course(
-                history_entry.student_module.course_id
+                history_entry.csm.course_id
             )
 
             yield XBlockUserState(username, block_key, state, history_entry.created, scope)
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index 73a43345c10f6dd0e8abcde9cf71a804931e5d77..7d4cde32abb8d5d796b867c6fc9d057257fd721d 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -59,7 +59,7 @@ from courseware.courses import (
 )
 from courseware.masquerade import setup_masquerade
 from courseware.model_data import FieldDataCache, ScoresClient
-from courseware.models import StudentModuleHistory
+from courseware.models import StudentModule, BaseStudentModuleHistory
 from courseware.url_helpers import get_redirect_url
 from courseware.user_state_client import DjangoXBlockUserStateClient
 from edxmako.shortcuts import render_to_response, render_to_string, marketing_link
@@ -1173,11 +1173,12 @@ def submission_history(request, course_id, student_username, location):
 
     # This is ugly, but until we have a proper submissions API that we can use to provide
     # the scores instead, it will have to do.
-    scores = list(StudentModuleHistory.objects.filter(
-        student_module__module_state_key=usage_key,
-        student_module__student__username=student_username,
-        student_module__course_id=course_key
-    ).order_by('-id'))
+    csm = StudentModule.objects.filter(
+        module_state_key=usage_key,
+        student__username=student_username,
+        course_id=course_key)
+
+    scores = BaseStudentModuleHistory.get_history(csm)
 
     if len(scores) != len(history_entries):
         log.warning(
diff --git a/lms/djangoapps/coursewarehistoryextended/__init__.py b/lms/djangoapps/coursewarehistoryextended/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lms/djangoapps/courseware/fields.py b/lms/djangoapps/coursewarehistoryextended/fields.py
similarity index 71%
rename from lms/djangoapps/courseware/fields.py
rename to lms/djangoapps/coursewarehistoryextended/fields.py
index 6e34c4e635f756a45df43d482a7bf285628a3854..a001543d31975bc154329fec78b856f6d0718efd 100644
--- a/lms/djangoapps/courseware/fields.py
+++ b/lms/djangoapps/coursewarehistoryextended/fields.py
@@ -1,5 +1,5 @@
 """
-Custom fields for use in the courseware django app.
+Custom fields for use in the coursewarehistoryextended django app.
 """
 
 from django.db.models.fields import AutoField
@@ -17,5 +17,9 @@ class UnsignedBigIntAutoField(AutoField):
             # is an alias for that (https://www.sqlite.org/autoinc.html). An unsigned integer
             # isn't an alias for ROWID, so we have to give up on the unsigned part.
             return "integer"
+        elif connection.settings_dict['ENGINE'] == 'django.db.backends.postgresql_psycopg2':
+            # Pg's bigserial is implicitly unsigned (doesn't allow negative numbers) and
+            # goes 1-9.2x10^18
+            return "BIGSERIAL"
         else:
             return None
diff --git a/lms/djangoapps/courseware/migrations/0002_csmh-extended-keyspace.py b/lms/djangoapps/coursewarehistoryextended/migrations/0001_initial.py
similarity index 67%
rename from lms/djangoapps/courseware/migrations/0002_csmh-extended-keyspace.py
rename to lms/djangoapps/coursewarehistoryextended/migrations/0001_initial.py
index 8ee63b6f3b88abd6d094111bcffc1a835ad3a03a..370327f9f2adaae6ab1dee03008e3993304e4a0c 100644
--- a/lms/djangoapps/courseware/migrations/0002_csmh-extended-keyspace.py
+++ b/lms/djangoapps/coursewarehistoryextended/migrations/0001_initial.py
@@ -2,18 +2,21 @@
 from __future__ import unicode_literals
 
 from django.db import migrations, models
-import courseware.fields
 from django.conf import settings
-
+import django.db.models.deletion
+import coursewarehistoryextended.fields
 
 def bump_pk_start(apps, schema_editor):
     if not schema_editor.connection.alias == 'student_module_history':
                 return
     StudentModuleHistory = apps.get_model("courseware", "StudentModuleHistory")
-    initial_id = settings.STUDENTMODULEHISTORYEXTENDED_OFFSET + StudentModuleHistory.objects.all().latest('id').id
+    biggest_id = StudentModuleHistory.objects.all().order_by('-id').first()
+    initial_id = settings.STUDENTMODULEHISTORYEXTENDED_OFFSET
+    if biggest_id is not None:
+        initial_id += biggest_id.id
 
     if schema_editor.connection.vendor == 'mysql':
-        schema_editor.execute('ALTER TABLE courseware_studentmodulehistoryextended AUTO_INCREMENT=%s', [initial_id])
+        schema_editor.execute('ALTER TABLE coursewarehistoryextended_studentmodulehistoryextended AUTO_INCREMENT=%s', [initial_id])
     elif schema_editor.connection.vendor == 'sqlite3':
         # This is a hack to force sqlite to add new rows after the earlier rows we
         # want to migrate.
@@ -25,7 +28,8 @@ def bump_pk_start(apps, schema_editor):
             version="",
             created=datetime.datetime.now(),
         ).save()
-
+    elif schema_editor.connection.vendor == 'postgresql':
+        schema_editor.execute("SELECT setval('coursewarehistoryextended_studentmodulehistoryextended_seq', %s)", [initial_id])
 
 class Migration(migrations.Migration):
 
@@ -37,13 +41,13 @@ class Migration(migrations.Migration):
         migrations.CreateModel(
             name='StudentModuleHistoryExtended',
             fields=[
-                ('id', courseware.fields.UnsignedBigIntAutoField(serialize=False, primary_key=True)),
                 ('version', models.CharField(db_index=True, max_length=255, null=True, blank=True)),
                 ('created', models.DateTimeField(db_index=True)),
                 ('state', models.TextField(null=True, blank=True)),
                 ('grade', models.FloatField(null=True, blank=True)),
                 ('max_grade', models.FloatField(null=True, blank=True)),
-                ('student_module', models.ForeignKey(to='courseware.StudentModule', db_constraint=False)),
+                ('id', coursewarehistoryextended.fields.UnsignedBigIntAutoField(serialize=False, primary_key=True)),
+                ('student_module', models.ForeignKey(to='courseware.StudentModule', on_delete=django.db.models.deletion.DO_NOTHING, db_constraint=False)),
             ],
             options={
                 'get_latest_by': 'created',
diff --git a/lms/djangoapps/coursewarehistoryextended/migrations/__init__.py b/lms/djangoapps/coursewarehistoryextended/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lms/djangoapps/coursewarehistoryextended/models.py b/lms/djangoapps/coursewarehistoryextended/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c3630b0e3d9dd0704e738b98053a1c07e629ceb
--- /dev/null
+++ b/lms/djangoapps/coursewarehistoryextended/models.py
@@ -0,0 +1,64 @@
+"""
+WE'RE USING MIGRATIONS!
+
+If you make changes to this model, be sure to create an appropriate migration
+file and check it in at the same time as your model changes. To do that,
+
+1. Go to the edx-platform dir
+2. ./manage.py schemamigration courseware --auto description_of_your_change
+3. Add the migration file created in edx-platform/lms/djangoapps/coursewarehistoryextended/migrations/
+
+
+ASSUMPTIONS: modules have unique IDs, even across different module_types
+
+"""
+from django.db import models
+from django.db.models.signals import post_save, post_delete
+from django.dispatch import receiver
+
+from coursewarehistoryextended.fields import UnsignedBigIntAutoField
+from courseware.models import StudentModule, BaseStudentModuleHistory
+
+
+class StudentModuleHistoryExtended(BaseStudentModuleHistory):
+    """Keeps a complete history of state changes for a given XModule for a given
+    Student. Right now, we restrict this to problems so that the table doesn't
+    explode in size.
+
+    This new extended CSMH has a larger primary key that won't run out of space
+    so quickly."""
+
+    class Meta(object):
+        app_label = 'coursewarehistoryextended'
+        get_latest_by = "created"
+
+    id = UnsignedBigIntAutoField(primary_key=True)  # pylint: disable=invalid-name
+
+    student_module = models.ForeignKey(StudentModule, db_index=True, db_constraint=False, on_delete=models.DO_NOTHING)
+
+    @receiver(post_save, sender=StudentModule)
+    def save_history(sender, instance, **kwargs):  # pylint: disable=no-self-argument, unused-argument
+        """
+        Checks the instance's module_type, and creates & saves a
+        StudentModuleHistoryExtended entry if the module_type is one that
+        we save.
+        """
+        if instance.module_type in StudentModuleHistoryExtended.HISTORY_SAVING_TYPES:
+            history_entry = StudentModuleHistoryExtended(student_module=instance,
+                                                         version=None,
+                                                         created=instance.modified,
+                                                         state=instance.state,
+                                                         grade=instance.grade,
+                                                         max_grade=instance.max_grade)
+            history_entry.save()
+
+    @receiver(post_delete, sender=StudentModule)
+    def delete_history(sender, instance, **kwargs):  # pylint: disable=no-self-argument, unused-argument
+        """
+        Django can't cascade delete across databases, so we tell it at the model level to
+        on_delete=DO_NOTHING and then listen for post_delete so we can clean up the CSMHE rows.
+        """
+        StudentModuleHistoryExtended.objects.filter(student_module=instance).all().delete()
+
+    def __unicode__(self):
+        return unicode(repr(self))
diff --git a/lms/djangoapps/coursewarehistoryextended/tests.py b/lms/djangoapps/coursewarehistoryextended/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..759fdfe4be24f8739f1e65a792bbe60d72d119d2
--- /dev/null
+++ b/lms/djangoapps/coursewarehistoryextended/tests.py
@@ -0,0 +1,81 @@
+"""
+Tests for coursewarehistoryextended
+Many aspects of this app are covered by the courseware tests,
+but these are specific to the new storage model with multiple
+backend tables.
+"""
+
+import json
+from mock import patch
+from django.test import TestCase
+from django.conf import settings
+from unittest import skipUnless
+from nose.plugins.attrib import attr
+
+from courseware.models import BaseStudentModuleHistory, StudentModuleHistory, StudentModule
+
+from courseware.tests.factories import StudentModuleFactory, location, course_id
+
+
+@attr('shard_1')
+@skipUnless(settings.FEATURES["ENABLE_CSMH_EXTENDED"], "CSMH Extended needs to be enabled")
+class TestStudentModuleHistoryBackends(TestCase):
+    """ Tests of data in CSMH and CSMHE """
+    # Tell Django to clean out all databases, not just default
+    multi_db = True
+
+    def setUp(self):
+        super(TestStudentModuleHistoryBackends, self).setUp()
+        for record in (1, 2, 3):
+            # This will store into CSMHE via the post_save signal
+            csm = StudentModuleFactory.create(module_state_key=location('usage_id'),
+                                              course_id=course_id,
+                                              state=json.dumps({'type': 'csmhe', 'order': record}))
+            # This manually gets us a CSMH record to compare
+            csmh = StudentModuleHistory(student_module=csm,
+                                        version=None,
+                                        created=csm.modified,
+                                        state=json.dumps({'type': 'csmh', 'order': record}),
+                                        grade=csm.grade,
+                                        max_grade=csm.max_grade)
+            csmh.save()
+
+    @patch.dict("django.conf.settings.FEATURES", {"ENABLE_CSMH_EXTENDED": True})
+    @patch.dict("django.conf.settings.FEATURES", {"ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES": True})
+    def test_get_history_true_true(self):
+        student_module = StudentModule.objects.all()
+        history = BaseStudentModuleHistory.get_history(student_module)
+        self.assertEquals(len(history), 6)
+        self.assertEquals({'type': 'csmhe', 'order': 3}, json.loads(history[0].state))
+        self.assertEquals({'type': 'csmhe', 'order': 2}, json.loads(history[1].state))
+        self.assertEquals({'type': 'csmhe', 'order': 1}, json.loads(history[2].state))
+        self.assertEquals({'type': 'csmh', 'order': 3}, json.loads(history[3].state))
+        self.assertEquals({'type': 'csmh', 'order': 2}, json.loads(history[4].state))
+        self.assertEquals({'type': 'csmh', 'order': 1}, json.loads(history[5].state))
+
+    @patch.dict("django.conf.settings.FEATURES", {"ENABLE_CSMH_EXTENDED": True})
+    @patch.dict("django.conf.settings.FEATURES", {"ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES": False})
+    def test_get_history_true_false(self):
+        student_module = StudentModule.objects.all()
+        history = BaseStudentModuleHistory.get_history(student_module)
+        self.assertEquals(len(history), 3)
+        self.assertEquals({'type': 'csmhe', 'order': 3}, json.loads(history[0].state))
+        self.assertEquals({'type': 'csmhe', 'order': 2}, json.loads(history[1].state))
+        self.assertEquals({'type': 'csmhe', 'order': 1}, json.loads(history[2].state))
+
+    @patch.dict("django.conf.settings.FEATURES", {"ENABLE_CSMH_EXTENDED": False})
+    @patch.dict("django.conf.settings.FEATURES", {"ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES": True})
+    def test_get_history_false_true(self):
+        student_module = StudentModule.objects.all()
+        history = BaseStudentModuleHistory.get_history(student_module)
+        self.assertEquals(len(history), 3)
+        self.assertEquals({'type': 'csmh', 'order': 3}, json.loads(history[0].state))
+        self.assertEquals({'type': 'csmh', 'order': 2}, json.loads(history[1].state))
+        self.assertEquals({'type': 'csmh', 'order': 1}, json.loads(history[2].state))
+
+    @patch.dict("django.conf.settings.FEATURES", {"ENABLE_CSMH_EXTENDED": False})
+    @patch.dict("django.conf.settings.FEATURES", {"ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES": False})
+    def test_get_history_false_false(self):
+        student_module = StudentModule.objects.all()
+        history = BaseStudentModuleHistory.get_history(student_module)
+        self.assertEquals(len(history), 0)
diff --git a/lms/envs/acceptance.py b/lms/envs/acceptance.py
index 41c64afe56d3e1f921f8181b2221bf516018e3c4..101e7bb7ecb8b2ca5909f52ed539f37c555c44ae 100644
--- a/lms/envs/acceptance.py
+++ b/lms/envs/acceptance.py
@@ -153,7 +153,9 @@ LETTUCE_APPS = ('courseware', 'instructor')
 # This causes some pretty cryptic errors as lettuce tries
 # to parse files in `instructor_task` as features.
 # As a quick workaround, explicitly exclude the `instructor_task` app.
-LETTUCE_AVOID_APPS = ('instructor_task',)
+# The coursewarehistoryextended app also falls prey to this fuzzy
+# for the courseware app.
+LETTUCE_AVOID_APPS = ('instructor_task', 'coursewarehistoryextended')
 
 LETTUCE_BROWSER = os.environ.get('LETTUCE_BROWSER', 'chrome')
 
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index 6f593d139c0ba60b826cd3e40fe51fe26a9463ca..d5935394af0d2ea26c02d48639ed9d59703c2a4f 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -771,3 +771,7 @@ if ENV_TOKENS.get('AUDIT_CERT_CUTOFF_DATE', None):
 ################################ Settings for Credentials Service ################################
 
 CREDENTIALS_GENERATION_ROUTING_KEY = HIGH_PRIORITY_QUEUE
+
+# The extended StudentModule history table
+if FEATURES.get('ENABLE_CSMH_EXTENDED'):
+    INSTALLED_APPS += ('coursewarehistoryextended',)
diff --git a/lms/envs/bok_choy.py b/lms/envs/bok_choy.py
index 1990473417e3c1c19e4288e5db1ff381dd78ffc2..cb74f9a87f18265ae15445d137cf27394be29f1a 100644
--- a/lms/envs/bok_choy.py
+++ b/lms/envs/bok_choy.py
@@ -178,6 +178,11 @@ PROFILE_IMAGE_BACKEND = {
         'base_url': os.path.join(MEDIA_URL, 'profile-images/'),
     },
 }
+
+# Make sure we test with the extended history table
+FEATURES['ENABLE_CSMH_EXTENDED'] = True
+INSTALLED_APPS += ('coursewarehistoryextended',)
+
 #####################################################################
 # Lastly, see if the developer has any local overrides.
 try:
diff --git a/lms/envs/common.py b/lms/envs/common.py
index fa7cbe68fc61f7f4eff5bb93e304f95e42daf398..c73eaae4517ffd9192f2c7529b13b72f6bf1fc0f 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -363,6 +363,18 @@ FEATURES = {
 
     # Show Language selector.
     'SHOW_LANGUAGE_SELECTOR': False,
+
+    # Write new CSM history to the extended table.
+    # This will eventually default to True and may be
+    # removed since all installs should have the separate
+    # extended history table.
+    'ENABLE_CSMH_EXTENDED': False,
+
+    # Read from both the CSMH and CSMHE history tables.
+    # This is the default, but can be disabled if all history
+    # lives in the Extended table, saving the frontend from
+    # making multiple queries.
+    'ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES': True
 }
 
 # Ignore static asset files on import which match this pattern
@@ -416,7 +428,7 @@ STATUS_MESSAGE_PATH = ENV_ROOT / "status_message.json"
 ############################ Global Database Configuration #####################
 
 DATABASE_ROUTERS = [
-    'courseware.routers.StudentModuleHistoryRouter',
+    'openedx.core.lib.django_courseware_routers.StudentModuleHistoryExtendedRouter',
 ]
 
 ############################ OpenID Provider  ##################################
@@ -2766,7 +2778,10 @@ MOBILE_APP_USER_AGENT_REGEXES = [
 ]
 
 # Offset for courseware.StudentModuleHistoryExtended which is used to
-# calculate the starting primary key for the underlying table.
+# calculate the starting primary key for the underlying table.  This gap
+# should be large enough that you do not generate more than N courseware.StudentModuleHistory
+# records before you have deployed the app to write to coursewarehistoryextended.StudentModuleHistoryExtended
+# if you want to avoid an overlap in ids while searching for history across the two tables.
 STUDENTMODULEHISTORYEXTENDED_OFFSET = 10000
 
 # Deprecated xblock types
diff --git a/lms/envs/test.py b/lms/envs/test.py
index 111de79ca9f2498b50a9e475d9f797c4b4b6cb71..83f55c337ccbf359d082143c99cf79983c243797 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -197,6 +197,10 @@ if os.environ.get('DISABLE_MIGRATIONS'):
     # to Django 1.9, which allows setting MIGRATION_MODULES to None in order to skip migrations.
     MIGRATION_MODULES = NoOpMigrationModules()
 
+# Make sure we test with the extended history table
+FEATURES['ENABLE_CSMH_EXTENDED'] = True
+INSTALLED_APPS += ('coursewarehistoryextended',)
+
 CACHES = {
     # This is the cache used for most things.
     # In staging/prod envs, the sessions also live here.
diff --git a/lms/djangoapps/courseware/routers.py b/openedx/core/lib/django_courseware_routers.py
similarity index 58%
rename from lms/djangoapps/courseware/routers.py
rename to openedx/core/lib/django_courseware_routers.py
index 1514c1025972f3992a9f281ea52aaa1c0b4f0c43..4665efe47f4974acbf9c912d065fe0447370ddd8 100644
--- a/lms/djangoapps/courseware/routers.py
+++ b/openedx/core/lib/django_courseware_routers.py
@@ -1,27 +1,27 @@
 """
-Database Routers for use with the courseware django app.
+Database Routers for use with the coursewarehistoryextended django app.
 """
 
 
-class StudentModuleHistoryRouter(object):
+class StudentModuleHistoryExtendedRouter(object):
     """
-    A Database Router that separates StudentModuleHistory into its own database.
+    A Database Router that separates StudentModuleHistoryExtended into its own database.
     """
 
     DATABASE_NAME = 'student_module_history'
 
     def _is_csmh(self, model):
         """
-        Return True if ``model`` is courseware.StudentModuleHistory.
+        Return True if ``model`` is courseware.StudentModuleHistoryExtended.
         """
         return (
-            model._meta.app_label == 'courseware' and  # pylint: disable=protected-access
-            model.__name__ == 'StudentModuleHistory'
+            model._meta.app_label == 'coursewarehistoryextended' and  # pylint: disable=protected-access
+            model.__name__ == 'StudentModuleHistoryExtended'
         )
 
     def db_for_read(self, model, **hints):  # pylint: disable=unused-argument
         """
-        Use the StudentModuleHistoryRouter.DATABASE_NAME if the model is StudentModuleHistory.
+        Use the StudentModuleHistoryExtendedRouter.DATABASE_NAME if the model is StudentModuleHistoryExtended.
         """
         if self._is_csmh(model):
             return self.DATABASE_NAME
@@ -30,7 +30,7 @@ class StudentModuleHistoryRouter(object):
 
     def db_for_write(self, model, **hints):  # pylint: disable=unused-argument
         """
-        Use the StudentModuleHistoryRouter.DATABASE_NAME if the model is StudentModuleHistory.
+        Use the StudentModuleHistoryExtendedRouter.DATABASE_NAME if the model is StudentModuleHistoryExtended.
         """
         if self._is_csmh(model):
             return self.DATABASE_NAME
@@ -39,7 +39,7 @@ class StudentModuleHistoryRouter(object):
 
     def allow_relation(self, obj1, obj2, **hints):  # pylint: disable=unused-argument
         """
-        Disable relations if the model is StudentModuleHistory.
+        Disable relations if the model is StudentModuleHistoryExtended.
         """
         if self._is_csmh(obj1) or self._is_csmh(obj2):
             return False
@@ -47,7 +47,7 @@ class StudentModuleHistoryRouter(object):
 
     def allow_migrate(self, db, model):  # pylint: disable=unused-argument
         """
-        Only sync StudentModuleHistory to StudentModuleHistoryRouter.DATABASE_Name
+        Only sync StudentModuleHistoryExtended to StudentModuleHistoryExtendedRouter.DATABASE_Name
         """
         if self._is_csmh(model):
             return db == self.DATABASE_NAME