Artículos

Controla el rendimiento de la BD en Django con django-test-query-counter

Presentamos una biblioteca para mantener el rendimiento de la base de datos de aplicaciones Django mediante pruebas. Mide las consultas durante pruebas unitarias y ofrece un comando para comparar conteos de dos ejecuciones, con detalles como trazas de pila. Busca detectar problemas lo antes posible.

Introducción

Cuando un problema ralentiza la aplicación, no es raro relegar el rendimiento al resolverlo. El mismo código puede generar el doble de consultas. Algunas causas comunes:

  • Requisitos nuevos que exigen información de registros relacionados
  • Instancias distintas de QuerySet para la misma consulta.
  • Falta de select_related, prefetch_related o subconsultas en el queryset original.
  • Falta de caché como @lrucache, @cached_property y el framework de caché de Django.

Si una página tarda mucho en cargar, el usuario puede reclamar. Después invertimos mucho tiempo en diagnosticar la lentitud. En mi proyecto, creábamos entidades que generaban 500 o más consultas en una sola solicitud. Detectarlo antes sería mucho más económico. La figura muestra el costo del cambio según el tiempo, también aplicable al rendimiento.

Costo del cambio según el tiempo
Costo del cambio según el tiempo

La documentación de Django describe problemas y soluciones habituales. Herramientas como django-debug-toolbar muestran lo que ocurre en una solicitud, incluido el conteo detallado de consultas. Son excelentes para depurar casos puntuales, pero no para flujos automatizados. Igual que la cobertura de código y las pruebas funcionales, el rendimiento de BD debe medirse y controlarse. Conviene integrarlo al flujo habitual, por ejemplo como comprobación antes de enviar código a master.

La solución

django-query-counter
django-query-counter

Django-test-query-counter, o query-counter, registra cada consulta de las pruebas unitarias bajo la premisa «menos consultas, más velocidad». Controla consultas por solicitud en cada prueba. Tiene dos partes: middleware que genera un JSON con el conteo de una ejecución, y un comando que compara dos archivos y muestra infracciones en la segunda. Puede usarse localmente, pero destaca con CI como Jenkins, donde las pruebas y controles se ejecutan periódicamente. No se integra con una CI específica, pero es sencillo añadir check_query_count a sus tareas.

Por ejemplo, una prueba obtiene una lista de libros y luego información del primero:

class BookTester(TestCase):
    def test_getbook(self):
      # This makes 30 queries
      book_list = self.client.get('/api/books/all')
      self.assertEqual(
        book_list.status_code,
        status.HTTP_200_OK
      )
      first_book = json.decode(book_list.data)

      # This makes 20 queries
      book_info = self.client.get(
        '/api/books/{}'.format(first_book['id'])
      )
      self.assertEqual(
        book_info.status_code,
        status.HTTP_200_OK
      )

Query-counter registró:

  • BookTester.test_getbook (50 consultas)
    * /api/books/all (20 consultas)
    
    * /api/books/5 (30 consultas)
    

El umbral de tolerancia k es porcentual. La comprobación falla si una solicitud supera en más de k por ciento las consultas de la última ejecución exitosa. Si es 10%, /api/books/all tolera como máximo 22 consultas.

Usar la aplicación localmente

Query-counter está en GitHub y PyPI.

  1. Instala desde PIP:

    $ pip install django-request-query-counter

  2. Añádela a INSTALLED_APPS en la configuración:

    INSTALLED_APPS = ( ... 'test_query_count', ... )

  3. Ejecuta las pruebas como siempre:

    $ python manage.py test

  4. Si se instaló correctamente, aparecerá reports/query_count.json en el directorio de la aplicación con el conteo por prueba. Copia el archivo localmente:

    $ cp reports/query_count.json last_query_count.json

  5. Añade nuevas consultas al código, vuelve a ejecutar pruebas y ejecuta check_query_count:

    $ python manage.py check_query_count --last-count-file last_query_count.json

Comparará last_query_count.json con reports/query_count.json e informará cada prueba que excedió el umbral del 10%, el predeterminado. Puedes configurarlo.

Interacción con CI

Puede integrarse fácilmente con Jenkins o Travis. La figura de la sección anterior describe el flujo.

1) Ejecutar pruebas unitarias

2) Ejecutar check_query_count contra query_count.json de la última compilación exitosa. La compilación debe marcarse inestable si hay una infracción.

3) Archivar query_count.json como artefacto de compilación.

Este script bash ilustra el paso 2 para Jenkins. Es similar al caso local, pero descarga query_count.json desde CI.

curl http://yourci.com/yourjob/lastSuccessfulBuild/artifact/reports/query_count.json -o last_query_count.json
python manage.py check_query_count --last-count-file last_query_count.json

Próximos pasos

Todo feedback es bienvenido. Es una biblioteca nueva y añadiremos funciones; puedes contribuir con reportes de errores o pull requests. La hoja de ruta incluye:

  • Trazas de pila detalladas en las consultas.
  • Analizar consultas sin índices mediante SQL explain y generar advertencias.
  • Informe gráfico HTML de consultas ejecutadas por cada línea de código.

“Controla el rendimiento de la BD en Django con django-test-query-counter” de Ignacio Avas está bajo la licencia CC BY SA. Los ejemplos de código fuente están bajo la licencia MIT.

Foto de sophilabs.

Clasificado en Django / Código abierto.