Como o RSYNC funciona?
Published: 2023-12-17 19:01:25

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](https://www.samba.org/~tridge/phd_thesis.pdf).

Como nós não somos acadêmicos aqui e queremos apenas usar a ferramenta, vamos decompor duas partes importantes do funcionando do rsync:

1. Como ele determina **que arquivos precisam ser enviados para o destino**;
2. 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](https://linux.die.net/man/1/rsync):


![Screenshot do Linux Die](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/radwnsxaxczvxvqad9ko.png)

Isso inclusive uma grande piada interna de alguns usuários mais atentos:

![Screeshot da rsync manpage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mo0sk67p0u5oo8n3gvv2.png)

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.

```bash
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:

```bash
echo "O rsync nao faz checksum por padrao" > /tmp/testersync/destino/arquivo
```

E usar o rsync:

```bash
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:

```bash
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:

```bash
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:

```bash
cat /tmp/testersync/origem/arquivo | tr '[a-z]' '[A-Z]' > /tmp/testersync/destino/arquivo
```

Do ponto de vista computacional, claramente eles são diferentes:

```bash
find /tmp/testersync -type f -exec sha256sum '{}' \+
f27d88d52759a4b07c077f34c230c47b94ea88b6640fc47b44d562243966eb7e  /tmp/testersync/destino/arquivo
47c6ec4243572212a13d657637a62975a1007cf5881668cf7d02d370956dc3b3  /tmp/testersync/origem/arquivo
```

O tamanho é o mesmo:

```bash
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:

```bash
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:

```bash
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:

```bash
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?):

```bash
touch /tmp/testersync/destino/arquivo -r /tmp/testersync/origem/arquivo
```  

Como o rsync enxerga o arquivo agora?

```bash
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:

```bash
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:

```bash
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:

```bash
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!