Django Performans Optimizasyon İpuçları 1: Indexes

Kader Miyanyedi
5 min readJan 24, 2022

--

Herkese selamlar :) Öncelikle uzun bir aradan sonra tekrar yazı yazmama ve bu yazının konusu/içeriği konusunda bana yardımcı olan takım arkadaşım Utkucan’a teşekkür ederim. Django uygulamaları yazarken zaman zaman sorgularımızın yavaş çalıştığını gözlemleriz. Bunun birçok sebebi bulunabileceği gibi birçok çözüm yöntemi de bulunmaktadır. Bu yazıda django indexes ile sorgularımızın nasıl daha hızlı çalıştığını inceleyeceğiz.

Index Nedir ?
SQL index READ sorgularımızı hızlandırmaya yarayan bir veri yapısıdır. İndekseleme yapılmış bir tabloda istediğimiz veriyi bulmak için tablonun tamamı okunmaz. Indeks key aracılığı ile veri tabloda hızlıca bulunur.
Örneğin deterjan almak için bir markette gittik. Deterjanınızı bulmak için tüm marketi dolaşmaz, temizlik ürünleri tabelasının bulunduğu bölüme gideriz. Aradığımız ürün için bir tabela bulunmaz ise tüm marketi dolaşırız.
Burada tabelama bizim için indeksleme olabilir.

İndekslemenin en büyük avantajı sorguları hızlandırmaktadır.

Madem sorgumuzu hızlandırıyor, her yerde index kullanalım?

Maalesef işler o şekilde yürümüyor. İndeksleme ile birlikte;

  • Oluşturulan her index diskte yer kaplayacaktır.
  • Write/Delete/Update sorgularında index çalışacaktır ve bu sorgu süresini uzatacaktır.
  • PostgreSQL bir sorguyu çalıştırırken parser-analyze-rewriter-execute aşamalarından geçirir. Analyze aşamasında sorguda index kullanılacak mı, hangi index kullanılmalı veya sequence ile arama mı yapmalı gibi soruların cevaplarını bulur. Bu sebeple tabloda yer alan index sayısı çoğalırsa analyze süresi de uzayacaktır.

İndexler farklı algoritmalar kullanabilirler. Kullanılan algoritmaya göre Btree, Hash, gin gibi farklı index türleri mevcuttur. İhtiyaç durumuna göre hangi index türünün kullanılacağına karar verilmelidir. PostgreSQL default olarak Btree index türünü kullanmaktadır.

Index Nasıl Oluşturulur?

Django modellerini yazarken index oluşturmak için 2 yöntem mevcuttur:

  1. Index oluşturmak için bir alan tanımlanırken db_index parametresi True verilir.
class Author(models.Model):
first_name = models.CharField(max_length=50, db_index=True)

2. Alanları tanımladıktan sonra class Meta altında indexlemek istenilen alanları belirtebilirsiniz.

class Author(models.Model):
first_name = models.CharField(max_length=50, db_index=True)
last_name = models.CharField(max_length=50)
class Meta:
indexes = [
models.Index(fields=['last_name',])
]

Index alanlarını belirlerken index türünü de belirleyebiliriz.

