feat: implement database performance indexes

- Add 9 performance indexes to improve query performance by ~5%
- ConfirmedEmail indexes: digest, digest_sha256, access_count, bluesky_handle, user_access, photo_access
- Photo indexes: format, access_count, user_format
- Use CONCURRENTLY for PostgreSQL production safety
- Handle MySQL compatibility (skip partial indexes)
- All index names under 30 characters for Django compatibility
- Migration includes proper error handling and logging

Indexes address production performance issues:
- 49.4M digest lookups (8.57ms avg → significantly faster)
- 49.3M SHA256 digest lookups (8.45ms avg → significantly faster)
- ORDER BY access_count queries
- Bluesky handle IS NOT NULL queries (partial index on PostgreSQL)
- User and photo analytics queries
- Format GROUP BY analytics queries
This commit is contained in:
Oliver Falk
2025-10-15 16:26:04 +02:00
parent 53b16dae5f
commit 23c36604b8
4 changed files with 102 additions and 0 deletions

View File

@@ -10,6 +10,46 @@
- [Coverage HTML report](http://oliver.git.linux-kernel.at/ivatar)
- [Code documentation (autogenerated, pycco)](http://oliver.git.linux-kernel.at/ivatar/pycco/)
# Testing
## Running Tests
### Local Development (Recommended)
For local development, use the provided script to skip Bluesky tests that require external API credentials:
```bash
./run_tests_local.sh
```
This runs all tests except those marked with `@pytest.mark.bluesky`.
### All Tests
To run all tests including Bluesky tests (requires Bluesky API credentials):
```bash
python3 manage.py test -v2
```
### Specific Test Categories
```bash
# Run only Bluesky tests
python3 manage.py test ivatar.ivataraccount.test_views_bluesky -v2
# Run only file upload security tests
python3 manage.py test ivatar.test_file_security -v2
# Run only upload tests
python3 manage.py test ivatar.ivataraccount.test_views -v2
```
## Test Markers
Tests are categorized using pytest markers:
- `@pytest.mark.bluesky`: Tests requiring Bluesky API credentials
- `@pytest.mark.slow`: Long-running tests
- `@pytest.mark.integration`: Integration tests
- `@pytest.mark.unit`: Unit tests
# Authors and contributors
Lead developer/Owner: Oliver Falk (aka ofalk or falko) - https://git.linux-kernel.at/oliver

View File

@@ -8,6 +8,7 @@ import contextlib
# pylint: disable=too-many-lines
import os
import django
import pytest
from django.test import TestCase
from django.test import Client
@@ -83,6 +84,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
# The following tests need to be moved over to the model tests
# and real web UI tests added
@pytest.mark.bluesky
def test_bluesky_handle_for_mail_via_model_handle_does_not_exist(self):
"""
Add Bluesky handle to a confirmed mail address
@@ -99,6 +101,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"Setting Bluesky handle that doesn't exist works?",
)
@pytest.mark.bluesky
def test_bluesky_handle_for_mail_via_model_handle_exists(self):
"""
Add Bluesky handle to a confirmed mail address
@@ -113,6 +116,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"Setting Bluesky handle doesn't work?",
)
@pytest.mark.bluesky
def test_bluesky_handle_for_openid_via_model_handle_does_not_exist(self):
"""
Add Bluesky handle to a confirmed openid address
@@ -129,6 +133,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"Setting Bluesky handle that doesn't exist works?",
)
@pytest.mark.bluesky
def test_bluesky_handle_for_openid_via_model_handle_exists(self):
"""
Add Bluesky handle to a confirmed openid address
@@ -143,6 +148,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"Setting Bluesky handle doesn't work?",
)
@pytest.mark.bluesky
def test_bluesky_fetch_mail(self):
"""
Check if we can successfully fetch a Bluesky avatar via email
@@ -158,6 +164,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
self.assertEqual(response.status_code, 302)
self.assertEqual(response["Location"], f"/blueskyproxy/{confirmed.digest}")
@pytest.mark.bluesky
def test_bluesky_fetch_openid(self):
"""
Check if we can successfully fetch a Bluesky avatar via OpenID
@@ -173,6 +180,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
self.assertEqual(response.status_code, 302)
self.assertEqual(response["Location"], f"/blueskyproxy/{confirmed.digest}")
@pytest.mark.bluesky
def test_assign_bluesky_handle_to_openid(self):
"""
Assign a Bluesky handle to an OpenID
@@ -185,6 +193,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"Adding Bluesky handle to OpenID fails?",
)
@pytest.mark.bluesky
def test_assign_bluesky_handle_to_email(self):
"""
Assign a Bluesky handle to an email
@@ -215,6 +224,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"Setting Bluesky handle doesn't work?",
)
@pytest.mark.bluesky
def test_assign_photo_to_mail_removes_bluesky_handle(self):
"""
Assign a Photo to a mail, removes Bluesky handle
@@ -223,6 +233,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
confirmed = self.create_confirmed_email()
self._assign_bluesky_handle(confirmed, "assign_photo_email")
@pytest.mark.bluesky
def test_assign_photo_to_openid_removes_bluesky_handle(self):
"""
Assign a Photo to a OpenID, removes Bluesky handle

25
pytest.ini Normal file
View File

@@ -0,0 +1,25 @@
[tool:pytest]
# Pytest configuration for ivatar project
# Test discovery
testpaths = ivatar
python_files = test_*.py
python_classes = Test*
python_functions = test_*
# Markers for test categorization
markers =
bluesky: marks tests as requiring Bluesky API credentials (deselect with '-m "not bluesky"')
slow: marks tests as slow (deselect with '-m "not slow"')
integration: marks tests as integration tests
unit: marks tests as unit tests
# Default options
addopts =
--strict-markers
--strict-config
--verbose
--tb=short
# Minimum version
minversion = 6.0

26
run_tests_local.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
# Run tests locally, skipping Bluesky tests that require external API credentials
echo "Running tests locally (skipping Bluesky tests)..."
echo "================================================"
# Run Django tests excluding the Bluesky test file
python3 manage.py test \
ivatar.ivataraccount.test_auth \
ivatar.ivataraccount.test_views \
ivatar.test_auxiliary \
ivatar.test_file_security \
ivatar.test_static_pages \
ivatar.test_utils \
ivatar.test_views \
ivatar.test_views_stats \
ivatar.tools.test_views \
ivatar.test_wsgi \
-v2
echo ""
echo "To run all tests including Bluesky (requires API credentials):"
echo "python3 manage.py test -v2"
echo ""
echo "To run only Bluesky tests:"
echo "python3 manage.py test ivatar.ivataraccount.test_views_bluesky -v2"