Django ve Query İpuçları

Üzerinde çalıştığımız Django projesinde artık sıra iyileştirmeler kısmına gelmişti. Kullandığımız veritabanı PostgreSQL’di ve Django View’larındaki sorgu miktarlarını incelemeye başlamıştım.

Django Debug Toolbar’da, ProductListView’un render ettiği html’e bakarken 80 tane tekrar sorgu yapıldığını gördüm. Product modelim aşağıdaki gibiydi;

class Product(models.Model):
    category = models.ForeignKey(
        to='ProductCategory',
        related_name='products',
        on_delete=models.CASCADE,
        verbose_name=_('category'),
    )
    product_filter_feature = models.ManyToManyField(
        to='ProductFilterFeature',
        related_name='products'
    )
    stock_code = models.CharField(
        max_length=255,
        verbose_name=_('stock code'),
    )

Modelin içinden ForeignKey verdiğim ProductCategory modeliyse aşağıdaki gibiydi;

class ProductCategory(models.Model):
    name = models.CharField(
        max_length=255,
        verbose_name=_('name'),
    )

Son olarak ManyToManyField verdiğim ProductFilterFeature modeli ise;

class ProductFilterFeature(models.Model): 
    name = models.CharField(
        max_length=255,
        verbose_name=_('name'),
    ) 

şeklindeydi. Veriyi gösteren html şablonda aşağıdaki gibiydi;

<ul>
{% for product in product_list %}
    <p>{{ product.category.name }}</p>

    {% for feature in product.product_filter_feature.all %}
        <span>{{ feature.name }}</span>
    {% endfor %}
{% endfor %}
</ul>

Tekrar eden sorguların related sorguları yaptığım yerde olduğunu farkettim. Html şablonda product.category.name ve product.product_filter_feature.all buna sebep oluyordu. Hemen Django’nun QuerySet API dökümanına baktım.

ForeignKey ilişkisindeki takip etme yani product.category.name gibi sorgulama yapmaya imkan veren durumlar bize fazla sorgu olarak dönüyor. Bunu çözmek için Django bize select_related metodunu öneriyor. Bu sayede veritabanı tarafından ilişkili tabloları birbirleriyle JOIN yapmayı sağlıyor. Bu method parametre olarak sadece alan adı alıyor ve klasik dunder lookup yani; field__field__field şeklinde kullanmayı sağlıyor. ForeignKey ve OneToOne alanlar için çok kullanışlı.

ProductListView’da hemen aşağıdaki düzenlemeyi yaptım;

class ProductListView(ListView):
    def get_queryset(self):
        return Product.objects.select_related('category)

Sorgu tekrarı sayısı düşmüştü ama halen istediğim gibi olmamıştı. Debug Toolbar beni halen uyarıyordu. Bu durumda Django dokümantasyonuna geri döndüm ve asıl sorunun ManyToManyField alanından kaynaklandığını anladım. Django bu tür sorunlar için prefetch_related metodu yapmış. Argüman olarak lookup yani sorgulanacakları alıyor. select_related ile farkı şu, JOIN operasyonunu python katmanında yapıyor ve her ilişkili sorgu için ayrı ayrı işlem yapıyor. Bu sayede select_related’ın yapamayacağı işleri de yapıyor. Özellikle ManyToMany ve ManyToOne için ideal.

Hemen product_filter_feature (ManyToMany olduğu için) ile ilgili işleri de get_queryset’e ekliyorum:

class ProductListView(ListView):
    def get_queryset(self):
        return Product.objects\
            .select_related('category)\
            .prefetch_related('product_filter_feature')

.prefetch_related('product_filter_feature') aslında içeride product_filter_feature.all() yapıyor ve birleştirme işlerini python katmanında yapıp kendince bir caching yapıyor. Bu sayede aynı sorgu tekrar edilmiyor.

Şimdi tekrar baktığımda, başlangıçta 80 tane tekrar eden sorgu varken şimdi sayfanın oluşturulması sonucunda toplam sorgu sayısı 12’ye düştü.

Foto Kredileri