Ваш застосунок може бути ідеально реалізований, але якщо SQL-запити повільні, користувачі швидко це помітять. Уявіть, що ваш сервер — це шеф-кухар, а SQL-запит — це замовлення на приготування їжі. Якщо шеф довго шукає потрібний інгредієнт або готує півгодини, клієнти почнуть сумувати (а потім писати погані відгуки). Профілювання SQL-запитів допомагає знайти «вузькі місця» в продуктивності й оптимізувати їх, а тестування допомагає впевнитися, що все працює правильно в різних сценаріях використання.
Підходи до тестування продуктивності запитів
Чому час виконання запиту не завжди показник? Річ у тому, що швидкість виконання запитів відрізняється в залежності від навантаження сервера, розміру бази даних і навіть від використаної системи кешування. Тому треба враховувати як абсолютний час (в мілісекундах), так і відносні характеристики, типу кількості операцій читання/запису.
Для тестування запитів важливо брати до уваги реальні сценарії використання. Наприклад, якщо у вас модель API для інтернет-магазину, то запити «отримати топ-10 продуктів» або «знайти продукт за SKU» будуть виконуватись частіше, ніж, скажімо, «видалити користувача».
Щоб бути впевненими в результатах, профілювання запитів проводять на репліці продакшн-бази або на даних, максимально наближених до реальних.
Використання інструментів профілювання
SQLAlchemy дозволяє ввімкнути логування запитів через прапорець echo=True. Це гарна відправна точка, щоб зрозуміти, які запити виконуються насправді.
from sqlalchemy import create_engine
DATABASE_URL = "postgresql+psycopg2://user:password@localhost/dbname"
engine = create_engine(DATABASE_URL, echo=True) # Увімкнемо логування запитів
Тепер при кожному запиті ви побачите SQL-код у консолі. Наприклад:
2023-01-01 12:00:00,123 INFO sqlalchemy.engine.Engine SELECT * FROM users WHERE id=1
У Django є спеціальна бібліотека — Django Debug Toolbar, яка допомагає бачити, які запити виконуються і скільки часу вони займають.
Для початку встановимо її:
pip install django-debug-toolbar
Налаштуємо INSTALLED_APPS і MIDDLEWARE у settings.py:
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
Додамо умову для відображення тулбару тільки на локальній машині:
INTERNAL_IPS = [
"127.0.0.1",
]
Тепер ви зможете відстежувати запити через браузер. Наприклад, для запиту виду:
users = User.objects.filter(is_active=True).select_related('profile')
Django Debug Toolbar покаже SQL-запит і час його виконання. Якщо щось починає виглядати підозріло довго — є привід подумати про оптимізацію.
Профілювання в FastAPI за допомогою sqltap
Для профілювання запитів у FastAPI можна використовувати бібліотеку sqltap. Вона дозволяє збирати дані про виконані запити та їх ефективність.
Встановимо sqltap:
pip install sqltap
Приклад використання:
from fastapi import FastAPI
import sqlalchemy as sa
import sqltap
app = FastAPI()
DATABASE_URL = "sqlite:///./test.db" # Для прикладу використовуємо SQLite
engine = sa.create_engine(DATABASE_URL)
@app.middleware("http")
async def profile_sql(request, call_next):
profiler = sqltap.start()
response = await call_next(request)
stats = profiler.stop()
sqltap.report_text(stats, file=open("sql_report.txt", "w"))
return response
Після виконання запитів ви отримаєте звіт sql_report.txt з інформацією про час виконання кожного SQL-запиту.
Інструменти профілювання PostgreSQL
Якщо ви використовуєте PostgreSQL, увімкнення параметра EXPLAIN/EXPLAIN ANALYZE стане вашим найкращим другом. Ці команди показують, як PostgreSQL виконує запит, скільки часу це займає і які індекси використовуються.
Приклад:
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
Як інтерпретувати результати профілювання?
Ключові показники
- Кількість запитів: якщо для виводу однієї сторінки виконується десяток запитів, це привід замислитись.
- Затримка: запити мають виконуватись за мілісекунди. Якщо затримка вимірюється секундами, варто оптимізувати логіку.
- Використання індексів: якщо запит сканує всю таблицю (
Sequential Scan), перевірте коректність налаштувань індексу.
Антипатерни
- N+1 проблема: виконується багато схожих запитів замість одного. Наприклад, завантаження списку користувачів з профілями:
- Поганий запит:
users = User.query.all() for user in users: print(user.profile.name) # Для кожного користувача виконується окремий запит! - Кращий запит:
users = User.query.options(joinedload(User.profile)).all()
- Поганий запит:
- Нераціональне використання JOIN: занадто багато зв'язків в одному запиті може сповільнити виконання. Використовуйте
select_relatedтільки там, де це необхідно.
Практичні поради щодо покращення продуктивності
1. Використовуйте select_related і prefetch_related у Django.
Ці методи допомагають керувати зв'язками й зменшити кількість запитів:
# Замість звернення до бази для кожного поста робимо один запит
posts = Post.objects.select_related('author').all()
2. Збільшуйте глибину вибірки в SQLAlchemy через joinedload.
Якщо у вас є таблиці зі зв'язками, уникайте додаткових запитів:
from sqlalchemy.orm import joinedload
query = session.query(User).options(joinedload(User.profile))
users = query.all()
3. Обмежте обсяг даних.
Запитуйте тільки потрібні поля:
# FastAPI: запит тільки імені та email
users = session.query(User.name, User.email).all()
Як впровадити тестування та профілювання в процес розробки?
- Регулярно запускайте інструменти оптимізації під час розробки.
- Профілюйте запити при додаванні нових функцій.
- Автоматизуйте тестування продуктивності, додавши тести в CI/CD пайплайн. Наприклад, можна порівнювати час виконання запитів до і після змін.
- Використовуйте інструменти моніторингу продакшн-бази, такі як New Relic, DataDog або Elastic APM.
Ці методи допоможуть вам не тільки знайти слабкі місця в застосунку, але й навчитися грамотно вирішувати проблеми, пов'язані з продуктивністю. У наступній лекції ми розберемо... що ж, читайте і дізнаєтесь!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