The Jerusalem PostUS offers reward of up to $20 million, possible relocation for information on Iran nuclear programCNN TürkFransa'da grev depremi: Macron'a destek tarihi dipteInquirer EntertainmentDolly Parton’s manager drops lawsuit against nephewBollywood HungamaDelhi High Court rules Doosra infringed Sourav Ganguly's personality rights, directs makers to deposit Rs. 2 croresPunchICYMI: How MTN users can access FG’s N50,000 grantZDF heuteAktuelle Pressemitteilungen des ZDFInteriaUSA kuszą nagrodami za informacje o Iranie. Do zdobycia 20 milionów dolarówSportstarन्यूजीलैंड बनाम भारत लाइव स्कोर, दूसरा वनडे: भारत 40/3, जीत के लिए 95 रन चाहिए; कोहली, गिल, रोहित पावरप्ले के अंदर आउट हो गए।CNN بالعربيةواقع جديد بعد 60 عاماً من الإنكار حول برنامج الأسلحة النووية الإسرائيليSözcüEn sağlıklı kuruyemiş açıklandı: Badem ve Antep fıstığından daha çok omega-3 içeriyorDaily MailDAILY MAIL COMMENT: How we can show dementia carers they are not alonePremium TimesSPECIAL REPORT: Inside Lagos communities where public drains have become sewage channels
The Daily Newsstand · Free, Always
Saturday, November 7, 2026

Что остаётся от бэкапа, если в архиве перевернулся один бит

Translate

Формат ночной копии обычно выбирают по размеру и скорости. Автор lzip Антонио Диас Диас пишет, что данных о надёжности форматов мало: разработчики редко публикуют что-то, кроме замеров скорости и сжатия. Цена одного испорченного бита так и остаётся неизвестной.

Я упаковал одни и те же 10 МБ в девять форматов, в каждом перевернул по одному биту в 200 случайных позициях и распаковал обычной командой. С настройками по умолчанию порчу пропускал только tar.br: код 0 при испорченных файлах в 55 случаях из 200 на исходниках и в 180 из 200 на смеси с несжимаемыми данными. Остальные восемь завершились с ошибкой во всех 200 случаях, но потери у них разные: zip почти всегда терял один файл, а tar.xz обычно всё, что лежало после бита. На смеси в 44 случаях из 200 tar.xz обошёлся одним файлом.

Как я портил архивы

Наборов два, оба по 10 МБ. В text лежат 450 файлов .py из стандартной библиотеки Python. В mixed 312 файлов: по объёму половина те же исходники, половина случайные байты, которые сжимаются примерно как фото или уже сжатые данные.

Один случай выглядит так: берём копию архива, переворачиваем бит, распаковываем, запоминаем код возврата и сверяем sha256 каждого файла с манифестом. В каждом случае бит стоит на одной и той же доле длины архива у всех форматов.

tar -I xz --no-same-owner -xf copy.tar.xz -C out   # и так же gzip, bzip2, zstd, lz4, brotli, lzip
unzip -qq -o copy.zip -d out
7z x -y copy.7z -oout

Дальше «молчит» значит одно: код 0, а хотя бы один файл не совпал с манифестом. Потерянным считаю файл, который испорчен или не распаковался. Контроль без порчи у всех вариантов прошёл чисто.

Безвредных битов не нашлось ни одного.

Что осталось от 450 файлов

Формат

Размер, % от исходного

Молчит, из 200 (text / mixed)

Целых файлов, медиана, %

Целых файлов в худших 10% случаев, до %

Потеряно не больше 3 файлов, из 200

tar.gz

23,3

0 / 0

98,2

26,4

77

tar.zst

22,9

0 / 0

97,8

28,2

60

tar.lz4

37,1

0 / 0

85,8

12,9

0

tar.bz2

18,4

0 / 0

53,6

0,0

0

tar.xz

16,8

0 / 0

58,2

4,9

1

tar.lz

17,0

0 / 0

58,2

5,1

