Descompactando e compactando sem descompactar

Recentemente tive que lidar com um arquivo .zip. O arquivo é composto basicamente por algumas dezenas de milhões de linhas de texto que têm que ser processadas uma a uma, e pesa 4.8GB compactado, 90+GB descompactado.

Com o gzip.GzipFile podemos iterar sobre o arquivo com Python e iterar sobre suas linhas sem ter que descompactar o arquivo todo em disco de antemão - algo bem interessante pro meu caso, visto que na máquina de processamento em questão só havia 16GB de espaço de disco livre, menos do que o necessário pro arquivo completo descompactado.

O gzip.GzipFile, porém, só funciona com arquivos gzip (geralmente .gz). Iterar sem descompactar também é possível com zipfile, mas o arquivo original apresenta alguma sorte de corrompimento e não permite listar o nome do arquivo de texto dentro do .zip; assim, não conseguiríamos abri-lo.

A missão, então, é transformar o .zip em .gz, mas sem descompactar o .zip inteiro primeiro. Será possível?

stdout e pipe

Sim! As ferramentas de extração e compactação que usaremos aceitam como entrada de dados o próprio stdout. Isso significa que podemos ligar nossos comandos em sequência, fazendo com que um consuma a saída do outro:

 $ cat dados.zip | funzip | gzip > dados.gz

O que está acontecendo?

O cat dados.zip direciona ao stdout uma stream dos dados binários contidos em dados.zip. O primeiro pipe ( | ) liga essa stream ao funzip, que faz um "unzip" dos dados binários, e o segundo pipe direciona os dados descompactados resultantes do funzip pro gzip, que vai recompactá-los dessa vez em .gz. Por fim, o > direciona a saída do gzip pro arquivo dados.gz.

Ufa!

O processo demora um pouco, então vamos ver a quantas anda.

Nossa máquina é parruda e tem 16 cores, mas só está usando um deles completamente, com outro sendo utilizado pela metade. Que ineficiente!

Como podemos observar, o gzip está sendo o gargalo, consumindo 100% de um dos cores, e o funzip consome só 46.5% de outro.

Uma rápida pesquisa nos torna cientes da existência do [pigz](https://zlib.net/pigz/), uma Parallel Implementation of GZip. Paralelizando nosso gargalo de modo a utilizar mais dos processadores, conseguimos um throughput maior. Infelizmente, descompressão de um único arquivo não é possível em múltiplos cores, mas nesse ponto estamos chegando perto do gargalo de I/O de disco de qualquer modo.

Pra usar o pigz, além do sudo apt install pigz, basta substituir o gzip do nosso comando original:

$ cat dados.zip | funzip | pigz > dados.gz

E a melhoria torna-se aparente:

Como esperado, funzip é nosso novo gargalo, agora utilizando 100% do disco (não tem mais que parar e esperar o gzip), e nossa compressão passou a utilizar 276% de um core (distribuido por vários dos outros cores na máquina).

Show Comments

Get the latest posts delivered right to your inbox.