Повышение производительности подсчетов в подзапросе с миллионами строк с использованием Prefetch - PullRequest
1 голос
/ 07 июня 2019

У меня есть приложение, которое отслеживает события с веб-сайтов, и часть пользовательского интерфейса показывает количество событий на веб-сайт за определенный период времени. Вот как выглядят модели:

class Website(models.Model):
    name = models.CharField(max_length=64)
    url = models.TextField()

class Event(models.Model):
    website = models.ForeignKey(Website, related_name="events")
    created_at = models.DateTimeField(default=timezone.now)
    ip_address = models.CharField(max_length=64)
    status = models.CharField(max_length=16)
    message = models.CharField(max_length=128)

Эти веб-сайты генерируют тысячи событий в день, поэтому таблица Event довольно велика по сравнению с другими таблицами. Вот как выглядит запрос, который я пытаюсь сгенерировать:

eargs = {
    "website": OuterRef("pk"),
    "created_at__gte": some_start_time,
    "created_at__lt": some_end_time
}
events = Event.objects.filter(**eargs).values("website")
events_count = events.annotate(c=Count("*").values("c")[:1]

websites = Website.objects.annotate(events=Coalesce(Subquery(events_count,
                                                    output_field=IntegerField()), 0)

Как я уже говорил, таблица Events содержит миллионы строк. Для небольшого количества веб-сайтов этот запрос не займет много времени. Но когда есть 100 или более сайтов, это занимает довольно много времени. Я провел некоторое профилирование, и база данных (внутренне) запрашивает счетчик для каждого веб-сайта. Поэтому, если у меня есть 100 веб-сайтов, база данных делает 100 запросов для генерации счетчиков (есть еще один запрос от Django, но Postgres внутренне делает эти 100 подзапросов).

То, что я хотел бы сделать, это предварительная выборка этих подсчетов, потому что когда я запускаю следующий необработанный SQL, это на самом деле довольно быстро:

SELECT
    website_id,
    COUNT(*)
FROM
    myapp_events
WHERE
    created_at >= some_start_time AND created_at < some_end_time
GROUP BY
    website_id;

Есть ли какой-нибудь возможный способ предварительно выбрать этот запрос и по-прежнему использовать результаты в моем QuerySet? Или я все об этом ошибся? Похоже, что это было бы обычным делом, и мне трудно обдумать, как это ускорить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...