fix: медленный '?'-запрос в базе заменили на рандом по ip (должно не тормозить в проде) + таймауты
Build and Push Docker Image / build-and-push (push) Successful in 1m13s

This commit is contained in:
2026-07-05 21:29:35 +03:00
parent e55ea68d05
commit 17d45d1c05
2 changed files with 38 additions and 15 deletions
+34 -11
View File
@@ -12,6 +12,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.views.generic import DetailView, TemplateView from django.views.generic import DetailView, TemplateView
import time import time
import pytils import pytils
import random
from web.models import TbDictumAndQuotes, TbImages, TbAuthor from web.models import TbDictumAndQuotes, TbImages, TbAuthor
@@ -98,9 +99,11 @@ class CommonContextMixin:
# --- 4. ВЫБОР КАРТИНКИ --- # --- 4. ВЫБОР КАРТИНКИ ---
if dq.kImages_id is None: if dq.kImages_id is None:
if len(tags) != 0: if len(tags) != 0:
tagged_image = TbImages.objects.filter(tags__name__in=tags).order_by('?').first() # Используем Python random вместо order_by('?') для производительности
if tagged_image: tagged_images = list(TbImages.objects.filter(tags__name__in=tags).values_list('id', 'imFile'))
context.update({'IMAGE': tagged_image.imFile}) if tagged_images:
tagged_image = tagged_images[random.randint(0, len(tagged_images) - 1)]
context.update({'IMAGE': tagged_image[1]})
else: else:
context.update({'IMAGE': dq.kImages.imFile}) context.update({'IMAGE': dq.kImages.imFile})
@@ -110,7 +113,13 @@ class CommonContextMixin:
# --- 6. ВЫБОР СЛЕДУЮЩЕЙ ЦИТАТЫ --- # --- 6. ВЫБОР СЛЕДУЮЩЕЙ ЦИТАТЫ ---
# Сначала пробуем найти следующую цитату, которую мы еще не видели # Сначала пробуем найти следующую цитату, которую мы еще не видели
dq_next = queryset.exclude(id__in=seen_ids).order_by('?').first() # Используем Python random вместо order_by('?') для производительности
unseen_ids = queryset.exclude(id__in=seen_ids).values_list('id', flat=True)
if unseen_ids:
dq_next_id = list(unseen_ids)[random.randint(0, len(unseen_ids) - 1)]
dq_next = queryset.filter(id=dq_next_id).first()
else:
dq_next = None
# Если таких нет (мы посмотрели все цитаты в этом контексте/теге) # Если таких нет (мы посмотрели все цитаты в этом контексте/теге)
if dq_next is None: if dq_next is None:
@@ -123,7 +132,12 @@ class CommonContextMixin:
# Вариант: Очистить seen_ids, чтобы в следующий раз (на некст странице) список был пуст? # Вариант: Очистить seen_ids, чтобы в следующий раз (на некст странице) список был пуст?
# Или просто выбрать любую КРОМЕ текущей? # Или просто выбрать любую КРОМЕ текущей?
dq_next = queryset.exclude(id=dq.id).order_by('?').first() other_ids = list(queryset.exclude(id=dq.id).values_list('id', flat=True))
if other_ids:
dq_next_id = other_ids[random.randint(0, len(other_ids) - 1)]
dq_next = queryset.filter(id=dq_next_id).first()
else:
dq_next = None
# Если мы действительно прошли весь цикл по тегу, логично сбросить seen_ids, # Если мы действительно прошли весь цикл по тегу, логично сбросить seen_ids,
# чтобы пользователь мог заново проходить этот список случайно, а не "застревать" на последних. # чтобы пользователь мог заново проходить этот список случайно, а не "застревать" на последних.
@@ -200,13 +214,19 @@ class IndexView(CommonContextMixin, TemplateView):
if active_qs is not None: if active_qs is not None:
# Если мы в режиме фильтрации, тоже стараемся не показывать то, что уже видели # Если мы в режиме фильтрации, тоже стараемся не показывать то, что уже видели
dq = active_qs.exclude(id__in=seen_ids).order_by('?').first() unseen_ids = list(active_qs.exclude(id__in=seen_ids).values_list('id', flat=True))
if unseen_ids:
dq = active_qs.filter(id=unseen_ids[random.randint(0, len(unseen_ids) - 1)]).first()
else:
dq = None
# Если после фильтрации ничего не осталось (мы просмотрели все цитаты тега) # Если после фильтрации ничего не осталось (мы просмотрели все цитаты тега)
if dq is None: if dq is None:
# Сбрасываем историю и берем любую # Сбрасываем историю и берем любую
self.request.session['seen_ids'] = [] self.request.session['seen_ids'] = []
dq = active_qs.order_by('?').first() all_ids = list(active_qs.values_list('id', flat=True))
if all_ids:
dq = active_qs.filter(id=all_ids[random.randint(0, len(all_ids) - 1)]).first()
if dq is None: if dq is None:
# Если тег не задан, или по тегу ничего не нашлось совсем # Если тег не задан, или по тегу ничего не нашлось совсем
@@ -214,11 +234,14 @@ class IndexView(CommonContextMixin, TemplateView):
active_qs = TbDictumAndQuotes.objects.all() active_qs = TbDictumAndQuotes.objects.all()
# Случайная цитата (с учетом истории, чтобы главная страница тоже не зацикливалась) # Случайная цитата (с учетом истории, чтобы главная страница тоже не зацикливалась)
dq = active_qs.exclude(id__in=seen_ids).order_by('?').first() unseen_ids = list(active_qs.exclude(id__in=seen_ids).values_list('id', flat=True))
if unseen_ids:
if dq is None: dq = active_qs.filter(id=unseen_ids[random.randint(0, len(unseen_ids) - 1)]).first()
else:
self.request.session['seen_ids'] = [] self.request.session['seen_ids'] = []
dq = active_qs.order_by('?').first() all_ids = list(active_qs.values_list('id', flat=True))
if all_ids:
dq = active_qs.filter(id=all_ids[random.randint(0, len(all_ids) - 1)]).first()
if dq: if dq:
# Используем миксин, ОБЯЗАТЕЛЬНО передаем active_qs # Используем миксин, ОБЯЗАТЕЛЬНО передаем active_qs
+4 -4
View File
@@ -48,7 +48,7 @@ services:
mkdir -p /app/public/media/errors && mkdir -p /app/public/media/errors &&
cp /home/app/web/dicquo/templates/static_404.html /app/public/media/errors/404.html && cp /home/app/web/dicquo/templates/static_404.html /app/public/media/errors/404.html &&
cp /home/app/web/dicquo/templates/static_500.html /app/public/media/errors/500.html && cp /home/app/web/dicquo/templates/static_500.html /app/public/media/errors/500.html &&
gunicorn --workers 3 --bind 0.0.0.0:8000 dicquo.wsgi:application" gunicorn --workers 2 --timeout 15 --bind 0.0.0.0:8000 dicquo.wsgi:application"
# 4. Проброс портов (Внешний Nginx -> localhost:8010) # 4. Проброс портов (Внешний Nginx -> localhost:8010)
ports: ports:
@@ -91,9 +91,9 @@ services:
healthcheck: healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/').read()"] test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/').read()"]
interval: 30s # Проверка каждые 30 секунд interval: 30s # Проверка каждые 30 секунд
timeout: 3s # Таймаут ответа - 3 секунды timeout: 5s # Таймаут ответа - 5 секунд
start_period: 10s # Даем 10 секунд на стартап перед первой проверкой start_period: 60s # Даем 60 секунд на стартап (миграции + collectstatic + gunicorn запуск)
retries: 3 # Unhealthy после 3 неудачных попыток retries: 5 # Unhealthy после 5 неудачных попыток
# 9. Логирование (Ротация) # 9. Логирование (Ротация)
logging: logging: