Para onde o tempo realmente vai

Prévia grátis 4 min de leitura

Este curso é sobre um truque conhecido. Rodar a suíte de testes contra um banco SQLite em memória em vez de um banco real, e ver ela ficar mais rápida. O truque é real. Se ele ajuda você é uma pergunta com resposta mensurável, e medir primeiro é a disciplina inteira.

Então aqui está a medição, tirada deste repositório.

A suíte

$ bundle exec rspec --profile 8
Finished in 1 minute 11.31 seconds (files took 2.86 seconds to load)
1206 examples, 1 failure

Duas execuções ficaram em 70,3 e 71,3 segundos, com cerca de 2,9 segundos de carga cada. (A única falha é um spec de fuso horário que depende do tzdata da máquina local, não de nada deste curso.) Ou seja: 1.206 exemplos, mais ou menos 70 segundos rodando, e cerca de 3 segundos de boot antes do primeiro exemplo.

Isso dá em média cerca de 58 milissegundos por exemplo. Antes de puxar qualquer otimização, a pergunta é do que esses milissegundos são feitos.

A ida e volta ao banco

adapter: PostgreSQL
SELECT 1 x2000, three runs: 0.062, 0.043, 0.061ms each
savepoint txn x200: 10.2ms, 0.051ms each

Cerca de 0,05 milissegundo por consulta. Isso é um PostgreSQL local por socket de domínio Unix, e é o número sobre o qual todo o argumento do banco em memória se apoia.

Um banco SQLite em memória remove o socket, então remove alguma fração de 0,05ms por consulta. Suponha que a suíte emita cinquenta consultas por exemplo: uma estimativa generosa para uma suíte com esta quantidade de specs de requisição. Isso é 1.206 × 50 × 0,05ms, ou cerca de 3 segundos de 70. E o banco em memória não remove tudo isso; ele remove o transporte, não a análise, o plano, nem o mapeamento do resultado no lado do Ruby.

flowchart TB
  T["~70s de suíte"] --> B["~3s de boot: Rails, gems, Zeitwerk"]
  T --> E["~67s de exemplos"]
  E --> S["~27s: 21 exemplos do sandbox esperando limites de tempo"]
  E --> D["~3s de transporte ao banco, estimativa generosa"]
  E --> R["o resto: Ruby, renderização, ActiveRecord, factories"]
  D --> W["o que um banco em memória poderia tocar"]

Onde estão os exemplos lentos

Top 8 slowest examples (29.6 seconds, 41.5% of total time):
  10.01s  Execution::Sandbox stops code that blocks forever
   4.45s  Execution::Sandbox keeps what a fast submission printed while other threads hold the GVL
   3.02s  Execution::Sandbox cleanup after the grade kills a double fork
   3.02s  Execution::Sandbox cleanup after the grade kills a detached fork
   3.02s  Execution::Sandbox cleanup after the grade kills a detached spawn
   2.06s  Execution::GradeSubmissionJob stops code that never terminates instead of hanging the queue
   2.02s  Execution::Sandbox stops code that burns CPU forever
   2.00s  Enrollment::AwardBadgesService.call handles 'The Completionist' badge (max course completion)

Oito exemplos são 41,5% da suíte, e lê-los te diz no que cada um gasta o tempo. Sete deles exercitam o sandbox de correção. E a maioria roda código feito para se comportar mal e espera um limite de tempo pará-lo: a espera é o teste. O exemplo de badge monta estado suficiente para completar todos os cursos. Mais abaixo na lista de grupos, os specs de autenticação rodam BCrypt: deliberadamente lento, por projeto, e nenhum banco de forma alguma o deixa mais rápido.

Nenhum deles é lento por causa do socket com o PostgreSQL.

A regra

Perfilar antes de otimizar é um conselho que todo mundo repete e poucos seguem, porque uma suíte não tem um perfilador óbvio e a intuição parece boa o bastante. O RSpec traz um:

$ bundle exec rspec --profile 8

Essa flag, e os dois tempos impressos no fim de toda execução, bastam para responder a pergunta que este curso faz. Elas custam nada e levam uma execução.

A conclusão honesta para este repositório: um banco em memória compraria alguns segundos de uma suíte de setenta segundos, e custaria as garantias das próximas quatro aulas. Essa é a resposta aqui. Não é a resposta em todo lugar, e a aula 6 é sobre os projetos onde ela vira.

Pontos-chave

  • Medido: 1.206 exemplos, cerca de 70 segundos, cerca de 3 segundos de boot, aproximadamente 58ms por exemplo em média.
  • Medido: cerca de 0,05ms por ida e volta ao PostgreSQL por socket local, 0,051ms para uma transação com savepoint.
  • Uma estimativa generosa põe o transporte total ao banco perto de 3 segundos dos 70.
  • Os exemplos mais lentos esperam limites de tempo do sandbox, montam estado grande ou rodam BCrypt: nunca o socket.
  • O --profile e as duas linhas que o RSpec já imprime respondem a pergunta em uma execução.
Aula 1 de 7 · grátis

Continue com a aula 2

O que é de fato um banco em memória

Explica o modo :memory: do SQLite, por que o pool de conexões exige a URI com cache compartilhado e a carga do esquema.

Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.

Digite para buscar...