1

7z

17,1

0 / 0

58,0

5,1

0

tar.br

16,7

55 / 180

0,0

0,0

39

zip

26,1

0 / 0

99,8

99,8

199

Все столбцы, кроме «Молчит», относятся к набору text. Ноль из 200 — это оценка: с доверием 95% незамеченной порчи не больше 1,9%.

tar.br: встроенные проверки пропускают порчу

В формате brotli нет поля контрольной суммы потока, и на утилите это видно. Обычно незамеченная порча касается немногих файлов: медиана 2 на text, 1 на mixed. Но бывает и хуже: в одном случае код 0 пришёл при 86 испорченных или пропавших файлах.

Проверить такой архив его же средствами не получилось. На mixed я взял 80 испорченных архивов: brotli -t и tar -tf вернули 0 на 71, хотя распакованный поток отличался от исходного во всех 80.

Когда brotli всё же замечает ошибку, остаётся пусто: все 145 отказов на text и все 20 на mixed дали ноль файлов. Поэтому медиана в таблице 0%. При обрыве потока на половине brotli тоже выдал 0 байт из 10 373 120, а gzip, xz и zstd отдали примерно половину. Похоже, на таком размере утилита не успевает ничего вывести до ошибки. В код я не смотрел, и на больших архивах это не проверял.

Если выключить контрольную сумму

Диас Диас считает необязательную проверку в xz и zstd слабым местом форматов, а спецификация LZ4 Frame разрешает декодеру игнорировать суммы. В обычной команде этого риска нет: zstd и lz4 включают сумму сами, и незамеченных у них 0 из 200. Он появляется, если её выключить при упаковке:

Вариант

Молчит, из 200 (text / mixed)

zstd --no-check

120 / 178

lz4 --no-frame-crc

146 / 191

xz --check=none

0 / 44

На чистом тексте xz без суммы не промолчал ни разу. Видимо, порчу сжатых данных там ловит сам декодер, а несжимаемые участки проходят мимо него. Эту причину я вывел из разницы наборов и отдельно не проверял.

У tar.lz4 сумма, наоборот, сама добавляет потери. Без суммы медиана 99,8% целых файлов, с суммой 85,8%: утилита при отказе отрезает хвост вывода. На архиве 10 МБ это почти всегда всё сверх 8 МиБ при любом размере блока, на 41 МБ потеря зависела от блока. Точной причины я не знаю. Повторная распаковка того же архива через lz4 --no-crc подняла медиану с 85,8% до 99,8%, и потерю не больше трёх файлов дали 60 случаев из 80 вместо нуля. Для zstd --no-check и xz --ignore-check то же самое почти ничего не дало: 95,8% → 96,0% и 55,8% → 55,8%.

Потеря всего или одного блока

В обсуждении на Hacker News в 2016 году один участник писал, что порча сжатого tar «may lead to the loss of everything», другой отвечал, что у gzip и bzip2 один бит стоит максимум блока. Цифр не привёл никто. Полная потеря действительно возможна, но зависит от места бита, а обещание про один блок при обычной распаковке не выполнилось.

На исходниках bzip2, xz, lzip, 7z теряют всё после бита. Число целых файлов на text почти линейно зависит от позиции: корреляция 0,96–0,99. Значит, медиана 53–58% в таблице показывает случай, когда бит попал примерно в середину архива. Корреляция не показывает, какие именно файлы пропали, поэтому я отдельно сверил это на 40 новых случаях для каждого формата. На text у всех четырёх после первого потерянного файла целых не осталось ни разу. На mixed то же у bzip2 и lzip, а у xz и 7z целые файлы после потери остались в 10 случаях из 40. В основной серии на mixed у них пострадал ровно один файл в 44 и 42 случаях из 200. У xz это те же 44 позиции, что молчали с --check=none. У bzip2 в 25 случаях из 200 не осталось ни одного файла. Утверждение про один блок при обычной распаковке не подтвердилось; bzip2recover я не проверял.

