ZFS Send/Receive: A Anatomia da Migração (Quase) Instantânea

      16 de dezembro de 2025 Marta G. Oliveira 8 min de leitura
      ZFS Send/Receive: A Anatomia da Migração (Quase) Instantânea

      Esqueça o rsync. Entenda como o ZFS serializa blocos para migrações de petabytes com janela de manutenção de segundos. O guia de engenharia para replicação.

      Compartilhar:

      Se você já tentou migrar 10 terabytes compostos por milhões de pequenos arquivos usando rsync, você conhece o desespero. Você observa o cursor piscar enquanto o sistema operacional luta para fazer stat() em cada inode, saturando os IOPS de leitura muito antes de transferir o primeiro byte de dados.

      A maioria das ferramentas de sincronização opera na camada do sistema de arquivos (File-Level). Elas são "cegas" para a estrutura subjacente do disco e precisam perguntar ao kernel: "O que mudou?".

      O ZFS opera de forma diferente. Ele não precisa perguntar o que mudou; ele é o registro do que mudou. Ao utilizar zfs send e zfs receive, não estamos copiando arquivos. Estamos serializando transações de blocos e transmitindo a estrutura da árvore de dados.

      Este artigo disseca a mecânica dessa operação, como medir seus gargalos e como evitar que uma migração mal planejada derrube sua infraestrutura de produção.

      O Abismo entre File-Walking e Block-Streaming

      Para entender a performance, precisamos isolar o método de descoberta de dados.

      No modelo tradicional (rsync, robocopy, tar), o custo da descoberta é O(n), onde n é o número de arquivos. O sistema precisa percorrer a árvore de diretórios, ler metadados e comparar timestamps ou checksums. Em storage de alta densidade, a latência de busca de metadados (metadata lookup latency) é o assassino silencioso da performance. O disco passa mais tempo buscando cabeçotes (em HDDs) ou processando filas de comandos aleatórios (em SSDs) do que movendo dados.

      O ZFS transforma esse problema. Como um sistema de arquivos Copy-on-Write (CoW), o ZFS nunca sobrescreve dados "in-place". Novos dados são escritos em novos blocos, e o ponteiro do metadado é atualizado atomicamente.

      A diferença de O(n) para O(1): Enquanto ferramentas tradicionais 'perguntam' arquivo por arquivo o que mudou, o ZFS consulta a árvore de blocos baseada no tempo de nascimento (TXG). Figura: A diferença de O(n) para O(1): Enquanto ferramentas tradicionais 'perguntam' arquivo por arquivo o que mudou, o ZFS consulta a árvore de blocos baseada no tempo de nascimento (TXG).

      Isso significa que o ZFS possui um índice temporal intrínseco. Quando solicitamos um stream incremental entre o Snapshot A e o Snapshot B, o ZFS não varre o sistema de arquivos. Ele consulta a árvore de blocos e ignora qualquer bloco cujo "tempo de nascimento" (birth time) seja anterior à criação do Snapshot A.

      O resultado é que o tempo de preparação para o envio de um diferencial é proporcional à quantidade de dados alterados, não ao tamanho total do dataset. É a diferença entre esperar 4 horas para o rsync começar a copiar e o zfs send começar a transmitir instantaneamente.

      A Física do Stream: Transaction Groups (TXGs)

      O que exatamente trafega pelo "cano" quando você executa um zfs send?

      Não é um arquivo zip. É um fluxo serializado de registros de objetos (dnodes) e blocos de dados, organizados por Transaction Groups (TXGs).

      Cada vez que o ZFS confirma escritas no disco (normalmente a cada 5 segundos por padrão), ele fecha um TXG. O stream de envio é, essencialmente, a reprodução desses TXGs no sistema de destino. Isso garante consistência transacional. O lado receptor (zfs receive) não vê um arquivo "pela metade". Ou a transação inteira é aplicada, ou nada é.

      Isso elimina a necessidade de file locking. Você pode enviar um dataset de um banco de dados em produção (desde que tenha feito um snapshot atômico) sem parar o banco, e o destino receberá uma cópia consistente crash-consistent daquele exato milissegundo.

      A Coreografia da Migração Live

      Em cenários de produção, o objetivo é minimizar o RPO (Recovery Point Objective) e o RTO (Recovery Time Objective). A migração "quase" instantânea baseia-se em reduzir a janela de delta.

      A metodologia correta segue este algoritmo:

      1. Snapshot Inicial (Base): Criar @migracao_inicio.

      2. Envio Full: Transferir todo o dataset. O sistema de origem continua operando e acumulando novos dados (divergência).

      3. Snapshots Incrementais (Catch-up): Criar @inc_1, enviar a diferença entre @migracao_inicio e @inc_1. Repetir conforme necessário para reduzir o delta.

      4. Janela de Corte (Cutover):

        • Parar a aplicação/serviço na origem (quiesce).
        • Criar Snapshot Final @final.
        • Enviar o incremental (-i @inc_N @final). Como o delta é pequeno (apenas os dados escritos desde o último sync), isso leva segundos.
        • Subir a aplicação no destino.

      Este processo transforma uma janela de manutenção de horas (cópia total) em minutos ou segundos (cópia do último delta).

      O Gargalo Invisível: TCP, SSH e Buffers

      Aqui é onde a teoria encontra a realidade física da rede. Um erro comum de engenharia é assumir que zfs send | ssh host zfs receive utilizará toda a banda de um link de 10GbE. Raramente utiliza.

      Existem dois culpados principais:

      1. Overhead de Criptografia (CPU Bound): O SSH é single-threaded por conexão. Em redes rápidas, um único núcleo da CPU pode saturar tentando criptografar/descriptografar o stream (frequentemente limitando-se a ~150-300 MB/s dependendo do clock da CPU), deixando o link de 10GbE 80% ocioso.

      2. Oscilação do TCP (Windowing): O zfs send produz dados em rajadas (bursts). O zfs receive grava em rajadas (sync writes). Se o pipe TCP esvaziar ou encher demais, a transmissão para e espera (TCP window collapse).

      Otimização de Transporte: O SSH é frequentemente o gargalo da CPU em redes de 10GbE+. O uso de buffers intermediários (mbuffer) e transporte raw (netcat) desacopla a leitura da escrita. Figura: Otimização de Transporte: O SSH é frequentemente o gargalo da CPU em redes de 10GbE+. O uso de buffers intermediários (mbuffer) e transporte raw (netcat) desacopla a leitura da escrita.

      A Solução: mbuffer e Transporte Raw

      Para resolver a oscilação, precisamos desacoplar a geração do stream da transmissão da rede. O mbuffer atua como um tanque de compensação, mantendo a rede saturada mesmo se o disco engasgar momentaneamente, e vice-versa.

      Para resolver o gargalo da CPU do SSH, em redes confiáveis (como uma cross-connect de datacenter ou VPN segura), usar netcat (nc) é ordens de magnitude mais rápido. Se a segurança for mandatória, utilize cifras mais leves no SSH (como [email protected] ou chacha20-poly1305) ou, idealmente, use o recurso ZFS Raw Send (explicado abaixo) que torna a criptografia de transporte redundante.

      Anatomia de um Comando Robusto

      Evite comandos simplistas em produção. Um comando de engenharia robusto deve prever falhas de rede, preservar propriedades e otimizar o fluxo.

      Flags Essenciais

      • -w (Raw Send): A "bala de prata". Se o seu dataset de origem é criptografado nativamente pelo ZFS, o -w envia os blocos ainda criptografados.

        • Por que usar: O servidor de origem não gasta CPU descriptografando e o de destino não gasta CPU re-criptografando. O dado trafega seguro pela rede sem necessidade de SSH pesado. A chave de criptografia não precisa nem estar carregada no destino.
      • -R (Replication): Envia recursivamente todos os datasets filhos e, crucialmente, suas propriedades (compressão, quotas, pontos de montagem).

      • -s (Resume Token): Se a conexão cair aos 95% de 10TB, o ZFS salva um token. Você pode retomar a transferência exatamente de onde parou, sem recomeçar do zero.

      O Comando de Produção

      Abaixo, um exemplo de pipeline otimizado para uma migração crítica via SSH, usando mbuffer para suavizar o fluxo e Resume Tokens para segurança:

      # NA ORIGEM (PUSH)
      # -w: raw send (mantém criptografia)
      # -R: replicação recursiva
      # -s: habilita resume token
      # mbuffer: buffer de 1GB para absorver latência
      
      zfs send -w -R -s pool/dataset@snap_final | \
      mbuffer -s 128k -m 1G | \
      ssh root@destino "mbuffer -s 128k -m 1G | zfs receive -F pool_destino/dataset"
      

      Nota: O -F no receive força o rollback do sistema de destino para o estado inicial da transferência, útil se uma tentativa anterior falhou e deixou o dataset "sujo". Use com cautela.

      Cenários de Risco: Onde a Migração Falha

      Mesmo com a ferramenta certa, a física do storage impõe limites.

      1. O Pesadelo da Deduplicação (Dedup)

      Se o pool de destino tiver dedup=on, sua migração vai colidir com uma parede. Cada bloco recebido exigirá uma consulta na Tabela de Deduplicação (DDT). Se a DDT não couber na RAM (ARC), o disco fará leituras aleatórias para cada escrita sequencial do stream. A performance cairá de 500MB/s para 5MB/s. Regra: Desligue o dedup no destino ou garanta RAM massiva.

      2. Mismatch de Recordsize

      Se você enviar um stream gerado com recordsize=128k (padrão) para um dataset que você gostaria que fosse recordsize=1M (banco de dados), o zfs receive respeitará o tamanho do bloco do stream original (a menos que seja forçado ou reescrito). O stream carrega a geometria dos dados. Para alterar o recordsize na migração, você não pode usar o stream raw; os dados devem ser reprocessados, o que aumenta a carga de CPU.

      3. Fragmentação Oculta

      Um stream de zfs send é linear. Quando o zfs receive escreve isso no disco, ele tenta fazer de forma sequencial. Isso geralmente resulta em um dataset menos fragmentado no destino do que na origem. No entanto, se o pool de destino estiver 90% cheio, o ZFS terá que lutar para encontrar blocos livres, transformando uma escrita sequencial (rápida) em aleatória (lenta).

      Veredito Técnico

      O zfs send/receive não é apenas uma ferramenta de cópia; é uma extensão da arquitetura transacional do ZFS. Ao entender que estamos movendo ponteiros e blocos baseados no tempo (TXGs), e não arquivos, podemos arquitetar migrações de petabytes com janelas de corte insignificantes.

      O segredo não está no comando em si, mas em tratar o fluxo de dados como um problema de engenharia hidráulica: remova as constrições (SSH), adicione reservatórios (mbuffer) e garanta que o cano de destino suporte a pressão.

      #ZFS #Replicação de Dados #Disaster Recovery #Performance Tuning #Linux Storage
      Marta G. Oliveira

      Marta G. Oliveira

      DevOps Engineer & Storage Nerd

      Automatiza provisionamento de storage com Terraform e Ansible. Defensora do 'Infrastructure as Code' para storage.