Quando la tua applicazione Django non va abbastanza veloce

Quando la tua applicazione Django non va abbastanza veloce

Django mette molta enfasi nel creare applicazioni velocemente. Questo è grandioso! Sviluppare applicazioni velocemente però non sempre significa sviluppare applicazioni veloci.

In questo talk mostrerò alcuni problemi di performance che ho incontrato (o introdotto) nella mia esperienza di sviluppatore. Vedremo esempi in diversi punti dell’applicazione: dai modelli, passando per le viste fino ai serializzatori. Alcuni di questi si sono rivelati facili da trovare e risolvere, altri subdoli o addirittura imbarazzanti. Tutti fortunatamente risolti.

61ba6f6b1fb82707b9344259f74a81b3?s=128

Riccardo Magliocchetti

May 04, 2019
Tweet

Transcript

  1. 1.

    Quando la tua Quando la tua applicazione Django applicazione Django

    non va abbastanza veloce non va abbastanza veloce Riccardo Magliocchetti Riccardo Magliocchetti
  2. 3.

    Ridiculously fast Ridiculously fast Django was designed to help developers

    take applications from concept to completion as quickly as possible. www.djangoproject.com
  3. 4.
  4. 7.

    1. Don't do it. 2. (for experts only) (for experts

    only) Don't do it yet. Don't do it yet.
  5. 8.

    1. Don't do it. 2. (for experts only) Don't do

    it yet. Not until you have a perfectly clear and unoptimized solution. Michael A. Jackson Principles of Program Design, 1975
  6. 10.

    Quando pensare alle Quando pensare alle performance performance Idealmente prima

    di andare in produzione Code review: Quante query fa questo endpoint?
  7. 15.

    Far meglio Far meglio Migliore algoritmo o struttura dati High

    Performance Python, Ian Ozsvald, Micha Gorelick
  8. 25.
  9. 28.

    Come ottimizzare in pratica Come ottimizzare in pratica 1. Fare

    un database fatto bene 2. Fare meno query 3. Fare query meno costose 4. Fare meno lavoro inutile
  10. 29.

    Un database Un database User <- Profile <- Specific profile

    <- Specific Django reusable app profile
  11. 30.

    Un database più veloce Un database più veloce Custom User

    <- Specific profile <- Specific Django reusable app profile
  12. 31.

    L'ereditarietà concreta dei L'ereditarietà concreta dei modelli Django è una

    trappola modelli Django è una trappola Usa quella astratta o esplicitamente una relazione
  13. 34.

    Salvare solo quello che serve Salvare solo quello che serve

    - score.save() + score.save(update_fields=['points'])
  14. 35.

    Le query N + 1 uccidono le Le query N

    + 1 uccidono le performance dell'applicazione performance dell'applicazione
  15. 36.

    class Room: building = models.ForeignKey(Building) class Appointment: room = models.ForeignKey(Room)

    doctor = models.ForeignKey(Doctor) a = Appointment.objects.get( id=appointment_id ) print('In {} with {}'.format(a.room, a.doctor))
  16. 37.

    Per evitare query N + 1 Per evitare query N

    + 1 select_related(), JOIN in SQL prefetch_related(), join in Python
  17. 38.

    select_related() select_related() class Room: building = models.ForeignKey(Building) class Appointment: room

    = models.ForeignKey(Room) doctor = models.ForeignKey(Doctor) - a = Appointment.objects.get( + a = Appointment.objects.select_related( + 'doctor', 'room__building' ).get( id=appointment_id )
  18. 39.

    select_related() è greedy select_related() è greedy + ).select_related( + 'doctor',

    'room__building' ) ^^^^^^^^^^^^^^^^ SELECT * in JOIN: doctor, room e building
  19. 40.

    Attenzione a select_related() Attenzione a select_related() L'admin se nel list_display

    c'è un solo campo preso da una relazione seleziona tutti i modelli delle relazioni Memory leak in django 1.11?
  20. 42.

    Attenzione alle librerie Attenzione alle librerie Do less work in

    MySerializer Use django-mptt get_children method instead of going straight to the field model. /myapi/ does 40% less queries
  21. 43.

    Il numero di query è testabile Il numero di query

    è testabile django.test.TransactionTestCase.assertNumQue ries() with self.assertNumQueries(2): Person.objects.create(name="Aaron") Person.objects.create(name="Daniel")
  22. 45.

    Indici: se ltriamo o ordiniamo Indici: se ltriamo o ordiniamo

    - complete = models.BooleanField(default=False) + complete = models.BooleanField( + default=False, + db_index=True + )
  23. 46.

    Ordinare non è gratis Ordinare non è gratis class MyAdmin:

    readonly_fields = ['creation_ts'] - ordering = ['creation_ts',] Remove unuseful ordering in MyAdmin Ordering a timestamp without indexes is sloooooow. Now the query takes 30 seconds less!
  24. 47.

    Gli indici non sono gratis Gli indici non sono gratis

    class MyModel: elements = models.ManyToManyField(Element) Indici nella tabella di join: Indici nella tabella di join: 1. PK 2. FK verso MyModel 3. FK verso Element 4. UNIQUE sulle FK
  25. 48.

    Gli indici ci servono sempre? Gli indici ci servono sempre?

    - elements = models.ManyToManyField(Element) + # Be aware we don't have indexes here + elements = ArrayField( + models.IntegerField(), + default=[], + blank=True + )
  26. 49.

    Se gli indici non ci servono Se gli indici non

    ci servono Use an array field for MyContainer elements Instead of using an m2m. We don't need the field for the application, we need it for analytics. This let us save 2GB from tables and indexes.
  27. 51.

    Usare il serializzatore giusto Usare il serializzatore giusto class MySerializer(serializers.ModelSerializer):

    - owner = UserSerializer() + owner = serializers.IntegerField( + read_only=True + )
  28. 52.

    Esplicito meglio di implicito #1 Esplicito meglio di implicito #1

    class MySerializer(serializers.ModelSerializer): class Meta: model = MyModel fields = '__all__'
  29. 53.

    Esplicito meglio di implicito #2 Esplicito meglio di implicito #2

    class MySerializer(serializers.ModelSerializer): class Meta: model = MyModel exclude = ('owner',)
  30. 55.

    Il context ti è amico Il context ti è amico

    class MySerializer: - title = serializers.CharField( - source='assignment.task.title' - ) + title = serializers.SerializerMethodField() + def get_title(self, obj): + titles = self.context.get('titles') + return titles[obj.assignment_id] select_related() e prefetch_related() sarebbero state dispendiose
  31. 56.

    Altre cose da guardare Altre cose da guardare e e

    e la documentazione del Database django ≥ 2 defer() only() union Prefetch aggregate() annotate() Database functions FilteredRelation() EXPLAIN
  32. 57.

    Letture interessanti Letture interessanti The Dramatic Bene ts of Django

    Subqueries and Annotations Django ORM Cookbook Updating a Django queryset with annotation and subquery
  33. 58.

    Conclusioni Conclusioni Misura prima di eventualmente ottimizzare Pensa alle performance

    Piccoli cambiamenti possono dare grandi risultati Conoscere SQL aiuta