from django.contrib.postgres.indexes import BrinIndexclass Meta:
indexes = (BrinIndex(fields=["username"])

Index kullanımının bize kazandırdığı hızı inceleyelim. Index kullanmadan bir model oluşturduktan sonra veritabanına faker paketi kullanarak 100K veri yükledim. Verileri oluşturma işlemini burada bahsetmeyeceğim.

Belirtilen username değerine göre author objelerini getiren bir fonksiyon yazalım:

def get_author(username):
return Author.objects.filter(username=username)
Index kullanımı yok: 0.377ms
Index kullanımı var: 0.273ms

Index kullanımı sonrasında sorgumuzun 100 ms daha kısa çalıştığını gözlemledik.

AND/OR İçeren Sorgular

Sorgularınızda AND / OR kullanıyorsanız bu alanları da index’e eklemek sorgularımızın hızlanmasında faydalı olacaktır. first_name ve last_name bilgilerine göre author objesini getiren bir fonksiyon yazalım:

def get_author_by_name(first_name, last_name):
return Author.objects.filter(
first_name=first_name, last_name=last_name
)
Index kullanımı yok: 2.677ms
Index kullanımı var: 0.274ms

Örnekte AND/OR işlemlerini içeren bir sorguda index kullanımı 2.403ms daha kısa bir sürede sonucu getirmektedir.

Author.objects.filter(first_name=first_name, last_name=last_name)
// Execution time: 0.000265s
Author.objects.filter(last_name=last_name, first_name=first_name)
// Execution time: 0.000269s

AND/OR işlemi içeren sorgularda alanların yer değiştirmesi index kullanımını etkilememektedir.

Lower/Upper İçeren Sorgular

Sorgularınızda lower, upper gibi fonksiyonları kullanıyorsanız index oluştururken bu fonksiyonları da kullanmanız gerekmektedir.

Index(Lower('first_name'), Lower('last_name'),            
name="lower_username_index"
)
Index kullanımı yok: 4.701ms
Index kullanımı var: 2.811ms

Kullanılan index türünün desteklediği sorgulardan emin olun. Örneğin Hash index sadece equal sorgularında çalışmaktadır.

Sorgularınız tablonun büyük bir kısmını kapsıyor ise index’in çalışmama ihtimali vardır. Örneğin 1'den 1000'e kadar sayıların tutulduğu bir toplada
WHERE sayi ≥ 10 sorgusunu çalıştırırsak tablonun büyük bir kısmını getireceği için burada index çalışmayabilir.

Partial Index

Partial Index sayesinde verdiğimiz koşulları sağlayan satırları index’e ekleyebiliriz. Bu sayede index daha az yer kaplayacaktır. Partial Index kullanılacağı zaman name argümanı zorunludur.
Örneğin son kullanıcıya sadece is_active=True olan içeriklerin gösterileceği bir senaryoyu düşünelim. Sorgularımız genel olarak şu şekilde olacaktır:

Model.objects.filter(is_active=True)

Aktif olmayan içeriklerin index’e eklenmesine gerek yoktur.

Index("is_active", 
name="is_active_index",
condition=Q(is_active=False)
)

Covering Index

Index(name="is_active_index",   
fields=['is_active'],
include=['username']
)

Include argümanını kullanarak covering index oluşturabiliriz. Bu sayede is_active alanı ile index only scan yapıldığı zaman include içinde belirtilen alanlar da index içinde getirilir.

Concurrently Index

Varsayılan bir şekilde index eklenirken tablo kilitlenebilir. Bu durumda diğer işlemler yine de tabloyu okuyabilir, ancak tabloya satır eklemeye, güncellemeye veya silmeye çalışırlarsa, index oluşturma tamamlanana kadar engellenebilir.
Canlıya alınmış bir projede bu durum problem oluşturabilir. Büyük bir tabloda indekslenmesi saatler alabilir veya daha küçük bir tablo için bile indeksleme canlıdaki bir projeyi kilitleyerek kayıplara yol açabilir.

PostgreSQL yazma işlemlerini kilitlemeden dizin oluşturmayı destekler. PostgreSQL Concurrently Index ile tabloyu kilitlemeden hem sorgulara cevap verebilir hemde indeksleme işlemini gerçekleştirebilir.
Bu durum indeksleme işlemini uzayacaktır. İndeksleme sırasında SIGTERM, sunucunun kapanması vb. durumlar oluşursa index INVALID şeklinde gözükecektir. REINDEX ile tekrardan index oluşturulması gerekmektedir.

PostgreSQL örneği:

create index concurrently user_idx on users(username);

Django örneği:

Djangoda concurrently index kullanmak için makemigrations komutu sonrası oluşturulan migrate dosyasında değişiklik yapılabilir. Migrate dosyasındaki AddIndex sınıfı yerine AddIndexConcurrently sınıfını kullanmamız gerekmektedir. Bu değişiklik migrate komutu koşturmadan önce gerçekleştirilmelidir.

Bu yazıda index kullanımına değindik. Özünde ve doğru index kullanımı ile sorgularımızı nasıl hızlandırabileceğimizi öğrendik. Bir sonraki yazıda görüşmek üzere…

Kaynaklar:
PostgreSQL Dökümanı — Concurrently Index
DjangoCon-Europe

--

--