Em um determinado projeto esta semana, comentei que o clássico programa rsync, na verdade, não faz análise de checksum por padrão na hora de identificar que arquivos transferir. Isso surpreendeu algumas pessoas!
Então acredito que compartilhar este "mini-lab" com explicações ajude você a entender o básico do funcionamento do rsync!
RSYNC é assunto para PHD!
O Tridgell , um dos criadores do rsync, descreveu o funcionamento do programa em sua tese de Ph.D. em 1999, e você pode lê-la aqui.
Como nós não somos acadêmicos aqui e queremos apenas usar a ferramenta, vamos decompor duas partes importantes do funcionando do rsync:
- Como ele determina que arquivos precisam ser enviados para o destino;
- Quais as partes do arquivo foram alteradas e precisam ser enviadas para o destino
São duas coisas totalmente diferentes, mas que geram a confusão que eu entendo que muitas pessoas fazem.
Analisando apenas a parte 2., o rsync é capaz de, a partir de um arquivo com determinado tamanho, dividí-lo em pedaços, calcular um hash para cada pedaço, e identificar qual pedaço precisa ser enviado. Isso é excepcionalmente útil na hora de transferir um arquivo muito grande mas que é 99,9% igual na origem ou no destino. Um exemplo seriam arquivos de logs gerados de maneira progressiva ou massas de dados incompletas que foram transferidas parcialmente por interrupção do serviço. Nessas horas, você pode contar com o rsync para transferir apenas o incremento de 1 ou 2 GiB, e não os 10TiB exatamente iguais do arquivo.
Mas essa operação de checksum é processada após o rsync entender que o arquivo precisa ser enviado. Isso não quer dizer que o rsync executa um checksum em cada arquivo para saber que ele precisa ser alterado.
É extremamente importante não confundir as duas coisas, são etapas diferentes!
Que arquivos precisam ser enviados para o destino?
A resposta está no man rsync
Rsync finds files that need to be transferred using a "quick check" algorithm (by default) that looks for files that have changed in size or in last-modified time. Any changes in the other preserved attributes (as requested by options) are made on the destination file directly when the quick check indicates that the file's data does not need to be updated.
Então, o "algoritmo" padrão de detecção nem merece um nome bonito, chamado de quick check. Ele observa mudanças no tamanho do arquivo ou na data e hora da última modificação (ou mtime).
Algumas pessoas chamam esse algoritmo de checkrq ou lquick ou ainda pior lquickcheckrq por causa desta página:
Isso inclusive uma grande piada interna de alguns usuários mais atentos:
O lq do 'lqquick' e o rq do 'checkrq' são, na verdade, o "left quote" e "right quote" da manpage mal transpostos do formato man para html! Logo, por favor, não chame o algoritmo de "checkrq" ou você corre o risco de alguém gargalhar na sua frente!
Mas a parte importante é essa: rsync, por padrão, não executa checksum nos arquivos para decidir se ele precisa transferí-lo (inteiro ou seus pedaços) ou não. Ele executa uma simples análise de timestamp e tamanho.
Por quê? Porque é muito mais rápido e extremamente efetivo! Qual a chance de um arquivo com mesmo tamanho e mesmo mtime ser diferente em duas origens que comumente se sincronizam?
Enganando o rsync:
Vamos fazer um lab.
mkdir -p /tmp/testersync/{origem,destino}
echo "O rsync nao faz checksum por padrao" > /tmp/testersync/origem/arquivo
Se, um tempo depois, você criar um arquivo igual no destino:
echo "O rsync nao faz checksum por padrao" > /tmp/testersync/destino/arquivo
E usar o rsync:
rsync -avi /tmp/testersync/origem/ /tmp/testersync/destino
sending incremental file list
>f..t...... arquivo
Esse t na saída do log mostra que ele notou que o timestamp está diferente.
Se você mudar o tamanho da frase:
echo "Claro que o rsync faz checksum por padrao, oras" > /tmp/testersync/destino/arquivo
Agora o rsync vai falar que ele mudou não só o timestamp mas o tamanho:
rsync -avi /tmp/testersync/origem/ /tmp/testersync/destino
sending incremental file list
>f.st...... arquivo
Agora vamos tentar enganar o rsync. Vou criar o arquivo com a mesma frase, mas toda em maiúscula:
cat /tmp/testersync/origem/arquivo | tr '[a-z]' '[A-Z]' > /tmp/testersync/destino/arquivo
Do ponto de vista computacional, claramente eles são diferentes:
find /tmp/testersync -type f -exec sha256sum '{}' \+
f27d88d52759a4b07c077f34c230c47b94ea88b6640fc47b44d562243966eb7e /tmp/testersync/destino/arquivo
47c6ec4243572212a13d657637a62975a1007cf5881668cf7d02d370956dc3b3 /tmp/testersync/origem/arquivo
O tamanho é o mesmo:
find /tmp/testersync -type f -printf '%P: %s\n'
destino/arquivo: 36
origem/arquivo: 36
Mas se você usar o rsync, ele vai retransferir o arquivo porque o mtime é diferente, e isso é um grande indicador que algo mudou no arquivo:
find /tmp/testersync -type f -printf '%P: %t\n'
destino/arquivo: Sun Dec 17 14:45:20.4011998470 2023
origem/arquivo: Sun Dec 17 14:43:11.4812008470 2023
Então, se você executar o rsync, ele vai substituir o arquivo:
rsync -avi /tmp/testersync/origem/ /tmp/testersync/destino
sending incremental file list
>f..t...... arquivo
find /tmp/testersync -type f -exec sha256sum '{}' \+
47c6ec4243572212a13d657637a62975a1007cf5881668cf7d02d370956dc3b3 /tmp/testersync/destino/arquivo
47c6ec4243572212a13d657637a62975a1007cf5881668cf7d02d370956dc3b3 /tmp/testersync/origem/arquivo
Só que isso é bem diferente de executar análise de checksum, que em larga escala, pode ser muito ineficiente.
Vamos enganar o rsync. Primeiro recrio o arquivo diferente:
cat /tmp/testersync/origem/arquivo | tr '[a-z]' '[A-Z]' > /tmp/testersync/destino/arquivo
Depois eu altero os horários dele para ficarem iguais ao da origem (conhece essa função do comando touch?):
touch /tmp/testersync/destino/arquivo -r /tmp/testersync/origem/arquivo
Como o rsync enxerga o arquivo agora?
find /tmp/testersync -type f -printf '%P:Tamanho %s, Mtime %t\n'
destino/arquivo:Tamanho 36, Mtime Sun Dec 17 14:43:11.4812008470 2023
origem/arquivo:Tamanho 36, Mtime Sun Dec 17 14:43:11.4812008470 2023
Do ponto de vista do rsync, eles agora são iguais. Duvida? Vamos executá-lo:
rsync -avi /tmp/testersync/origem/ /tmp/testersync/destino
sending incremental file list
sent 85 bytes received 12 bytes 194.00 bytes/sec
total size is 36 speedup is 0.37
Pelo log, você pode ver que ele não transferiu nada, e o checksum segue diferente:
find /tmp/testersync -type f -exec sha256sum '{}' \+
f27d88d52759a4b07c077f34c230c47b94ea88b6640fc47b44d562243966eb7e /tmp/testersync/destino/arquivo
47c6ec4243572212a13d657637a62975a1007cf5881668cf7d02d370956dc3b3 /tmp/testersync/origem/arquivo
Usando checksum para identificar arquivos diferentes
Neste tipo de situação - que geralmente é improvável de acontecer, mas não necessariamente impossível, você precisa da flag -c:
rsync -avic /tmp/testersync/origem/ /tmp/testersync/destino
sending incremental file list
>fc........ arquivo
find /tmp/testersync -type f -exec sha256sum '{}' \+
47c6ec4243572212a13d657637a62975a1007cf5881668cf7d02d370956dc3b3 /tmp/testersync/destino/arquivo
47c6ec4243572212a13d657637a62975a1007cf5881668cf7d02d370956dc3b3 /tmp/testersync/origem/arquivo
Agora sim, eu garanto que origem e destino são exatamente iguais.
Conclusão
Se você quiser garantir que a origem e o destino são iguais, você precisa modificar o comportamento padrão do rsync usando a flag -c ou --checksum.
Se você está trabalhando com arquivos que usam algum tipo de empacotamento binário de data (basicamente qualquer coisa), a chance desse tipo de situação passar batido é extremamente baixa.
Mas quando você trabalha com muitos arquivos textos com formatos e layouts parecidos e com filesystems extravagantes que podem não expor o mtime adequadamente, você pode cair nessa armadilha.
Em uma era em que a leitura dinâmica e o aprendizado resumido estilo TL;DR segue em voga, muitas vezes vale a pena você parar para entender com calma as ferramentas que você usa para evitar cair em armadilhas no futuro!
Top comments (1)
Uma questão dessas em um concurso derrubava quase todos os candidatos kkkk. Boa dica mestre @marcelo_devsres