2016-05-02

О gzip

Издавна файлы упаковывались и сжимались. Часто для экономии дискового пространства. Или же для передачи по сети. И как-то повелось, что сжимать нужно было сразу целые кучки файлов. Так родились такие милые форматы архивов, как Zip или Rar. Они выполняют сразу две задачи: засунуть кучу файлов в один файл и сжать всё это дело.
В мире Unix принято поступать своим путём. И эти две задачи разделены. За то, чтобы поместить кучу файлов в один большой файл, отвечает tar. Вообще-то, это — tape archiver, т.е. архиватор на ленту. Давным давно файлы нужно было засовывать в один непрерывный файл-поток для записи на магнитную ленту. Так и получался архив.
А вот сжатие архива делается отдельно. Формально, внешними утилитами. Такими, как gzip, bzip2, а в последнее время, ещё и lzma и xz. gzip использует тот же алгоритм сжатия Deflate, что и классический Zip. А lzma и xz реализуют алгорим LZMA (и LZMA2), те же, что в 7-zip.
Получается, что .tar.gz — это куча файлов в одном tar архиве, сжатое с помощью gzip. Это в некотором смысле противоположно zip архиву, где сначала сжимается каждый файл, а потом они помещаются в один архив. С другой стороны, у Rar и 7-zip есть возможность создания непрерывных (solid) архивов, когда всё происходит как в .tar.gz: сначала соединяем файлы, а потом всё жмём.
tar.gz
Юниксовые компрессоры gzip, bzip2, xz могут работать и без tar, сжимая один единственный файл. Как-то так:
% gzip big_long.log 
gzip жмёт похуже, bzip2 — получше, xz — ещё лучше. Но gzip жмёт всё же вполне прилично.
Допустим, у нас есть какой-то дурацкий лог-файл. Аж на 10 гигабайт.
% ls -s *.log
11002376 big_long.log
Этот файлик можно, конечно, засунуть в 7z архив.
% time 7z a big_long.7z big_long.log 

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,4 CPUs)
Scanning

Creating archive big_long.7z

Compressing  big_long.log      

Everything is Ok
7z a big_long.7z big_long.log  1955.19s user 33.48s system 118% cpu 28:03.36 total
Почти полчаса на то, чтобы сжать. Зато сжалось хорошо.
% ls -s1 *.log *.7z
  281720 big_long.7z
11002376 big_long.log
Сжалось аж почти в сорок раз.
Что с этим архивом можно сделать? Ну, распаковать.
% time 7z x big_long.7z                                 

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,4 CPUs)

Processing archive: big_long.7z

Extracting  big_long.log

Everything is Ok

Size:       11266426416
Compressed: 288475059
7z x big_long.7z  39.95s user 8.17s system 33% cpu 2:22.03 total
За две с половиной минуты мы можем получить наши десять гигабайт обратно.
Попробуем gzip.
% time gzip big_long.log 
gzip big_long.log  125.82s user 4.13s system 87% cpu 2:29.06 total    
Ухты. Две с половиной минуты на упаковку. Вместо получаса у 7z.
А что с размером?
% ls -s1 *.log *.gz
11002376 big_long.log
  456112 big_long.log.gz
Сжалось в два раза хуже. Но всё равно, четыреста пятьдесять мегабайт — это сильно транспортабельнее, чем десять гигабайт.
Что можно сделать с этим архивом? Можно распаковать.
% time gunzip big_long.log.gz 
gunzip big_long.log.gz  39.73s user 7.69s system 29% cpu 2:38.33 total
За те же две с половиной минуты.
Но можно и не распаковывать. Да.
Зачем нам этот лог? Файлик в десять гигабайт невозможно открыть ни в одном текстовом редакторе, памяти не хватит. Даже less не поможет, он тоже грузит файл в память. Его долго и проблематично грузить во всякие логстэши, в любом случае потребуется больше десяти гигов для хранения этой прелести. А я не хочу хранить, мне нужно найти пару десятков или сотен строк, относящихся к определённому моменту. Нам нужен grep.
Погрепаем десятигиговый файл.
% time grep "12:43:58" < big_long.log > big_long.grep_12_43_58.log
grep "12:43:58" < big_long.log > big_long.grep_12_43_58.log  9.32s user 7.55s system 13% cpu 2:07.35 total
Чуть больше двух минут на то, чтобы прочесть и профильтровать десять гигов, и получить сто семь строчек в двадцати семи килобайтах того, что нужно, и что можно открыть в редакторе.
Но gzip архивы можно грепать без распаковки.
% time zgrep "12:43:58" < big_long.log.gz > big_long.zgrep_12_43_58.log
zgrep "12:43:58" < big_long.log.gz > big_long.zgrep_12_43_58.log  48.25s user 3.80s system 116% cpu 44.796 total
Сорок пять секунд, и абсолютно тот же результат.
Как это работает? gzip — это поток, именно потому его используют веб-серверы для сжатия контента. Можно читать сжатые данные, разжимать их поблочно, сравнивать и грепать. Именно так и работает zgrep. Нет необходимости загружать все десять гигов в память, всё будет распаковываться маленькими кусочками по мере необходимости. А с диска читать нужно только сжатые данные, которых в двадцать раз меньше.
Есть куча других утилит, которые могут работать с файлами, сжатыми gzip, без явной распаковки. Собственно, zgrep — это, по сути, комбинация zcat и обычного grep. А есть ещё zdiff, zmore и zless.
gzip
Кстати, есть и bzgrep, и xzgrep.
Попробуем bzip2.
% time bzip2 big_long.log 
bzip2 big_long.log  2173.18s user 5.57s system 95% cpu 37:59.61 total
Доолго.
% ls -s *.bz2
250208 big_long.log.bz2
Сжал даже лучше, чем 7z.
Погрепаем.
% time bzgrep "12:43:58" < big_long.log.bz2 > big_long.bzgrep_12_43_58.log
bzgrep "12:43:58" < big_long.log.bz2 > big_long.bzgrep_12_43_58.log  249.57s user 11.47s system 106% cpu 4:05.09 total
Тоже долго. Дольше, чем читать несжатый файл.
Попробуем xz.
% time xz big_long.log
xz big_long.log  2326.37s user 6.65s system 98% cpu 39:18.39 total
Тоже долго.
% ls -s *.xz
253300 big_long.log.xz
Так же компактно.
Грепаем.
% time xzgrep "12:43:58" < big_long.log.xz > big_long.xzgrep_12_43_58.log
xzgrep "12:43:58" < big_long.log.xz > big_long.xzgrep_12_43_58.log  46.81s user 6.83s system 127% cpu 42.044 total
А вот тут быстро, почти как с gzip.
Выводы. Не засовывайте логи в архивы типа Zip, 7-zip, Rar. Сжимайте их в .gz. Тогда можно будет их обрабатывать без полной распаковки. Згрепать можно даже очень большие файлы. Если хочется сэкономить ещё немного места, можно взять bzip2 или xz вместо gzip, но помните, что gzip пакует значительно быстрее.

размер (в блоках)
время сжатия
время грепа
исходный файл, grep 11002376 02:07
gzip, zgrep 456112 02:29 00:44
bzip2, bzgrep 250208 37:59 04:05
xz, xzgrep 253300 39:18 00:42
7z 281720 28:03