Para onde o tempo realmente vai
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
--profilee as duas linhas que o RSpec já imprime respondem a pergunta em uma execução.
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.