Перевод заметки The Practical Guide to Scaling Django.
Большинство руководств по масштабированию Django сосредоточены на теоретических аспектах. Но реальное масштабирование - это не работа с гипотетическими миллионами пользователей, а систематическое устранение узких мест по мере роста. Здесь мы рассмотрим, как сделать это правильно, основываясь на подходах, которые работают в реальности.
Django - это фреймворк, который выбирают для многих крупнейших веб-приложений (например, Instagram, Pinterest и т. д.). Однако, не так уж сложно столкнуться с распространенными подводными камнями.
Во-первых, узнайте свои реальные узкие места
Прежде чем лезть в дебри, поймите, что производительность Django обычно зависит от этим узких мест:
- Запросы к базе данных
- Рендеринг шаблонов
- Исполнение кода на Python
- Пропуски кэша
- Файловый ввод/вывод
- Задержки сети
Не оптимизируйте то, что вас не тормозит. Вот как бороться с каждой из этих проблем, когда они реально возникают:
Оптимизация базы данных
Оптимизация запросов
# Плохо: N+1 запросов
for user in Users.objects.all():
print(user.profile.bio) # Один запрос на каждого пользователя
# Хорошо: Одиночный запрос с select_related
users = User.objects.select_related('profile').all()
for user in users:
print(user.profile.bio) # Без доп. запросов
Индексирование данных
class Order(models.Model):
user = models.ForeignKey(User)
created_at = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20)
class Meta:
indexes = [
models.Index(fields=['created_at', 'status']),
models.Index(fields=['user', 'status']),
]
Оптимизация кверисетов
# Плохо: Загрузка целых объектов
users = User.objects.all()
# Хорошо: Загрузка только необходимых полей
users = User.objects.values('id', 'email')
# Лучше: Использование iterator() для больших запросов
for user in User.objects.iterator():
process_user(user)
Кэширование
Кэширование уровня представления
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # Кэшируем на 15 минут
def product_list(request):
products = Product.objects.all()
return render(request, 'products/list.html', {'products': products})
Кэширование фрагментов шаблонов
{% load cache %}
{% cache 500 sidebar request.user.id %}
{% for item in expensive_query %}
{{ item }}
{% endfor %}
{% endcache %}
Низкоуровневое кэширование
from django.core.cache import cache
def get_expensive_result(user_id):
cache_key = f'expensive_result_{user_id}'
result = cache.get(cache_key)
if result is None:
result = expensive_computation(user_id)
cache.set(cache_key, result, timeout=3600)
return result
Async: Когда нужны параллельные соединения
# views.py
async def async_view(request):
async with aiohttp.ClientSession() as session:
async with session.get('http://api.example.com/data') as response:
data = await response.json()
return JsonResponse(data)
# urls.py
path('async-data/', async_view)
Фоновые задачи: Не блокируйте цикл запрос-ответ
from django.core.mail import send_mail
from celery import shared_task
@shared_task
def send_welcome_email(user_id):
user = User.objects.get(id=user_id)
send_mail(
'Welcome!',
'Thanks for joining.',
'from@example.com',
[user.email],
)
# Во вьюхе
def signup(request):
user = User.objects.create_user(...)
send_welcome_email.delay(user.id)
return redirect('home')
Балансировка нагрузки: Когда одного сервера недостаточно
# settings.py для серверов с репликацией
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydb',
'HOST': 'primary.database.host',
'CONN_MAX_AGE': 60,
},
'read_replica': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydb',
'HOST': 'replica.database.host',
'CONN_MAX_AGE': 60,
}
}
Медиафайлы: Перенесите в CDN по-раньше
# settings.py
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage'
AWS_ACCESS_KEY_ID = 'your-access-key'
AWS_SECRET_ACCESS_KEY = 'your-secret-key'
AWS_STORAGE_BUCKET_NAME = 'your-bucket-name'
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
Контрольные точки масштабирования в реальном мире
При 100 запросах в секунду:
- Внедрите базовое кэширование
- Добавьте индексы базы данных
- Переместите статические файлы в CDN
При 1000 запросов в секунду:
- Добавьте реплики для чтения
- Внедрите кэширование фрагментов
- Переходите на управляемые Redis/Memcached
При 10000 запросов/секунду:
- Шардируйте базы данных
- Внедрите кэширование на уровне сервисов
- Рассмотрите возможность использования микросервисов для тяжелых операций
Контрольный список масштабирования
Прежде чем добавлять вышеописанные сложности, проверьте, всё ли вы сделали:
- Оптимизировали запросы к базе данных (
select_related
,prefetch_related
) - Добавили надлежащие индексы базы данных
- Реализовали кэширование представлений и шаблонов
- Унесли статические/медиа файлы в CDN
- Настроили мониторинг и оповещения
- Настроили пул соединений
- Добавили фоновые задачи для тяжелых операций
- Добавили реплики чтения для больших нагрузок на чтение
- Настроили надлежащее логирование и отслеживание ошибок
Помните: Django может выдержать большую нагрузку, чем многие думают, если его правильно оптимизировать. Начните с простого: измерьте все и масштабируйте то, что действительно нуждается в масштабировании.
Лучшая стратегия масштабирования - это не добавление дополнительных ресурсов, а устранение потерь в существующих.