O despertar da lentidão

Você está há um tempo trabalhando no seu projeto de data science. Tem vinte notebooks, algumas imagens, e a típica estrutura de pastas que parecia boa ideia há três meses.

Faz git status para ver o que você mexeu e… espera. E espera. E enquanto espera dá tempo de se perguntar se o computador travou ou simplesmente está meditando.

Spoiler: não está meditando. Está sofrendo.

O problema tem nome (e sobrenome)

O Git não é lento. Seu repo é.

Quando executa git status, o git tem que fazer duas coisas que parecem simples mas não são:

  1. Escanear toda a árvore de arquivos para ver o que mudou
  2. Comparar cada arquivo com o que tem salvo

Num repo normal isso é instantâneo. Mas os notebooks do Jupyter são JSON disfarçado de documento. E não qualquer JSON: um que inclui código, saídas, imagens codificadas em base64, metadados do kernel, e basicamente tudo que ocorreu a quem projetou o formato.

Um notebook “pequeno” com alguns gráficos pode pesar vários megas. Multiplique isso por vinte notebooks e você tem um repo que geme toda vez que você olha pra ele.

E se ainda por cima renomeia uma pasta… o git interpreta como “você apagou 50 arquivos e criou 50 novos”. Diversão garantida.

Solução 1: Ponha um porteiro no seu repo

A primeira solução é tão elegante que dá raiva não ter conhecido antes.

Chama-se FSMonitor e funciona assim: em vez do git escanear todo o repo toda vez que você faz algo, o sistema operacional avisa quais arquivos mudaram.

Dito em português claro: é como ter um porteiro na entrada que te fala “só entrou o João” em vez de ter que revisar toda a lista de convidados toda vez.

Para ativar:

1
2
git config core.fsmonitor true
git config core.untrackedcache true

Pronto. Já está.

A primeira vez que fizer git status depois de ativar vai demorar o mesmo (ou mais, porque tem que inicializar o cache). Mas a partir da segunda… mágica.

No meu repo de 400+ arquivos, git status passou de 2-3 segundos para ser instantâneo. Não “rápido”. Instantâneo.

Funciona em todo lugar?

  • macOS: Sim, usa FSEvents
  • Linux: Sim, usa inotify (se seu kernel suportar, e suporta)
  • Windows: Sim, usa ReadDirectoryChangesW

Ou seja, sim.

Solução 2: Guarde a receita, não a foto do prato

FSMonitor acelera o escaneamento, mas há outro problema: os notebooks continuam sendo enormes. E toda vez que executa uma célula e salva, mesmo que o código seja o mesmo, o arquivo muda porque as saídas são diferentes.

Isso significa:

  • Diffs ilegíveis (quem quer revisar base64 de uma imagem?)
  • Commits inflados
  • Merges do inferno

A solução chama-se nbstripout e faz exatamente o que seu nome indica: remove o output dos notebooks antes de commitar.

É como guardar a receita sem as fotos do prato pronto. O código está lá, os resultados você regenera quando quiser.

Para instalar com uv:

1
2
uv add nbstripout
uv run nbstripout --install

Ou com pip, se você é da velha guarda:

1
2
pip install nbstripout
nbstripout --install

O --install configura o git automaticamente para usar nbstripout como filtro. A partir desse momento, quando fizer commit de um notebook, ele salva sem outputs.

Para verificar que está funcionando:

1
git config --get filter.nbstripout.clean

Se retornar algo como nbstripout, está indo bem.

E se eu precisar salvar os outputs?

Boa pergunta. Às vezes você quer commitar um notebook já executado, por exemplo para documentação ou para que alguém veja sem ter que executar.

nbstripout deixa você marcar notebooks específicos para que pulem o filtro:

1
git -c diff.nbstripout.textconv=cat diff

Ou pode configurar exceções no seu .gitattributes. Mas em geral, meu conselho é: não guarde outputs. Os notebooks são código, não documentos finais.

Menção honrosa: git-lfs

Se além de notebooks você tem imagens ou vídeos que mudam frequentemente, existe o git-lfs (Large File Storage).

A ideia é simples: em vez de guardar o binário pesado no repo, você guarda um ponteiro e o arquivo real fica num servidor à parte.

É como guardar as malas no depósito do aeroporto e levar só o comprovante.

1
2
3
git lfs install
git lfs track "*.png"
git lfs track "*.mp4"

Por que é só menção honrosa e não solução principal? Porque adiciona complexidade. Você precisa de um servidor LFS (GitHub e GitLab oferecem, mas têm limites), e se alguém clonar o repo sem git-lfs instalado, leva os ponteiros em vez dos arquivos.

Para notebooks, FSMonitor + nbstripout costumam ser suficientes. git-lfs é para quando você tem datasets de verdade ou assets multimídia.

O combo vencedor

Minha configuração para qualquer repo com notebooks:

1
2
3
4
5
6
7
# Velocidade
git config core.fsmonitor true
git config core.untrackedcache true

# Notebooks limpos
uv add nbstripout
uv run nbstripout --install

Quatro comandos e seu repo passa de vovô com artrite para atleta olímpico.

Verifique que tudo funciona

1
2
3
4
5
6
7
# FSMonitor ativo
git config --get core.fsmonitor
# Deveria retornar: true

# nbstripout configurado
git config --get filter.nbstripout.clean
# Deveria retornar: nbstripout

Se ambos retornarem o esperado, já está. Aproveite seu git status instantâneo e seus diffs legíveis.

Fechamento

A próxima vez que alguém te disser “git é lento”, já sabe o que responder: git não é lento, seu repo está mal configurado.

FSMonitor para que o sistema operacional faça o trabalho pesado. nbstripout para que os notebooks não pesem como se carregassem lastro.

E se depois de tudo isso ainda estiver lento… talvez seja hora de se questionar se realmente precisa de 200 notebooks no mesmo repo. Mas essa é outra história.