From 3b074783ee00cb787fc7de22a2c1b33f781a47e6 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Wed, 7 Oct 2026 18:55:55 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F(backend)=20add=20UPPER(email?= =?UTF-8?q?)=20index=20for=20case-insensitive=20user=20lookups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Case-insensitive user lookups during token issuance currently take around 100 ms in production, accounting for roughly a third of the total 300 ms token request trace. Add an `UPPER(email)` index on the user table so these lookups can use the index instead of scanning, cutting the dominant cost on the token issuance path. --- CHANGELOG.md | 4 ++++ .../migrations/0026_user_email_upper_idx.py | 23 +++++++++++++++++++ src/backend/core/models.py | 3 +++ 3 files changed, 30 insertions(+) create mode 100644 src/backend/core/migrations/0026_user_email_upper_idx.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 432303eda..35f284725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to ## [Unreleased] +### Added + +- ⚡️(backend) add UPPER(email) index for case-insensitive user lookups + ### Changed - ⚡️(backend) hash application secrets with SHA-256 diff --git a/src/backend/core/migrations/0026_user_email_upper_idx.py b/src/backend/core/migrations/0026_user_email_upper_idx.py new file mode 100644 index 000000000..c778723f9 --- /dev/null +++ b/src/backend/core/migrations/0026_user_email_upper_idx.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.14 on 2026-10-07 18:45 + +from django.contrib.postgres.operations import AddIndexConcurrently +from django.db import migrations, models +from django.db.models.functions import Upper + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ('core', '0025_application_client_secret_sha256'), + ] + + operations = [ + AddIndexConcurrently( + model_name="user", + index=models.Index( + Upper("email"), + name="user_email_upper_idx", + ), + ), + ] diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 8d4d13a3e..284ff98a0 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -246,6 +246,9 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin): name="unique_email_when_sub_is_null", ) ] + indexes = [ + models.Index(models.functions.Upper("email"), name="user_email_upper_idx"), + ] def __str__(self): return self.email or self.admin_email or str(self.id)