Commit 9b63f0d1 by Renzo Lucioni

Use drf-extensions to cache ProgramViewSet responses

drf-extensions provides a variety of tools we can use to implement response caching. This change is a proof of concept, adding caching with a short 1 minute TTL for the ProgramViewSet's list and retrieve actions. Error responses are not cached. This will provide some protection against significant increases in throughput. We can build on this later to add a longer TTL and invalidation on model changes.

LEARNER-401
parent 60637f67
......@@ -14,9 +14,9 @@ services:
es:
image: elasticsearch:1.5.2
container_name: es
memcache:
memcached:
image: memcached:1.4.24
container_name: memcache
container_name: memcached
course-discovery:
# Uncomment this line to use the official course-discovery base image
image: edxops/discovery:latest
......@@ -33,8 +33,10 @@ services:
depends_on:
- "db"
- "es"
- "memcache"
- "memcached"
environment:
CACHE_BACKEND: "django.core.cache.backends.memcached.MemcachedCache"
CACHE_LOCATION: "memcached:11211"
CONN_MAX_AGE: 60
DB_ENGINE: "django.db.backends.mysql"
DB_HOST: "db"
......
import ddt
from django.core.cache import cache
from django.core.urlresolvers import reverse
from rest_framework.test import APITestCase
......@@ -24,6 +25,9 @@ class ProgramViewSetTests(SerializationMixin, APITestCase):
self.user = UserFactory(is_staff=True, is_superuser=True)
self.client.login(username=self.user.username, password=USER_PASSWORD)
# Clear the cache between test cases, so they don't interfere with each other.
cache.clear()
def create_program(self):
organizations = [OrganizationFactory()]
person = PersonFactory()
......@@ -66,8 +70,13 @@ class ProgramViewSetTests(SerializationMixin, APITestCase):
program = self.create_program()
with self.assertNumQueries(42):
response = self.assert_retrieve_success(program)
assert response.data == self.serialize_program(program)
# Verify that repeated retrieve requests use the cache.
with self.assertNumQueries(2):
self.assert_retrieve_success(program)
@ddt.data(True, False)
def test_retrieve_with_sorting_flag(self, order_courses_by_start_date):
""" Verify the number of queries is the same with sorting flag set to true. """
......@@ -116,6 +125,9 @@ class ProgramViewSetTests(SerializationMixin, APITestCase):
expected.reverse()
self.assert_list_results(self.list_path, expected, 13)
# Verify that repeated list requests use the cache.
self.assert_list_results(self.list_path, expected, 2)
def test_filter_by_type(self):
""" Verify that the endpoint filters programs to those of a given type. """
program_type_name = 'foo'
......
from rest_framework import mixins, viewsets
from rest_framework.filters import DjangoFilterBackend
from rest_framework.permissions import IsAuthenticated
from rest_framework_extensions.cache.mixins import CacheResponseMixin
from course_discovery.apps.api import filters, serializers
from course_discovery.apps.api.pagination import ProxiedPagination
......@@ -9,7 +10,7 @@ from course_discovery.apps.course_metadata.models import ProgramType
# pylint: disable=no-member
class ProgramViewSet(viewsets.ReadOnlyModelViewSet):
class ProgramViewSet(CacheResponseMixin, viewsets.ReadOnlyModelViewSet):
""" Program resource. """
lookup_field = 'uuid'
lookup_value_regex = '[0-9a-f-]+'
......
......@@ -308,7 +308,6 @@ LOGGING = {
}
}
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
......@@ -335,6 +334,12 @@ REST_FRAMEWORK = {
},
}
# http://chibisov.github.io/drf-extensions/docs/
REST_FRAMEWORK_EXTENSIONS = {
'DEFAULT_CACHE_ERRORS': False,
'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60,
}
# NOTE (CCB): JWT_SECRET_KEY is intentionally not set here to avoid production releases with a public value.
# Set a value in a downstream settings file.
JWT_AUTH = {
......
......@@ -19,6 +19,13 @@ TEST_NON_SERIALIZED_APPS = [
'django.contrib.auth',
]
CACHES = {
'default': {
'BACKEND': os.environ.get('CACHE_BACKEND', 'django.core.cache.backends.locmem.LocMemCache'),
'LOCATION': os.environ.get('CACHE_LOCATION', ''),
}
}
DATABASES = {
'default': {
'ENGINE': os.environ.get('DB_ENGINE', 'django.db.backends.sqlite3'),
......
......@@ -27,9 +27,9 @@ services:
- "9200:9200"
- "9300:9300"
memcache:
memcached:
image: memcached:1.4.24
container_name: memcache
container_name: memcached
course-discovery:
# Uncomment this line to use the official course-discovery base image
......@@ -47,7 +47,7 @@ services:
depends_on:
- "db"
- "es"
- "memcache"
- "memcached"
environment:
TEST_ELASTICSEARCH_URL: "http://es:9200"
ENABLE_DJANGO_TOOLBAR: 1
......
......@@ -26,6 +26,7 @@ djangorestframework-csv==1.4.1
djangorestframework-jwt==1.8.0
djangorestframework-xml==1.3.0
django-rest-swagger[reST]==0.3.10
drf-extensions==0.3.1
drf-haystack==1.6.0rc1
dry-rest-permissions==0.1.6
edx-auth-backends==1.0.3
......
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