Django ORM генерирует безумные подзапросы с prefetch_related - страница загружается за 3 секунды

Я тут с ума схожу. Я уже несколько месяцев работаю над этим сайтом электронной коммерции, и вдруг страница каталога товаров загружается более чем за 3 секунды. ORM Django генерирует абсолютно безумный SQL с вложенными подзапросами повсюду вместо простых соединений.

Вот моя настройка (упрощенная):

class ProductManager(models.Manager):
    def active(self):
        return self.filter(is_active=True, category__is_active=True)
    
    def with_reviews_stats(self):
        return self.annotate(
            avg_rating=Avg('reviews__rating'),
            reviews_count=Count('reviews', distinct=True),
            recent_reviews_count=Count(
                'reviews',
                filter=Q(reviews__created_at__gte=timezone.now() - timedelta(days=30)),
                distinct=True
            )
        )

class Product(models.Model):
    name = models.CharField(max_length=200)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    objects = ProductManager()

# ... other models (Category, Review, CartItem, Wishlist)

И вот вид, который меня убивает:

def product_catalog_view(request):
    products = Product.objects.active().with_reviews_stats()
    
    # Category filtering
    if category_id := request.GET.get('category'):
        category = get_object_or_404(Category, id=category_id, is_active=True)
        subcategories = Category.objects.filter(
            Q(parent=category) | Q(parent__parent=category) | Q(id=category.id)
        ).values_list('id', flat=True)
        products = products.filter(category_id__in=subcategories)
    
    # Sort by popularity
    products = products.annotate(
        popularity_score=F('reviews_count') * F('avg_rating')
    ).order_by('-popularity_score', '-created_at')
    
    # Try to optimize (spoiler: doesn't work)
    products = products.select_related('category').prefetch_related(
        'reviews__user',
        Prefetch(
            'reviews',
            queryset=Review.objects.filter(is_approved=True).select_related('user'),
            to_attr='approved_reviews'
        )
    )
    
    # Add user-specific data (this is where it gets ugly)
    if request.user.is_authenticated:
        user_cart_items = CartItem.objects.filter(user=request.user).values_list('product_id', flat=True)
        user_wishlist_items = Wishlist.objects.filter(user=request.user).values_list('product_id', flat=True)
        
        products = products.annotate(
            in_cart=Case(
                When(id__in=user_cart_items, then=Value(True)),
                default=Value(False),
                output_field=BooleanField()
            ),
            in_wishlist=Case(
                When(id__in=user_wishlist_items, then=Value(True)),
                default=Value(False),
                output_field=BooleanField()
            ),
            cart_quantity=Subquery(
                CartItem.objects.filter(
                    user=request.user, product=OuterRef('pk')
                ).values('quantity')[:1]
            )
        )
    
    paginator = Paginator(products, 20)
    page = paginator.get_page(request.GET.get('page', 1))
    return render(request, 'catalog.html', {'products': page})

Сгенерированный SQL абсолютно логичен - в нем есть примерно 5 различных подзапросов для корзины пользователя / списка пожеланий, вместо того чтобы просто выполнять левые объединения. Я запустил EXPLAIN ANALYZE, и PostgreSQL тратит 90% времени на эти подзапросы.

Что я пробовал до сих пор:

Добавлены индексы для всего (немного помогло) Пробовал только() и отложить() - никакой разницы

Переписал части на необработанном SQL - работает в 10 раз быстрее, но не достигает цели

Разбивка на несколько запросов - привет, проблема с N+1

Гуглил часами, ничего полезного не нашел

Самое странное, что если я удаляю пользовательские аннотации (in_cart, in_wishlist, cart_quantity), все выполняется быстро. Но мне нужны эти данные для интерфейса.

Мои вопросы:

Почему Django генерирует подзапросы вместо объединений для случаев / When?

Есть ли способ заставить Django использовать здесь объединения?

Я делаю что-то в корне неправильное с тем, как я структурирую этот запрос?

Должен ли я просто сдаться и использовать для этого необработанный SQL

Среда: Django 4.2, PostgreSQL 14 ~50 тыс. продуктов, ~200 тыс. отзывов Раньше это нормально работало с небольшими наборами данных

Я серьезно подумываю о переходе на raw SQL для этого представления, но я действительно не хочу терять все преимущества ORM. Кто-нибудь сталкивался с подобными проблемами производительности? Буду очень признателен за любые идеи!

РЕДАКТИРОВАТЬ: Только что попробовал удалить пользовательские методы диспетчера, и проблема не устранена, так что проблема определенно связана с пользовательскими аннотациями.

ПРАВКА 2: Для тех, кто спрашивает, да, у меня есть соответствующие индексы:

CREATE INDEX idx_product_category_active ON products_product(category_id, is_active);
CREATE INDEX idx_review_product_approved ON products_review(product_id, is_approved);
CREATE INDEX idx_cartitem_user_product ON products_cartitem(user_id, product_id);
-- etc.

Все еще чертовски медленно.

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