diff --git a/.gitignore b/.gitignore index ff0958f1..ac41fdd8 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,7 @@ pip-delete-this-directory.txt # VirtualEnv .venv/ +# Developers *.sw* +manage.py +.DS_Store diff --git a/.travis.yml b/.travis.yml index fe6f5fe5..fba0fd29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ script: - isort --check-only --verbose --recursive --diff rest_framework_json_api # example has extra dependencies that are installed in a dev environment # but are not installed in CI. Explicitly set those packages. - - isort --check-only --verbose --recursive --diff --thirdparty pytest --thirdparty polymorphic --thirdparty pytest_factoryboy example + - isort --check-only --verbose --recursive --diff --thirdparty pytest --thirdparty polymorphic --thirdparty pytest_factoryboy --thirdparty packaging example - coverage run setup.py -v test after_success: - codecov diff --git a/docs/usage.md b/docs/usage.md index f4faeea8..95567b89 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -23,7 +23,12 @@ REST_FRAMEWORK = { ), 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework_json_api.renderers.JSONRenderer', - 'rest_framework.renderers.BrowsableAPIRenderer', + # If you're performance testing, you will want to use the browseable API + # without forms, as the forms can generate their own queries. + # If performance testing, enable: + # 'example.utils.BrowsableAPIRendererWithoutForms', + # Otherwise, to play around with the browseable API, enable: + 'rest_framework.renderers.BrowsableAPIRenderer' ), 'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata', } @@ -36,6 +41,12 @@ retrieve the page can be customized by subclassing `PageNumberPagination` and overriding the `page_query_param`. Page size can be controlled per request via the `PAGINATE_BY_PARAM` query parameter (`page_size` by default). +#### Performance Testing + +If you are trying to see if your viewsets are configured properly to optimize performance, +it is preferable to use `example.utils.BrowsableAPIRendererWithoutForms` instead of the default `BrowsableAPIRenderer` +to remove queries introduced by the forms themselves. + ### Serializers It is recommended to import the base serializer classes from this package @@ -558,6 +569,43 @@ class QuestSerializer(serializers.ModelSerializer): `included_resources` informs DJA of **what** you would like to include. `included_serializers` tells DJA **how** you want to include it. +#### Performance improvements + +Be aware that using included resources without any form of prefetching **WILL HURT PERFORMANCE** as it will introduce m*(n+1) queries. + +A viewset helper was designed to allow for greater flexibility and it is automatically available when subclassing +`views.ModelViewSet` +``` + # When MyViewSet is called with ?include=author it will dynamically prefetch author and author.bio + class MyViewSet(viewsets.ModelViewSet): + queryset = Book.objects.all() + prefetch_for_includes = { + '__all__': [], + 'author': ['author', 'author__bio'] + 'category.section': ['category'] +} +``` + +The special keyword `__all__` can be used to specify a prefetch which should be done regardless of the include, similar to making the prefetch yourself on the QuerySet. + +Using the helper to prefetch, rather than attempting to minimise queries via select_related might give you better performance depending on the characteristics of your data and database. + +For example: + +If you have a single model, e.g. Book, which has four relations e.g. Author, Publisher, CopyrightHolder, Category. + +To display 25 books and related models, you would need to either do: + +a) 1 query via selected_related, e.g. SELECT * FROM books LEFT JOIN author LEFT JOIN publisher LEFT JOIN CopyrightHolder LEFT JOIN Category + +b) 4 small queries via prefetch_related. + +If you have 1M books, 50k authors, 10k categories, 10k copyrightholders +in the select_related scenario, you've just created a in-memory table +with 1e18 rows which will likely exhaust any available memory and +slow your database to crawl. + +The prefetch_related case will issue 4 queries, but they will be small and fast queries.