Оптимизация производительности: устранение N+1 запросов и дубликатов
- Добавлен django-debug-toolbar 6.1.0 для мониторинга производительности
- Устранен дублирующийся COUNT запрос в списке клиентов (используется paginator.count)
- Добавлен select_related('status') в списке заказов для устранения N+1
Результаты:
- Список клиентов: 6→5 запросов, 13.24→10мс
- Список заказов: 18→7 запросов, 52.68→15-20мс, устранено 11 дубликатов
This commit is contained in:
@@ -33,9 +33,6 @@ def customer_list(request):
|
|||||||
# Исключаем системного клиента из списка
|
# Исключаем системного клиента из списка
|
||||||
customers = Customer.objects.filter(is_system_customer=False)
|
customers = Customer.objects.filter(is_system_customer=False)
|
||||||
|
|
||||||
# Общее количество клиентов
|
|
||||||
total_customers = customers.count()
|
|
||||||
|
|
||||||
if query:
|
if query:
|
||||||
# Нормализуем номер телефона
|
# Нормализуем номер телефона
|
||||||
phone_normalized = normalize_query_phone(query)
|
phone_normalized = normalize_query_phone(query)
|
||||||
@@ -74,7 +71,7 @@ def customer_list(request):
|
|||||||
context = {
|
context = {
|
||||||
'page_obj': page_obj,
|
'page_obj': page_obj,
|
||||||
'query': query,
|
'query': query,
|
||||||
'total_customers': total_customers,
|
'total_customers': paginator.count, # Используем count из paginator, чтобы избежать дублирования SQL запроса
|
||||||
}
|
}
|
||||||
return render(request, 'customers/customer_list.html', context)
|
return render(request, 'customers/customer_list.html', context)
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ TENANT_APPS = [
|
|||||||
# Объединяем для INSTALLED_APPS
|
# Объединяем для INSTALLED_APPS
|
||||||
INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS]
|
INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS]
|
||||||
|
|
||||||
|
# Django Debug Toolbar (только в DEBUG режиме)
|
||||||
|
if DEBUG:
|
||||||
|
INSTALLED_APPS += ['debug_toolbar']
|
||||||
|
|
||||||
# Модели тенанта и домена
|
# Модели тенанта и домена
|
||||||
TENANT_MODEL = "tenants.Client"
|
TENANT_MODEL = "tenants.Client"
|
||||||
TENANT_DOMAIN_MODEL = "tenants.Domain"
|
TENANT_DOMAIN_MODEL = "tenants.Domain"
|
||||||
@@ -117,6 +121,11 @@ MIDDLEWARE = [
|
|||||||
'simple_history.middleware.HistoryRequestMiddleware', # История изменений
|
'simple_history.middleware.HistoryRequestMiddleware', # История изменений
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Django Debug Toolbar Middleware (только в DEBUG режиме)
|
||||||
|
# ВАЖНО: добавляем ПОСЛЕ TenantMainMiddleware, чтобы toolbar видел корректный tenant
|
||||||
|
if DEBUG:
|
||||||
|
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
|
||||||
|
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# AUTHENTICATION BACKENDS
|
# AUTHENTICATION BACKENDS
|
||||||
@@ -379,6 +388,29 @@ TENANT_DOMAIN_BASE = env('TENANT_DOMAIN_BASE', default='localhost:8000')
|
|||||||
USE_HTTPS = env.bool('USE_HTTPS', default=False)
|
USE_HTTPS = env.bool('USE_HTTPS', default=False)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# DJANGO DEBUG TOOLBAR SETTINGS
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
# IP адреса, с которых разрешен доступ к Debug Toolbar
|
||||||
|
INTERNAL_IPS = [
|
||||||
|
'127.0.0.1',
|
||||||
|
'localhost',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Если запускаете в Docker, добавьте IP хоста
|
||||||
|
# Например: INTERNAL_IPS += ['172.17.0.1', '192.168.65.1'] # Docker Desktop
|
||||||
|
|
||||||
|
# Конфигурация Debug Toolbar
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
|
# Показывать toolbar всегда при DEBUG=True и INTERNAL_IPS
|
||||||
|
'SHOW_TOOLBAR_CALLBACK': lambda request: DEBUG,
|
||||||
|
# Или можно фильтровать по tenant (например, только для определенных поддоменов):
|
||||||
|
# 'SHOW_TOOLBAR_CALLBACK': lambda request: DEBUG and getattr(request, 'tenant', None) and request.tenant.schema_name != 'public',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# EMAIL SETTINGS
|
# EMAIL SETTINGS
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ urlpatterns = [
|
|||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
||||||
|
# Django Debug Toolbar (только в DEBUG режиме)
|
||||||
|
import debug_toolbar
|
||||||
|
urlpatterns += [
|
||||||
|
path('__debug__/', include(debug_toolbar.urls)),
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
# Force serve media files in production (for NAS setup)
|
# Force serve media files in production (for NAS setup)
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ urlpatterns = [
|
|||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
||||||
|
# Django Debug Toolbar (только в DEBUG режиме)
|
||||||
|
import debug_toolbar
|
||||||
|
urlpatterns += [
|
||||||
|
path('__debug__/', include(debug_toolbar.urls)),
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
# Force serve media files in production (for NAS setup)
|
# Force serve media files in production (for NAS setup)
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def order_list(request):
|
|||||||
"""
|
"""
|
||||||
# Базовый queryset с оптимизацией запросов
|
# Базовый queryset с оптимизацией запросов
|
||||||
orders = Order.objects.select_related(
|
orders = Order.objects.select_related(
|
||||||
'customer', 'delivery_address', 'pickup_warehouse'
|
'customer', 'delivery_address', 'pickup_warehouse', 'status' # Добавлен 'status' для избежания N+1
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
# Применяем фильтры через django-filter
|
# Применяем фильтры через django-filter
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ click-repl==0.3.0
|
|||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
Django==5.0.10
|
Django==5.0.10
|
||||||
django-celery-results==2.5.1
|
django-celery-results==2.5.1
|
||||||
|
django-debug-toolbar==6.1.0
|
||||||
django-environ==0.12.0
|
django-environ==0.12.0
|
||||||
django-filter==24.3
|
django-filter==24.3
|
||||||
django-nested-admin==4.1.5
|
django-nested-admin==4.1.5
|
||||||
|
|||||||
Reference in New Issue
Block a user