question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`HasAPIKey` adds huge performance (speed) hit

See original GitHub issue

I’m seeing a huge performance hit when adding the HasAPIKey permission class. I’ve isolated it down to toggling the following setting:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [],
    # 'DEFAULT_PERMISSION_CLASSES': ['rest_framework_api_key.permissions.HasAPIKey'],
}

Which gives the following results:

# Without API Keys

Testing Resource #1...
	Done in 0.09s

Testing Resource #2...
	Avg after 10 attempts: 0.35s

Testing Resource #3...
	Avg after 10 attempts: 0.11s
# With HasAPIKey

Testing Resource #1...
	Done in 0.55s

Testing Resource #2...
	Avg after 10 attempts: 0.83s

Testing Resource #3...
	Avg after 10 attempts: 0.46s

I wasn’t able to find any similar tickets. Has anyone seen this behaviour before?

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
hyzylacommented, Dec 14, 2021

So, one for improving performance of key verification, you can tweak PASSWORD_HASHERS for using different hash method.

https://security.stackexchange.com/questions/246062/pbkdf2-usage-will-slow-rest-api-down

https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-PASSWORD_HASHERS

0reactions
florimondmancacommented, Jun 4, 2022

I did some basic benchmarking on my end using the test_project in this repo, slightly edited to add a few routes:

# views.py
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

from test_project.heroes.permissions import HasHeroAPIKey


class PublicAPIView(APIView):
    def get(self, request: Request) -> Response:
        return Response({"message": "Hello, world!"})


class ProtectedAPIView(APIView):
    permission_classes = [HasHeroAPIKey]

    def get(self, request: Request) -> Response:
        return Response({"message": "Hello, world!"})


class ProtectedObjectAPIView(APIView):
    permission_classes = [HasHeroAPIKey]

    def get(self, request: Request) -> Response:
        self.check_object_permissions(request, object())
        return Response({"message": "Hello, world!"})

# urls.py
from django.contrib import admin
from django.urls import path

from . import views

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/public/", views.PublicAPIView.as_view()),
    path("api/protected/", views.ProtectedAPIView.as_view()),
    path("api/protected/object/", views.ProtectedObjectAPIView.as_view()),
]

And running this script:

import httpx
import statistics

api_key = "<Hero API key obtained via test_project admin>"


def api_key_auth(request):
    request.headers["Authorization"] = f"Api-Key {api_key}"
    return request


def timeit(url, auth):
    times = []

    with httpx.Client() as client:
        for _ in range(10):
            response = client.get(url, auth=auth)
            times.append(response.elapsed.total_seconds())

    m = statistics.mean(times)
    s = statistics.stdev(times)

    print(f"{url=:<20}: {m=:.3f} seconds ({s=:.3f} seconds)")


timeit("http://localhost:8000/api/public/", auth=None)
timeit("http://localhost:8000/api/protected/", auth=api_key_auth)
timeit("http://localhost:8000/api/protected/object/", auth=api_key_auth)

Results (in seconds):

  • /api/public/ (no API key checking): 0.005s (std=0.003) (very low because of being on localhost)
  • /api/protected/:
    • Argon2: 0.197, std=0.040
    • PBKDF2: 0.363, std=0.093
    • BcryptSHA256: 0.425, std=0.050
  • /api/protected/object/ (Argon2): 0.316 (std=0.039)

(Reference: Result w/o API key permissions: 0.005 (std=0.003) (on localhost).)

So:

  • Performance hit is indeed reduced (about 40-50%) when using Argon2 instead of the default PBKDF2 hasher (i.e. placing Argon2 higher in PASSWORD_HASHERS), as @hyzyla suggested.
  • As I assumed, the included .has_object_permissions() implementation does result in duplicate work, which would explain larger performance hit on detail API views. This hints us to move forward on #150.
Read more comments on GitHub >

github_iconTop Results From Across the Web

djangorestframework-api-key 2.2.0 - PythonFix.com
... returns strings that are more than 100 chars; this project cannot support ModelViewSet? HasAPIKey adds huge performance (speed) hit.
Read more >
Web API performance: profiling Django REST framework
As this article will argue, the biggest performance gains for Web APIs can be made not by code tweaking, but by proper caching...
Read more >
User Guide - Django REST Framework API Key - GitHub Pages
The HasAPIKey permission class protects a view behind API key authorization. ... When it is installed, djangorestframework-api-key adds an "API Key ...
Read more >
Database Performance Tips with Django - YouTube
Most performance problems in web applications come down to one thing: the database. In this webinar, veteran #Python developer Andrew ...
Read more >
Improve Serialization Performance in Django Rest Framework
When a developer chooses Python, Django, or Django Rest Framework, it's usually not because of its blazing fast performance.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found