Press ESC to close

Django Sorgularını Optimize Edelim!


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.

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ındabir 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.

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 11000yeni 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.26saniye 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.19saniye de çalışır.

Bu eğitim yazısında Türkçe kaynak olması açısından Timur Bakibayev’inharika 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.

Leave a Reply

Your email address will not be published. Required fields are marked *