Artigos

Controle o desempenho do BD no Django com django-test-query-counter

Apresentamos uma biblioteca para manter o desempenho do banco de dados de aplicações Django por testes. Mede consultas durante testes unitários e oferece um comando para comparar contagens de duas execuções, com detalhes como rastreamentos de pilha. Busca detectar problemas o quanto antes.

Introdução

Quando um problema torna a aplicação lenta, não é raro deixar o desempenho de lado ao resolvê-lo. O mesmo código pode gerar o dobro de consultas. Algumas causas comuns:

  • Requisitos novos que exigem informações de registros relacionados
  • Instâncias diferentes de QuerySet para a mesma consulta.
  • Falta de select_related, prefetch_related ou subconsultas no queryset original.
  • Falta de cache como @lrucache, @cached_property e o framework de cache do Django.

Se uma página demora a carregar, o usuário pode reclamar. Depois, gastamos muito tempo diagnosticando a lentidão. No meu projeto, criávamos entidades que geravam 500 ou mais consultas em uma única solicitação. Detectar antes seria muito mais econômico. A figura mostra o custo da mudança em função do tempo, também aplicável ao desempenho.

Custo da mudança em função do tempo
Custo da mudança em função do tempo

A documentação do Django descreve problemas e soluções comuns. Ferramentas como django-debug-toolbar mostram o que acontece em uma solicitação, incluindo a contagem detalhada de consultas. São excelentes para depurar casos pontuais, mas não para fluxos automatizados. Assim como cobertura de código e testes funcionais, o desempenho do BD deve ser medido e controlado. Convém integrá-lo ao fluxo habitual, por exemplo antes de enviar código para master.

A solução

django-query-counter
django-query-counter

Django-test-query-counter, ou query-counter, registra cada consulta dos testes unitários sob a premissa “menos consultas, mais velocidade”. Controla consultas por solicitação em cada teste. Tem duas partes: middleware que gera um JSON com a contagem de uma execução e um comando que compara dois arquivos e mostra violações na segunda. Pode ser usado localmente, mas se destaca com CI como Jenkins, em que testes e verificações são periódicos. Não se integra a uma CI específica, mas é simples adicionar check_query_count às tarefas.

Por exemplo, um teste obtém uma lista de livros e depois informações do primeiro:

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 registrou:

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

O limite de tolerância k é percentual. A verificação falha se uma solicitação ultrapassa em mais de k por cento as consultas da última execução bem-sucedida. Se for 10%, /api/books/all tolera no máximo 22 consultas.

Usar a aplicação localmente

Query-counter está no GitHub e no PyPI.

  1. Instale pelo PIP:

    $ pip install django-request-query-counter

  2. Adicione a INSTALLED_APPS nas configurações:

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

  3. Execute os testes como sempre:

    $ python manage.py test

  4. Se foi instalado corretamente, aparecerá reports/query_count.json no diretório da aplicação com a contagem por teste. Copie o arquivo localmente:

    $ cp reports/query_count.json last_query_count.json

  5. Adicione consultas ao código, execute os testes novamente e execute check_query_count:

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

Ele comparará last_query_count.json com reports/query_count.json e informará cada teste que excedeu o limite de 10%, o padrão. Você pode configurá-lo.

Interação com CI

Pode ser integrado facilmente com Jenkins ou Travis. A figura da seção anterior descreve o fluxo.

1) Executar testes unitários

2) Executar check_query_count contra query_count.json da última compilação bem-sucedida. A compilação deve ser marcada como instável se houver uma violação.

3) Arquivar query_count.json como artefato de compilação.

Este script bash ilustra o passo 2 para Jenkins. É semelhante ao caso local, mas baixa query_count.json da 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 passos

Todo feedback é bem-vindo. É uma biblioteca nova e adicionaremos funcionalidades; você pode contribuir com relatos de erros ou pull requests. O roteiro inclui:

  • Rastreamentos de pilha detalhados nas consultas.
  • Analisar consultas sem índices por SQL explain e gerar avisos.
  • Relatório gráfico HTML de consultas executadas por cada linha de código.

“Controle o desempenho do BD no Django com django-test-query-counter” por Ignacio Avas está sob a licença CC BY SA. Os exemplos de código-fonte estão sob a licença MIT.

Foto de sophilabs.

Classificado em Django / Código aberto.