27

Optimización de Serializadores en Django

8000Puntos

hace 5 años

¿Qué es un serializador?

Los serializadores son unos de los componentes más poderosos que tiene Django Rest Framework. Estos permiten que estructuras complejas y modelos de nuestro proyecto en Django sean convertidos a estructuras nativas de Python y puedan ser convertidas fácilmente en JSON o XML. El uso de ellos es muy extendido y uno de los temas claves que tocaremos en este post es la optimización de serializadores.

Lógica del proyecto

Imaginemos que tenemos una estructura básica de Country y City donde una ciudad pertenece a un país, así:

    # models.py
    
    from django.db import models
classCountry(models.Model):
    		name = models.CharField(max_length=55)
    
    
    classCity(models.Model):
    		name = models.CharField(max_length=55)
    		country = models.ForeignKey(
    				Country, related_name='cities', on_delete=models.SET_NULL)

El viewsets es el lugar donde declaramos la lógica de nuestra vista, con Django Rest Framework en muy pocas líneas declararemos lo necesario para que funcione nuestra lógica

    # viewsets.py
    
    from rest_framework import viewsets
    
    from .models import Country
    from .serializers import CountrySerializer
classCountryViewSet(viewsets.ReadOnlyModelViewSet):
    		...
    		queryset = Country.objects.all()
    		serializer_class = CountrySerializer
		    model = Country
    		...

En los serializadores es donde escribiremos la lógica de la estructura final que será la respuesta al request que haremos a nuestro proyecto

# serializers.pyfrom rest_framework import serializers
    from .models import Country, City
    
    
    classCitySerializer(serializers.ModelSerializer):classMeta:
            model = City
            fields = (
                'id',
                'name',
            )
    
    
    classCountrySerializer(serializers.ModelSerializer):
        cities = CitySerializer(many=True)
        classMeta:
            model = Country
            fields = (
                'id',
                'name',
                'cities',
            )

Antes de continuar analicemos más a fondo lo que tenemos escrito más arriba:

  1. Dos modelos que representan las entidades de City y Country, donde City contiene el foreign key a Country
  2. Un ViewSet donde declaramos en el atributo queryset, la sentencia que nos buscará todos registros existentes de Country y que los serializará con CountrySerializer
  3. En CountrySerializer pedimos que nos muestre los datos según la estructura mostrada en el atributo fields. Lo importante acá, y nos servirá luego para hacer la optimización, es el nested serializer que declaramos para el atributo cities. Esto básicamente indica que todas las cities asociadas al Country serán serializadas con CitySerializer

Si por ejemplo hacemos una petición GET al endpoint country/1/ tendremos la siguiente estructura de respuesta:

    {
        "id": 1,
        "name": "Colombia",
        "cities": [
            {
                "id": 1,
                "name": "Bogotá"
            },
            {
                "id": 2,
                "name": "Medellín"
            },
            {
                "id": 3,
                "name": "Cali"
            }
        ]
    }

Problema del N+1

La lógica que declaramos en los serializadores nos permite tener estructuras muy ordenadas como esta que obtuvimos, pero cuando nuestro proyecto es muy grande y procesa una cantidad de requests alta es necesario pensar en todos los queries que se hacen a la base de datos.

En este caso y con esta respuesta tenemos los siguientes queries:

  1. Un query para obtener el country de id=1
  2. Tres queries para obtener todos las city que tienen como foreign key=1

En total ejecutamos 4 queries a la base de datos para obtener la información. Este problema es muy común en programación y se llama el problema del N+1, donde 1 representa al objeto base de la consulta (en este caso Country) y N representa a la N cantidad de atributos relacionados a él (en nuestro caso City).

Eager Loading

Eager Loading es un concepto muy utilizado para resolver el problema del N+1, básicamente disminuye las consultas con un proceso más inteligente para hacer queries a la base de datos; en nuestro caso, en vez de hacer cuatro peticiones para solicitar la información de City hace una sola consulta y precarga la información de estas.

Django permite utilizar eager loading a través del método prefetch_related, este es el que implementaremos para optimizar las consultas. El cambio no es tan grande a nivel de código, solo tenemos que sobreescribir el método get_queryset de CountryViewSet, así:

# viewsets.pyfrom rest_framework import viewsets
    from django.db.models import Prefetch
    from .models import Country
    from .serializers import CountrySerializer
    
    
    classCountryViewSet(viewsets.ReadOnlyModelViewSet):
    
    		...
        queryset = Country.objects.all()
        serializer_class = CountrySerializer
        ...
    
        defget_queryset(self):
            queryset = super().get_queryset()
            queryset = queryset.prefetch_related(
                Prefetch('cities')
            )
            return queryset

Optimización del serializador

Sobreescribir el método get_queryset nos permite ajustar el comportamiento y modificar la consulta conforme a lo que necesitemos, en este caso, que nos precargue los datos de cities asociados al Country que se consulta.

Si hacemos de nuevo el llamado al endpoint country/1/ no notaremos cambios en el JSON de la respuesta, pero sí habrá una disminución en la cantidad de queries a la base de datos, en lugar de las 4 que teníamos al inicio se disminuyó a 2, dado que la primera consulta es para traer la información de Country y la segunda es para traer la información de las ciudades asociadas.

De esta forma y aplicando unos cambios muy pequeños en la lógica de nuestro código podemos mejorar el rendimiento y la velocidad de respuesta del proyecto.

En el Curso Avanzado de Django podrás aprender muchos conceptos como este y tendrás una aplicación tan rápida que tus usuarios amarán usarla.

Eduardo
Eduardo
walis85300

8000Puntos

hace 5 años

Todas sus entradas
Escribe tu comentario
+ 2
Ordenar por:
2
43519Puntos

Excelente post! Una pregunta, de qué forma puedo ver cuántas cosultas se hacen a la base de datos?

Preferiblemente una que pueda ejecutar desde el shell interactivo de Django!

1
636Puntos

Creo que en este ejemplo se debió utilizar el comando select_related() el prefetch related() se utilizaría cuando realizarías la consulta en Many To Many, con el seletc_related() se haría la consulta con los inner joins correspondientes

defget_queryset(self):
		queryset = super().get_queryset()
		queryset = queryset.prefetch_related('cities')
		return queryset

Que alguien me corrija si me equivoco, saludos!

4
8000Puntos
5 años

El select_related se usaría si quisiéramos traer el país asociado a la ciudad, como en este caso son las ciudades asociadas al país usamos prefetch_related

1
316Puntos

como puedo trabajar imagenes con los serializadores

1
1375Puntos

Excelent post.

Btw, está roto el link “Curso Avanzado de Django”