DEV Community

Nahuel Segovia
Nahuel Segovia

Posted on

Django eficiente - select_related

Uno de los grandes desafíos de una aplicación cuando esta está siendo utilizada en producción, es que tenga un tiempo de respuesta razonable.

Empecemos con un problema sencillo, supongamos que tenemos un modelo de subforo de un foro, en donde cada sufbforo puede tener un comentario, y ese comentario a su vez puede pertenecer a un subforo en concreto.


class SubForo(models.Model):
name = models.CharField(max_length=255, null=True)
creator = models.CharField(max_length=255, null=True)
hashtags = models.CharField(max_length=255, null=True)


class Comentario(models.Model):
comment = models.TextField()
subforo = models.ForeignKey(SubForo, on_delete=models.CASCADE)
Enter fullscreen mode Exit fullscreen mode

Ahora para poder listar todos los comentarios con el foro al que pertenece, lo más común es encontrar código como este:

@api_view(["GET"])
@permission_classes([permissions.IsAuthenticated])
def obtener_comentarios_foro(request):
  comentarios = Comentario.objects.all()
  comentarios_con_foro = {}
  for comentario in comentarios:
      comentarios_con_foro['comentario'] = comentario.comment
      comentarios_con_foro['foro'] = comentario.subforo.name
  print(comentarios_con_foro)
Enter fullscreen mode Exit fullscreen mode

El problema de esto es que estamos accediendo a la base de datos por lo menos, dos veces:

  • Cuando vamos a buscar los comentarios
  • Por cada comentario vamos a ir a buscar el subforo al que pertenece

es decir que si tengo 10 comentarios, iríamos a buscar todos los comentarios y además hacer una query para ir a buscar el subforo al que pertenece ese comentario, la querie seria algo como esto:

SELECT
 `comentario`.`id`,
 `comentario`.`comment`,
 `comentario`.`subforo_id`
FROM
 `comentario`;

#QUERIES A EJECUTAR UNA VEZ QUE OBTENEMOS EL COMENTARIO CON SU SUBFORO_ID CORRESPONDIENTE:
SELECT `subforo`.`id`, `subforo`.`name`, `subforo`.`creator`, `subforo`.`hashtags` FROM `subforo` WHERE `subforo`.`id` = 1 LIMIT 21;
SELECT `subforo`.`id`, `subforo`.`name`, `subforo`.`creator`, `subforo`.`hashtags` FROM `subforo` WHERE `subforo`.`id` = 1 LIMIT 21;
SELECT `subforo`.`id`, `subforo`.`name`, `subforo`.`creator`, `subforo`.`hashtags` FROM `subforo` WHERE `subforo`.`id` = 1 LIMIT 21;
SELECT `subforo`.`id`, `subforo`.`name`, `subforo`.`creator`, `subforo`.`hashtags` FROM `subforo` WHERE `subforo`.`id` = 2 LIMIT 21;
SELECT `subforo`.`id`, `subforo`.`name`, `subforo`.`creator`, `subforo`.`hashtags` FROM `subforo` WHERE `subforo`.`id` = 2 LIMIT 21;
SELECT `subforo`.`id`, `subforo`.`name`, `subforo`.`creator`, `subforo`.`hashtags` FROM `subforo` WHERE `subforo`.`id` = 3 LIMIT 21;
SELECT `subforo`.`id`, `subforo`.`name`, `subforo`.`creator`, `subforo`.`hashtags` FROM `subforo` WHERE `subforo`.`id` = 4 LIMIT 21;
SELECT `subforo`.`id`, `subforo`.`name`, `subforo`.`creator`, `subforo`.`hashtags` FROM `subforo` WHERE `subforo`.`id` = 4 LIMIT 21;
SELECT `subforo`.`id`, `subforo`.`name`, `subforo`.`creator`, `subforo`.`hashtags` FROM `subforo` WHERE `subforo`.`id` = 4 LIMIT 21;
SELECT `subforo`.`id`, `subforo`.`name`, `subforo`.`creator`, `subforo`.`hashtags` FROM `subforo` WHERE `subforo`.`id` = 5 LIMIT 21;
Enter fullscreen mode Exit fullscreen mode

Quizás en un proyecto chico esto no sea un gran problema, pero ahora imaginemos que tenemos 600 comentarios en el subforo… sería totalmente ineficiente hacer un select por cada uno de esos comentarios.

¿La solución? Usar select_related, de esta forma vamos a hacer una query mucho más larga pero el tiempo de respuesta será mucho menor:

@api_view(["GET"])
@permission_classes([permissions.IsAuthenticated])
def obtener_comentarios_foro(request):
  comentarios = Comentario.objects.select_related('subforo').all()
  comentarios_con_foro = {}
  for comentario in comentarios:
      comentarios_con_foro['comentario'] = comentario.comment
      comentarios_con_foro['foro'] = comentario.subforo.name
  print(comentarios_con_foro)
Enter fullscreen mode Exit fullscreen mode

la query nos quedaría de esta manera:

SELECT
"comentario"."id",
"comentario"."comment",
"comentario"."subforo_id",
"subforo"."id",
"subforo"."name",
"subforo"."creator",
"subforo"."hashtags",
FROM
 "comentario"
 INNER JOIN "subforo" ON (
   "comentario"."subforo_id" = "subforo"."id"
 )
Enter fullscreen mode Exit fullscreen mode

Demostraciones de un proyecto con datos reales

Tiempo de respuesta de una api utilizando select_related:

Image description

Tiempo de respuesta de una api sin utilizar select_related:

Image description

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up