Slide 1

Slide 1 text

Django ORM at Scale to win! Recomendaciones y otras yerbas! Martin Alderete @alderetemartin [email protected]

Slide 2

Slide 2 text

QuerySet los “Vagos” Los “QuerySet” son instancias con la propiedad de ser “lazy” (vagos) No implican un “hit” a la base de datos Se pueden encadenar No realiza el “query” hasta que le preguntemos su valor Permiten visualizar el SQL por medio del atributo “query”

Slide 3

Slide 3 text

QuerySet Los QuerySet se pueden evaluar: ● Iteración ● Slicing ● Pickling/Caching ● repr() ● len() ● list() ● Operación Booleana

Slide 4

Slide 4 text

QuerySet ● QuerySet → Clase base (filter(), all()) ● ValuesQuerySet → Subclase que retorna dicts cuando se evalua ● ValuesListQuerySet → Subclase que retorna tuples cuando se evalua

Slide 5

Slide 5 text

QuerySet “anti-patterns” queryset = MyModel.objects.filter(**f) len(queryset) → Forzamos la evaluación e iteración para un len() if queryset: → Forzamos la evaluacion por una operacion booleana # code for obj in huge_queryset: → La memoria se va por las nubes! #code

Slide 6

Slide 6 text

QuerySet “anti-patterns” mejor! queryset = MyModel.objects.filter(**f) queryset.count() → Resolver las cantidades en base de datos! (COUNT()) if queryset.exists(): → Resolver la existencia en base de datos! (“LIVIANO”) # code for obj in huge_queryset.iterator(): → Mejora el uso de memoria elimina el cache! #code

Slide 7

Slide 7 text

Relaciones: “fetch” “Django automáticamente NO sigue la relaciones de un modelo” Ejemplo: cars_qs = Car.objects.filter(**kwargs) for car in cars_qs: print(car.owner.full_name) Cantidad de queries = O(n) + 1 (n: número de cars)

Slide 8

Slide 8 text

Relaciones: Controlar la cantidad de queries “Si al hacer obtener un modelo necesitamos sus relaciones debemos controlar la cantidad de queries” cars_qs = Car.objects.select_related(“owner”).filter(**kwargs) for car in cars_qs: print(car.owner.full_name) # No hay hit a la DB Cantidad de queries = 1

Slide 9

Slide 9 text

Relaciones: Controlar la cantidad de queries “Si al hacer obtener un modelo necesitamos sus relaciones debemos controlar la cantidad de queries” cars_qs = Car.objects.prefetch_related(“vendors”).filter(**kwargs) for car in cars_qs: for vendor in car.vendors.all(): print(vendor.name) # No hay hit a la DB Cantidad de queries = 2

Slide 10

Slide 10 text

Relaciones: Controlar la cantidad de queries select_related() → Genera un JOIN en SQL ForeignKey, OneToOne Relaciones de Relaciones prefetch_related() → Una consulta por relación y hace un “JOIN” en memoria ManyToMany Relaciones de Relaciones (Agrega 1 query cada nivel) Company.objects.prefetch_related(“cars__vendors”) Reducir/Controlar la cantidad de queries nos da un BOOST de performance!

Slide 11

Slide 11 text

Relaciones: Summary Debemos conocer y controlar nuestras consultas! select_related() Minimiza las consultas pero requiere consultas más caras en memoria para el motor! Generalmente es un BOOST! prefetch_related() Minimiza las consultas pero requiere mayor esfuerzo en Python! Generalmente es un BOOST para M2M! Evaluar los trade-off!

Slide 12

Slide 12 text

Proyecciones: Obtener lo necesario SELECT FROM WHERE ; SELECT plate_number FROM cars; Manipular la “proyección” nos da un BOOST porque: Genera consultas más específicas en el motor Potencialmente no necesitamos la “traducción” del ORM Quiero eso en Django!

Slide 13

Slide 13 text

Proyecciones: Obtener lo necesario (dict/tuple) value_qs = Car.objects.filter(**kwargs).values(“plate_number”) ValuesQuerySet: QuerySet basado en dicts. valuelist_qs = Car.objects.filter(**kwargs).values_list(“plate_number”) ValuesListQuerySet: QuerySet basado en tuples. Generan consultas más específicas y al retornar dicts/tuples no necesitan crear instanciar , no agregan ningun overhead en Python (chau ORM!)

Slide 14

Slide 14 text

Proyecciones: Obtener lo necesario (instancias) qs = Car.objects.filter(**kwargs).only(“plate_number”) qs = Car.objects.filter(**kwargs).defer(“plate_number”) Generan consultas más específicas pero deben instanciar, los fields se cargan de forma “lazy” dependiendo el caso. Al ser instancias podemos utilizar sus métodos. Tiene un leve overhead debido a que el ORM debe instanciar.