tar.gz от случайного бита всё не терял. Ни один из 200 случаев не дал ноля файлов, худший оставил 15 из 450. Полная потеря у него всё же бывает, если бит попал в начало файла: об этом ниже. Но и «один файл» здесь не гарантия: не больше трёх файлов потеряно в 77 случаях из 200 на text и в 168 из 200 на mixed, а худшие 10% случаев на text сохранили не больше 26,4%. gzip доводит поток до конца, но в 45 случаях из 80 длина выхода отличалась от исходной, в самых частых вариантах на единицы байт. Думаю, из-за этого tar теряет выравнивание и следующие заголовки, хотя напрямую я такую связь не мерил. tar.zst ведёт себя похоже.

zip почти всегда теряет один файл, потому что сжимает файлы по отдельности: ровно один файл пострадал в 199 случаях из 200 на text и в 200 из 200 на mixed. В оставшемся случае на text не распаковались 47 файлов: бит лежал в самом конце архива, на 99,88% его длины. Об этой защите говорили и на HN. Цена на исходниках: 26,1% против 16,8% у tar.xz. На mixed разница почти исчезает: 63,2% против 60,5%, а tar.gz с его 64,6% даже больше zip.

У zip есть слабое место, которое случайные позиции почти не задевают. Я перебрал биты служебных зон на архиве в 1 МБ, и на последних 32 байтах 77 битов из 256 привели к потере всего. Из 1750 проверенных битов центрального каталога 6 дали полную потерю, а 29 прошли с кодом 0 при несовпадении с манифестом. Эти 29 я не разбирал.

У потоковых форматов та же беда в начале файла: бит в первых 32 байтах давал полную потерю в 206–256 случаях из 256, у zip в 9. 7z в этой проверке оказался хрупким с обоих концов: 248 и 256 из 256.

Код возврата не показывает, какие файлы сохранились

Диас Диас предупреждает, что сумма сама может испортиться: данные целы, а декодер их отвергает. На архиве в 1 МБ это выглядит так: из 256 битов в последних 32 байтах ненулевой код при полностью целых файлах дали 256 у tar.xz, 243 у tar.lz и 131 у tar.gz. Если копия из таких не распаковалась чисто, это ещё не повод её выбрасывать: файлы могли лечь на диск целыми.

Обратный случай я не планировал. На tar.bz2 9 битов из 256 в тех же 32 байтах дали код 0 от tar -xf при испорченном bdb.py. bzip2 -t поймал все девять. В 8 случаях tar напечатал A lone zero block: порча дала блок нулей, tar счёл его концом архива и вышел с нулём. Случай редкий, на 200 случайных позициях его не было, и видел я его только на bzip2 и одном архиве.

Проверять копию лучше самим распаковщиком, а не tar -xf.

Что из этого взять в скрипт копии

  • Рядом с архивом держать хеш: sha256sum меняется от любого бита. Для tar.br другой рабочей проверки я не нашёл.

  • Не выключать сумму при упаковке: --no-check, --no-frame-crc, --check=none.

  • Если после порчи нужна хотя бы часть файлов, сравнивать форматы по столбцу потерь. На mixed zip по размеру почти не проигрывал: 63,2% против 60,5% у tar.xz.

  • Разбирать отказ по файлам. Ненулевой код бывает при целых данных, а у lz4 стоит попробовать --no-crc.

Границы вывода: один перевёрнутый бит, два набора по 10 МБ, по 200 случаев на вариант. Частоту такой порчи на дисках и лентах я не мерил, битые секторы не проверял. Обрыв копии уже разбирали на Хабре. На другом размере архива цифры сдвинутся, особенно у lz4.

Версии: gzip 1.13, bzip2 1.0.8, xz 5.8.1, zstd 1.5.7, lz4 1.10.0, brotli 1.1.0, lzip 1.25, Zip 3.0, UnZip 6.00, 7-Zip 25.01, GNU tar 1.35, образ python:3.15-slim.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.