← Назад к вопросам
Как улучшить производительность передачи данных в SQL с Backend на Frontend?
2.3 Middle🔥 211 комментариев
#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как улучшить производительность передачи данных в SQL с Backend на Frontend
Передача данных между backend и frontend часто является узким местом. Большие объёмы JSON замедляют сеть, парсинг, и рендеринг. Рассмотрим способы оптимизации.
1. Выбор только нужных полей
# ❌ Плохо: передаём весь объект
from django.http import JsonResponse
class PostDetailView(View):
def get(self, request, post_id):
post = Post.objects.get(id=post_id)
return JsonResponse(model_to_dict(post))
# Передаём: id, title, content, author_id, created_at, updated_at, tags и т.д.
# ✓ Хорошо: передаём только нужное
def post_detail(request, post_id):
post = Post.objects.get(id=post_id)
return JsonResponse({
'id': post.id,
'title': post.title,
'content': post.content,
'author': post.author.name,
})
# ✓ Хорошо: используем values() в Django ORM
posts = Post.objects.values('id', 'title', 'author__name')
return JsonResponse(list(posts), safe=False)
2. Пагинация больших наборов данных
from django.paginator import Paginator
from django.http import JsonResponse
def posts_list(request):
page_num = request.GET.get('page', 1)
page_size = request.GET.get('limit', 20)
posts = Post.objects.all()
paginator = Paginator(posts, page_size)
page = paginator.get_page(page_num)
return JsonResponse({
'data': list(page.object_list.values('id', 'title')),
'total': paginator.count,
'pages': paginator.num_pages,
'current': page_num,
})
3. Сжатие данных (gzip, brotli)
# settings.py
MIDDLEWARE = [
# ... другие middleware ...
'django.middleware.gzip.GZipMiddleware', # Сжимает ответы > 200 байт
]
# Nginx конфиг для дополнительного сжатия
gzip on;
gzip_types text/plain application/json text/javascript;
gzip_min_length 1000;
gzip_level 6;
4. Фильтрация и поиск на backend
# ❌ Плохо: передаём всё, фильтруем на frontend
allPosts = await fetch('/api/posts')
const filtered = allPosts.filter(p => p.status === 'published')
# ✓ Хорошо: фильтруем на backend
def posts_list(request):
status = request.GET.get('status')
posts = Post.objects.all()
if status:
posts = posts.filter(status=status)
return JsonResponse(list(posts.values('id', 'title')))
5. Кэширование результатов
from django.views.decorators.cache import cache_page
from django.core.cache import cache
# Кэш на уровне view
@cache_page(60 * 5) # 5 минут
def popular_posts(request):
posts = Post.objects.filter(views__gt=1000)
return JsonResponse(list(posts.values('id', 'title')))
# Или ручное кэширование с инвалидацией
def get_posts_cached():
key = 'posts:list'
cached = cache.get(key)
if cached:
return cached
posts = Post.objects.values('id', 'title')
cache.set(key, list(posts), 300) # 5 минут
return list(posts)
# При изменении данных — инвалидируем кэш
def create_post(request):
post = Post.objects.create(...)
cache.delete('posts:list') # Очистить кэш
return JsonResponse({'id': post.id})
6. Асинхронность и стриминг
import json
from django.http import StreamingHttpResponse
def stream_posts(request):
"""Стриминг больших результатов"""
def generate():
posts = Post.objects.all().iterator(chunk_size=1000)
yield '['
first = True
for post in posts:
if not first:
yield ','
yield json.dumps({'id': post.id, 'title': post.title})
first = False
yield ']'
return StreamingHttpResponse(
generate(),
content_type='application/json'
)
7. GraphQL вместо REST
# pip install graphene-django
from graphene_django import DjangoObjectType
import graphene
class PostType(DjangoObjectType):
class Meta:
model = Post
fields = ['id', 'title', 'author']
class Query(graphene.ObjectType):
posts = graphene.List(PostType, status=graphene.String())
def resolve_posts(self, info, status=None):
posts = Post.objects.all()
if status:
posts = posts.filter(status=status)
return posts
schema = graphene.Schema(query=Query)
# Frontend запрашивает только нужное
query = """
{
posts(status: "published") {
id
title
}
}
"""
8. Batch API запросы
# Вместо 100 отдельных запросов — 1 batch
# /api/posts/batch
def batch_posts(request):
"""Получить несколько постов за раз"""
post_ids = request.GET.getlist('ids')
posts = Post.objects.filter(id__in=post_ids)
return JsonResponse(list(posts.values('id', 'title')))
# Frontend
const ids = [1, 2, 3, 4, 5]
const response = await fetch(`/api/posts/batch?ids=${ids.join(',')}`)
9. Incremental updates (SSE, WebSockets)
# Вместо полной переиндексации — отправляем только изменения
# Server-Sent Events (SSE)
from django.http import StreamingHttpResponse
def post_updates(request):
def generate():
while True:
new_posts = Post.objects.filter(
created_at__gte=timezone.now() - timedelta(seconds=5)
)
for post in new_posts:
yield f"data: {json.dumps({'id': post.id, 'title': post.title})}\n\n"
time.sleep(5)
return StreamingHttpResponse(
generate(),
content_type='text/event-stream'
)
# WebSocket для двусторонней коммуникации
# pip install django-channels
10. Оптимизация JSON размера
import json
# ✓ Хорошо: компактный JSON
data = {
'posts': [
{'id': 1, 't': 'Title 1'}, # сокращённые ключи
{'id': 2, 't': 'Title 2'},
]
}
json_str = json.dumps(data, separators=(',', ':')) # Минимальные пробелы
# ✓ Хорошо: используем MessagePack вместо JSON
import msgpack
data_bytes = msgpack.packb(data)
# Размер меньше, но нужна поддержка на frontend
11. SELECT RELATED и PREFETCH RELATED
# ❌ Плохо: N+1 queries
posts = Post.objects.all()
for post in posts:
print(post.author.name) # Запрос за каждый пост
# ✓ Хорошо: 1 запрос с JOIN
posts = Post.objects.select_related('author')
# Для много-ко-многим
posts = Post.objects.prefetch_related('tags')
# Combined
posts = Post.objects.select_related('author').prefetch_related('tags')
12. Database query optimization
# ✓ Хорошо: используем только нужные поля и агрегацию
from django.db.models import Count, Q
stats = Post.objects.aggregate(
total=Count('id'),
published=Count('id', filter=Q(status='published')),
unpublished=Count('id', filter=Q(status='draft')),
)
# Результат: {'total': 100, 'published': 80, 'unpublished': 20}
# Вместо передачи всех 100 постов
13. ETags для кэширования
from django.views.decorators.http import condition
def posts_etag(request):
return Post.objects.latest('updated_at').updated_at.timestamp()
@condition(etag_func=posts_etag)
def posts_list(request):
posts = Post.objects.values('id', 'title')
return JsonResponse(list(posts))
# Frontend будет кэшировать, пока etag не изменится
14. CDN для API ответов
# Используй Cloudflare, AWS CloudFront для кэширования API
# /api/posts — может быть закеширован на CDN
# Nginx
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m;
server {
location /api/posts {
proxy_cache api_cache;
proxy_cache_valid 200 1m; # Кэш на 1 минуту
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
}
15. Best practices
# ✓ Хорошо
response = {
'data': posts,
'total': total_count,
'page': current_page,
}
# ✓ Хорошо: используем only() для QuerySet
posts = Post.objects.only('id', 'title', 'author_id')
# ✓ Хорошо: дефер для больших полей
posts = Post.objects.defer('description', 'content')
# ✗ Плохо: передаём лишние данные
JsonResponse(Post.objects.all(), safe=False)
Заключение
Для оптимизации передачи данных:
- Выбирай только нужные поля (values, only)
- Пагинируй большие наборы (limit, offset)
- Кэшируй результаты (Redis, HTTP кэш)
- Сжимай данные (gzip, brotli)
- Фильтруй на backend (не на frontend)
- Используй select_related/prefetch_related (избегай N+1)
- Рассмотри GraphQL (запрашивай только нужное)
- Мониторь (профилирование показывает реальные проблемы)