Slide 15

Slide 15 text

Ganar Performance al modificar (bulk) Cuando queremos hacer un “update” muchas veces hacemos “fetch” y ”save”, sin embargo lo que queremos es un UPDATE de SQL. qs = Car.objects.filter(**kwargs) for car in qs: car.status = INACTIVE car.save() qs = Car.objects.filter(**kwargs) qs.update(status=INACTIVE) F() Permiten tomar el valor de la DB!

Slide 16

Slide 16 text

Ganar Performance al crear (bulk) Cuando queremos hacer un “create” muchas veces hacemos múltiples ”save”, sin embargo lo que queremos es un BULK INSERT de SQL. for d in data: car = Car(**d) car.save() cars = [Car(**d) for d in data] Car.objects.bulk_create(cars) OJO las Signals!

Slide 17

Slide 17 text

Managers: Logica encapsulada Los Managers son la interface que tenemos en el ORM para interactuar con la DB. Todos los modelos tiene al menos un Manager. Podemos crear Manager personalizados para: Customizar el queryset inicial Agregar métodos

Slide 18

Slide 18 text

Managers: Customizar el QuerySet class LogicalDeleteManager(models.Manager): def get_queryset(self): # queryset instance of models.QuerySet queryset = super(LogicalDeleteManager, self).get_queryset() queryset = queryset.filter(removed=False) return queryset Todas las queries que hagamos usando este manager no importa de donde encapsulan la misma logica!! DRY BABY!

Slide 19

Slide 19 text

Managers: Agregar metodos ( Django < 1.7) class CustomManager(models.Manager): def available_for_user(self, user): condition = Q(share=True) | Q(owner=user) queryset = self.filter(condition) return queryset MyModel.objects.available_for_user(u) No permite encadenar llamadas! Para eso debemos crear un CustomQuerySet con la misma API que el Manager…. =(!

Slide 20

Slide 20 text

Managers: Agregar metodos ( Django < 1.7) class CustomQuerySet(models.QuerySet): def available_for_user(self, user): condition = Q(share=True) | Q(owner=user) queryset = self.filter(condition) return queryset class CustomManager(models.Manager): def get_queryset(self): return CustomQuerySet(self.model, using=self._db) def available_for_user(self, user): queryset = self.get_queryset().available_for_user(user) return queryset

Slide 21

Slide 21 text

Managers: Agregar metodos ( Django > 1.7) class CustomQuerySet(models.QuerySet): def available_for_user(self, user): condition = Q(share=True) | Q(owner=user) queryset = self.filter(condition) return queryset class MyModel(models.Model): objects = CustomQuerySet.as_manager() MyModel.objects.available_for_user(u).other_custom_method()

Slide 22

Slide 22 text

Extra: Mysql + get_or_create() ~ Race-Condition MySQL por default utiliza el ISOLATION Level “REPEATABLE-READ” lo cual rompe el algoritmo que usa Django con “get_or_create” (READ-COMMITED) cuando empezamos a tener concurrencia esto puede ser un dolor de cabeza!

Slide 23

Slide 23 text

Extra: Mysql + get_or_create() ~ Race-Condition REPEATABLE-READ: “Al iniciar una transaccion hace un snapshot del conjunto y futuras lecturas del mismo conjunto darán los mismos resultados”

Slide 24

Slide 24 text

Extra: Mysql + get_or_create() ~ Race-Condition La forma “más” segura de corregir esto es en el settings, asi no dependemos de la configuracion del MOTOR. DATABASES = { ‘default’: { # mas cosas 'OPTIONS' : { 'init_command': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED' } }

Slide 25

Slide 25 text

“Conclusiones” ● Conocer nuestras queries. ● Lamentablemente debemos saber SQL y ser capaces de analizarlo. ● Utilizar queryset.query para entender que estamos haciendo. ● Utilizar EXPLAIN para ver cómo se comporta el motor (Lea su doc). ● Probar estrategias de “fetch” / Optimizar las proyecciones. ● No solo existe filter() (managers, queryset). ● Aprovechar al máximo que los QuerySet son “vagos” y posponer la evaluación. ● Utilizar operaciones en bulk (disminuye el overhead). ● Los manager son excelentes y ayudan a NO repetir código. ● Los managers permiten compartir lógica por TODO django de forma segura.

Slide 26

Slide 26 text

Muchas Gracias! ¿Preguntas? Martin Alderete @alderetemartin [email protected]