Packfiles e garbage collection
1. Introdução aos Packfiles
Quando você trabalha com Git, cada commit, árvore e blob é inicialmente armazenado como um objeto solto (loose object) — um arquivo individual comprimido com zlib, salvo em .git/objects/XX/YYYYYY.... Esse formato é simples e direto, mas extremamente ineficiente para repositórios grandes. Um projeto com milhares de commits pode gerar dezenas de milhares de arquivos pequenos, desperdiçando espaço em disco (cada arquivo ocupa pelo menos um bloco do sistema de arquivos) e tornando operações como clone e fetch muito lentas.
Para resolver isso, o Git cria packfiles — arquivos binários que consolidam muitos objetos em um único arquivo .pack, acompanhado de um índice .idx. O ciclo de vida típico é:
- Objetos são criados como loose objects.
- Periodicamente (ou manualmente), o Git empacota esses objetos em um packfile.
- Objetos soltos originais são removidos após a verificação bem-sucedida.
Um repositório saudável contém predominantemente packfiles, com poucos objetos soltos (apenas os muito recentes).
# Exemplo: contagem de objetos em um repositório típico
$ git count-objects -v
count: 12
size: 48
in-pack: 5842
packs: 3
size-pack: 2847
prune-packable: 0
garbage: 0
Aqui, apenas 12 objetos soltos (48 KB) contra 5842 objetos empacotados (2,8 MB). A eficiência é clara.
2. Estrutura Interna de um Packfile
Um packfile não é apenas uma concatenação de objetos comprimidos. Ele possui uma estrutura binária bem definida:
Arquivo .pack:
- Cabeçalho: 12 bytes — assinatura "PACK", versão (2 ou 3), número de objetos.
- Corpo: sequência de objetos, cada um com tipo (commit, tree, blob, tag), tamanho e dados comprimidos (zlib).
- Trailer: checksum SHA-1 de 20 bytes de todo o conteúdo do pack.
Arquivo .idx:
- Fanout table: 256 entradas de 4 bytes cada, mapeando o primeiro byte do SHA-1 para a posição no índice.
- SHA-1 list: lista ordenada de todos os SHA-1 presentes no pack.
- CRC checksums: checksums para cada objeto (opcional, versão 2).
- Offsets: posição de cada objeto dentro do arquivo .pack.
- Trailer: checksum SHA-1 do arquivo .idx e do .pack correspondente.
Podemos inspecionar packfiles com ferramentas nativas:
# Verificar a integridade e listar objetos de um packfile
$ git verify-pack -v .git/objects/pack/pack-abc123.pack
# Mostrar o índice de um packfile (fanout table e offsets)
$ git show-index < .git/objects/pack/pack-abc123.idx
3. Compressão, Deltas e Otimização
O Git comprime cada objeto individualmente usando zlib (deflate), mas a grande inovação está no armazenamento delta. Em vez de armazenar cada versão de um arquivo por completo, o Git armazena apenas as diferenças entre versões.
Tipos de delta:
- Forward delta: um objeto armazena a diferença em relação a uma versão anterior (base).
- Base object: o objeto completo que serve como referência para os deltas.
O Git organiza os deltas em uma árvore de deltas, onde um objeto base pode ter múltiplos deltas encadeados. Para reconstruir um objeto, o Git percorre a cadeia até o objeto base.
Estratégias de seleção de base:
- Window: quantos objetos anteriores são considerados como candidatos a base (padrão: 10, mas pode ser aumentado para 250).
- Depth: profundidade máxima da cadeia de deltas (padrão: 50, máximo recomendado: 250).
# Exemplo de cadeia de deltas em um packfile
$ git verify-pack -v .git/objects/pack/pack-abc123.pack | head -20
...
1a2b3c4d... commit 1 244 142 12
5e6f7g8h... tree 1 102 58 154
9i0j1k2l... blob 1 512 300 212
m3n4o5p6... blob 2 128 90 512 (delta de 9i0j1k2l)
...
Aqui, o blob m3n4o5p6 é um delta que depende do blob 9i0j1k2l (base). O número "2" indica que é a segunda versão do arquivo.
4. O Comando git gc – Garbage Collection
Garbage collection no Git tem dois objetivos principais:
1. Remover objetos inalcançáveis (órfãos) que não são mais referenciados por nenhum branch, tag ou reflog.
2. Otimizar o armazenamento consolidando múltiplos packfiles e recalculando deltas.
O comando principal é git gc:
# Execução automática (gatilho após certas operações)
$ git gc --auto
# Execução manual com parâmetros
$ git gc --prune=now --aggressive
Parâmetros importantes:
- --auto: executa apenas se o número de objetos soltos ou packfiles exceder limites configuráveis (gc.auto, gc.autopacklimit).
- --prune=<date>: remove objetos inalcançáveis mais antigos que a data especificada. --prune=now remove imediatamente (cuidado!).
- --aggressive: otimiza agressivamente o repositório (mais lento, mas maior compressão).
- --no-prune: não remove objetos inalcançáveis (apenas otimiza).
Configurações relacionadas:
# Configurar gatilhos automáticos
$ git config gc.auto 6700 # Número de objetos soltos para gatilho
$ git config gc.autopacklimit 10 # Número de packfiles para gatilho
5. Identificando e Removendo Objetos Órfãos
Objetos tornam-se inalcançáveis (órfãos) quando:
- Você faz git reset --hard descartando commits.
- Você altera o histórico com git rebase ou git commit --amend.
- Você remove branches ou tags que referenciavam objetos.
Para identificar esses objetos:
# Listar objetos não referenciados
$ git fsck --unreachable
unreachable commit 1a2b3c4d...
unreachable tree 5e6f7g8h...
unreachable blob 9i0j1k2l...
# Contar objetos órfãos
$ git fsck --unreachable | wc -l
A remoção é feita com git prune (mais seguro) ou git gc --prune:
# Remover objetos inalcançáveis com mais de 2 semanas (padrão)
$ git prune
# Remover objetos inalcançáveis imediatamente
$ git gc --prune=now
Cuidados importantes:
- O Git não remove objetos referenciados pelo reflog (por padrão, 90 dias para commits e 30 dias para outros objetos).
- Stashes e branches remotos também mantêm objetos vivos.
- Sempre faça backup antes de --prune=now.
6. Repacking e Estratégias de Otimização
O comando git repack oferece controle fino sobre o empacotamento:
# Forçar novo empacotamento completo
$ git repack -a -d -f --depth=250 --window=250
Parâmetros:
- -a: empacota tudo em um único packfile.
- -d: remove packfiles antigos após o novo ser criado.
- -f: força o recálculo de todos os deltas (ignora deltas existentes).
- --depth: profundidade máxima da cadeia de deltas (padrão: 50).
- --window: número de objetos considerados como base para delta (padrão: 10).
git gc --aggressive vs. git repack -a -d -f --depth=250 --window=250:
Na prática, git gc --aggressive internamente chama git repack -a -d -f --depth=250 --window=250. A diferença é que gc também executa outras tarefas (limpeza, reflog, etc.). Para otimização pura de packfiles, o repack direto é mais rápido.
Trade-offs:
- Window maior → melhor compressão, mas mais tempo de CPU e memória.
- Depth maior → melhor compressão para arquivos com muitas versões, mas acesso mais lento ao objeto (precisa percorrer a cadeia).
- Repack completo → ideal após grandes reescritas de histórico ou antes de clonar.
# Comparação de tamanho antes e depois do repack agressivo
$ git count-objects -v | grep size-pack
size-pack: 5847
$ git gc --aggressive
$ git count-objects -v | grep size-pack
size-pack: 4123
7. Monitoramento e Manutenção de Repositórios
A ferramenta principal para monitorar o estado do repositório é git count-objects -v:
$ git count-objects -v
count: 12 # Objetos soltos
size: 48 # Tamanho total dos objetos soltos (KB)
in-pack: 5842 # Objetos em packfiles
packs: 3 # Número de packfiles
size-pack: 4123 # Tamanho total dos packfiles (KB)
prune-packable: 0 # Objetos soltos que poderiam ser empacotados
garbage: 0 # Arquivos corrompidos ou não reconhecidos
Boas práticas:
- Frequência de
git gc: uma vez por mês para repositórios ativos, ou após operações que geram muitos objetos órfãos (rebase, reset, filter-branch). - Tamanho ideal de packfiles: entre 2 GB e 8 GB. Packfiles muito grandes tornam operações incrementais lentas.
- Evite
--aggressiveem produção: pode travar o repositório por horas. Prefira parâmetros moderados (--depth=100 --window=100). - Monitore com
git fsck: execute regularmente para detectar corrupção.
Problemas comuns:
# Repositório inchado: muitos objetos duplicados ou desnecessários
$ git count-objects -v | grep "prune-packable"
prune-packable: 1500 # Muitos objetos soltos que poderiam ser empacotados
# Solução
$ git gc
# Corrupção detectada
$ git fsck
error: object 1a2b3c4d... is corrupt
# Solução (tentar restaurar de um clone ou backup)
$ git fetch --all
$ git fsck
Referências
- Pro Git Book - Chapter 10: Git Internals - Packfiles — Explicação detalhada da estrutura de packfiles, deltas e compressão.
- Git Documentation: git-gc — Documentação oficial completa do comando
git gc, incluindo todas as opções e configurações. - Git Documentation: git-repack — Documentação oficial do
git repack, com parâmetros para otimização de packfiles. - Git Documentation: git-prune — Documentação oficial do
git prunepara remoção segura de objetos inalcançáveis. - Atlassian Git Tutorial: Git GC and Garbage Collection — Tutorial prático sobre garbage collection, com exemplos de quando e como executar.
- Git SCM Wiki: Git Pack Files — Recurso complementar com diagramas e explicações visuais sobre a estrutura de packfiles.