Django: Фильтрация полей модели m2m от моделей, связанных с моделью m2m, займет время

Здесь довольно много строк кода и журналов, которые могут быть сложными и трудными для понимания. Я приношу свои извинения за это.

Ниже приведен ИЛИ полнотекстовый поиск по Video.title и Tag.name с использованием pgroonga, расширения для полнотекстового поиска

Но на выполнение запроса уходит около 2000-4000 мс. Количество просмотров составляет около 130 000, но у нас стоит ограничение LIMIT 20. Индекс для полнотекстового поиска используется успешно.

Видео: 300K строк Тег: 5K рядов video_tag_through: 1.3M rows

# models.py
class Tag(models.Model):
    name = models.CharField(unique=True, max_length=30)
    created_at = models.DateTimeField(default=timezone.now)
    ...


class Video(models.Model):
    title = models.CharField(max_length=300)
    tags = models.ManyToManyField(Tag, blank=True)
    updated_at = models.DateTimeField(auto_now=True)
    ...


class History(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    video = models.ForeignKey(Video, on_delete=models.CASCADE)
    ...


class Favorite(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE
    video = models.ForeignKey(Video, on_delete=models.CASCADE)
    ...


class Playlist(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    is_wl = models.BooleanField(default=False, editable=False)
    ...


class Track(models.Model):
    playlist = models.ForeignKey(Playlist, on_delete=models.CASCADE, null=True)
    video = models.ForeignKey(Video, on_delete=models.CASCADE)
    ...
# migration file
...
    operations = [
        migrations.RunSQL(
            "CREATE EXTENSION pgroonga",
            "DROP EXTENSION pgroonga",
        ),
        migrations.RunSQL(
            "CREATE INDEX pgroonga_video_index ON videos_video USING pgroonga (title pgroonga_varchar_full_text_search_ops_v2)",
            "DROP INDEX pgroonga_video_index",
        ),
        migrations.RunSQL(
            "CREATE INDEX pgroonga_tag_index ON videos_tag USING pgroonga (name pgroonga_varchar_full_text_search_ops_v2)",
            "DROP INDEX pgroonga_tag_index",
        ),
    ]
# lookups.py
class Groonga(Lookup):
    lookup_name = "groonga"

    def as_sql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.process_rhs(compiler, connection)
        params = lhs_params + rhs_params
        return "%s &@~ %s" % (lhs, rhs), params

# queryset
Video.objects.annotate(
    is_viewed=Exists(History.objects.filter(user=user, video=OuterRef("pk"))),
    is_favorited=Exists(
        Favorite.objects.filter(user=user, video=OuterRef("pk"))
    ),
    is_wl=Exists(
        Track.objects.filter(
            playlist__user=user, playlist__is_wl=True, video=OuterRef("pk")
        )
    ),
).filter(
    Q(title__groonga=value)
    | Q(tags__pk__in=Tag.objects.filter(name__groonga=value).values_list("pk")),
    is_public=True,
    published_at__lte=timezone.now(),
).order_by("-published_at").distinct()[:20]

---------- Без annotate ----------

Удаление annotate довольно сильно повысит скорость и сделает ее почти нормальной.

Video.objects.filter(
  Q(title__groonga=value)
  | Q(tags__pk__in=Tag.objects.filter(name__groonga=value).values_list("pk"))
).order_by("-published_at").distinct()[:20]

---------- Без Q(tags__pk__in=Tag.objects.filter... ----------

Если не удалять annotate, а удалить Q(tags__pk__in=Tag.objects.filter... без удаления annotate, это будет еще быстрее. Я считаю, что такой уровень скорости не является проблемой.

Video.objects.annotate(
    is_viewed=Exists(History.objects.filter(user=user, video=OuterRef("pk"))),
    is_favorited=Exists(
        Favorite.objects.filter(user=user, video=OuterRef("pk"))
    ),
    is_wl=Exists(
        Track.objects.filter(
            playlist__user=user, playlist__is_wl=True, video=OuterRef("pk")
        )
    ),
).filter(
    title__groonga=value,
    is_public=True,
    published_at__lte=timezone.now(),
).order_by("-published_at")[:20]

Исходя из приведенных выше результатов, я думаю, что проблема не столько в аннотации, сколько в том, что в модели m2m through так много строк, что поиск занимает много времени.

Как я могу ускорить процесс, не перебирая все сквозные модели?

Я долго ломал голову над этим вопросом. Я буду признателен за любую помощь, которую вы можете мне оказать. Если вам нужна какая-либо другая информация, пожалуйста, скажите об этом.

Вернуться на верх