Django Sorgularını Optimize Edelim!

Oğuzhan Çelikarslan
3 min readNov 9, 2020

--

Zaman zaman Django ile yazmış olduğunuz projenizin, bazı noktalarda çok yavaş çalıştığını fark etmiş olabilirsiniz. Bunun büyük bir sebebi, Django ile yaptığınız sorgular ile ilgili olabilir. Bu makalede sorgularınızı nasıl optimize edebilir ve daha hızlı çalıştırabilirsiniz gelin inceleyelim!

Hazırlık

Makale boyunca aşağıda ki Student ve Grade tabloları ile uğracağız. Projemizde binlerce öğrenci olduğunu ve bu binlerce öğrenciye ait çokça sayıda notlar olduğunu varsayalım.

Student ve Grade modelleri

Analiz Vakti

Biz Django’da sorgu yaptığımızda arka tarafta hangi SQL kodunun kullanıldığını ve bu SQL kodunun çalışmasının ne kadar zaman aldığını görebilmek için Django’nun Log sisteminden yardım alacağız. Her sorgu ile ilgili bilginin sql.log adında bir dosyaya otomatik olarak yazılması için, LOG ayarlarını içeren aşağıda ki sözlüğü, settings.py dosyası içerisine yapıştıralım.

Django Logging Ayarları

Tüm ayarlarımızın istediğimiz gibi çalıştığını görmek için aşağıda ki gibi bir kayıt oluşturalım ve sql.log dosyasına ne yazıldığını görelim.

student = Student()
student.first_name = "1"
student.last_name = "2"
student.save()

Sql.log dosyasının içerisini açıp baktığınızda aşağıda ki gibi bir sonuç alacaksınız.

(0.002) INSERT INTO "student_student" ("first_name", "last_name") VALUES ('1', '2'); args=['1', '2']

Bu loga bakarak bir öğrenci eklemek için yapılacak sorguyu ve bu sorgunun ne kadar zaman aldığını (0.002) görebilirsiniz.

Toplu Kayıt

Aşağıda ki kod ile beraber 1000 adet öğrenci oluşturup daha sonra her bir öğrenci için 10 adet not girişi yapıyoruz.

for i in range(1000):
student = Student()
student.first_name = str(i)
student.last_name = str(i)
student.save()
for j in range(10):
grade = Grade()
grade.student = student
grade.course = "Math"
grade.grade = j*10
grade.save()

Bu işlemin bitmesi, tam olarak 9.45 saniye sürerken log dosyasında 11000 yeni kayıt oluşmuş oldu.

Peki bu işlemi toplu şekilde yapmış olsaydık? Yani tüm öğrencileri tek seferde oluşturup, tüm bu öğrencilerin notlarını da tek seferde eklemiş olsaydık?

students = []
grades = []
batch_size = 500
for i in range(1000):
student = Student()
student.first_name = str(i)
student.last_name = str(i)
students.append(student)
Student.objects.bulk_create(students, batch_size)
for student in Student.objects.all():
for j in range(10):
grade = Grade()
grade.student = student
grade.course = "Math"
grade.grade = j*10
grades.append(grade)
Grade.objects.bulk_create(grades, batch_size)

Bu işlem ise 0.26 saniye sürdü ve log dosyasını açıp incelediğimizde 5 satır olduğunu göreceksiniz.

Bu da 36 kat daha hızlı demektir!!! Hemde kodun ortasında ki ekstra olarak Student modeline yapılmış olan sorgu olmasına rağmen.

Toplu Güncelleme

Buraya kadar her şey güzel peki ya toplu güncelleme yapmak istersek? Varsayalım ki bütün öğrencilerin notunu 100 olarak güncellemek istiyoruz.

grades = Grade.objects.all()
for grade in grades:
grade.grade = 100
grade.save()

0.455 saniye sürdü. 10 000 adet kayıt ve SQLite kullandığımızı göz önüne alırsak hiç fena değil :).

Güzel, şimdi ise toplu güncelleme yapalım.

batch_size = 500
grades = Grade.objects.all()
for grade in grades:
grade.grade = 100
Grade.objects.bulk_update(grades, ['grade'], batch_size=batch_size)

0.11 saniye sürdü!

Veri Ön Yükleme(select related)

Şimdi ise Grade(Notları) tablosunu döngü içerisine alalım (itere) edelim ve tüm öğrencilerin tüm notlarını ekrana bastıralım.

grades = Grade.objects.all()
for grade in grades:
print(f"The student {grade.student.first_name} has got {grade.grade}%")

4.9 saniye ve 10 000 sorgu!

Gelin select_related kullanarak aynı işlemi 0.19 saniye de çalıştıralım:

grades = Grade.objects.select_related("student").all()
for grade in grades:
print(f"The student {grade.student.first_name} has got {grade.grade}%")

ve bu işlem sonunda sadece 1 SELECT sorgusu yapılmış olacak.

Aynı query içerisinde bütün ilişkili verileri çekmiş olduğumuzdan dolayı tek bir sorgu yapmış olduk.

Toplu Olarak Seçme

Diyelim ki, herhangi bir kurstan 100 almış öğrencileri listeleyelim.

“select_related” kullanmadığımıza dikkat edelim.:

ids = []
grades = Grade.objects.all()
for grade in grades:
if grade.grade == 100:
ids.append(grade.student_id)
# Yukarıda ki kodda notu 100'e eşit olan tüm öğrencileri idsler
# dizisi içerisine kayıt ediyorum.
# Aşağıda ki kod ile birlikte tüm idlere ait kayıtları tek seferde
# çağırıyorum.

students = Student.objects.in_bulk(ids)
for student in students:
print(student.first_name, student.last_name)

Bu kod ise 0.19 saniye de çalışır.

Bu eğitim yazısında Türkçe kaynak olması açısından Timur Bakibayev’in harika içeriğini sizlere hafifte kendi yorumumla sunmaya çalıştım. Hatalarımı varsa bunları duymaktan mutluluk duyarım ve kendilerine çok ama çok teşekkür ederiz.

Oğuzhan Çelikarslan

--

--

Oğuzhan Çelikarslan
Oğuzhan Çelikarslan

Written by Oğuzhan Çelikarslan

Python enthusiast who focuses on the problem, break down it, and solves it. https://oguzhann.net

No responses yet