0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

gzip のログは「探すたびに全部展開」している — ブロック圧縮+索引で 50GBの検索時間を 120秒から 7秒にした話

0
Posted at

ログは圧縮して置いてあるのに、調べるときは毎回もとに戻している——という運用をしていませんか。私はしていました。

50GB のログを gzip -6 で固めると 5.75GB になります。ここから「東京」を探すと 120.67秒。内訳は解凍 65.05秒+rg 55.62秒です。zip -6 にすると同じ 5.76GB なのに 203.06秒。そして圧縮率がほぼ同じ 5.74GB でも、7.21秒で探せる置き方があります。

同じ 1/9 に縮んでいるのに、探す時間が 17倍 違う。その差がどこから来るのかを、DEFLATE の仕様まで下りて整理します。

実測

Apple M4 / メモリ32GB / 外付け USB SSD、ripgrep 15.2.0、対象は OpenStreetMap 日本データ(XML)です。

固める

元ファイル gzip -6 zip -6 ブロック圧縮+索引
3.03GB 0.30GB(16.8秒) 0.30GB(19.4秒) 0.25GB(1.2秒)
10.26GB 1.12GB(66.5秒) 1.12GB(76.8秒) 1.16GB(14.9秒
51.25GB 5.75GB(347.2秒) 5.76GB(394.7秒) 5.74GB(65.0秒

探す(固定文字列1語)

サイズ gzip(解凍+rg) zip(解凍+rg) 索引つきを直接検索
3GB 6.47秒(3.49+2.98) 8.71秒(8.08+0.63) 0.68秒
10GB 24.70秒(13.12+11.58) 40.48秒(29.28+11.20) 1.74秒
50GB 120.67秒(65.05+55.62) 203.06秒(146.97+56.09) 7.21秒

圧縮率は3方式ともほぼ同じです。違うのは探し方のほうです。

なぜ gzip は「全部展開」しかできないのか

gzip(DEFLATE)はひとつながりのストリームです。途中から読み始められないのは、次の2つが前を引きずるからです。

ひとつは LZ77 のスライディングウィンドウ。DEFLATE は「32KB 手前の、この位置から N バイトをコピー」という参照でデータを表します。したがって 40GB 目を復元するには、その手前 32KB が復元済みでなければならず、その 32KB を作るにはさらに手前が要る——と芋づる式に先頭まで遡ります。

もうひとつは ハフマン符号の境界がバイト境界に揃っていないこと。DEFLATE はビット単位で詰めるので、「ブロックの切れ目がファイルの何バイト目か」は解凍してみるまで分かりません。バイトオフセットで飛び込んでも、そこが符号の途中なのか先頭なのか判定できないわけです。

だから zgrepgzip -dc | grep と同じことをしています。圧縮の中を検索しているのではなく、展開しながら検索しているだけです。そして展開は毎回発生します。

zip はどうか。zip はエントリ単位では独立しているので、100個のファイルを固めた zip から1個だけ取り出すのは高速です。しかし今回のように巨大な1ファイルを1エントリで固めた場合、中身は同じ DEFLATE ストリームなので、gzip と同じ制約を受けます。上の表で zip が gzip より遅いのは形式の差というより実装の差(unzip の展開が 146.97秒 対 gzip -dc の 65.05秒)ですが、構造的な問題は同じです。

途中から読めるようにする方法

対策は昔からあって、どれも発想は同じです。ストリームを切って、切れ目の位置を覚えておく

  • BGZF(bioinformatics の bgzip): 64KB ごとに独立した gzip メンバとして圧縮し、別ファイルに索引を持つ。gzip として展開できる互換性を保ったまま、ランダムアクセスができる
  • dictzip: gzip の extra field にチャンク長の表を埋め込む。普通の gzip としても読める
  • zstd の seekable format: フレームを分けて、末尾にシークテーブルを置く
  • zlib の zran: 一度通しで読んで「一定間隔ごとのウィンドウ状態」を記録しておき、次回からそこへ飛ぶ

共通する代償は圧縮率の悪化です。ブロックごとに辞書がリセットされるので、ブロックをまたぐ繰り返しを拾えません。ブロックを小さくするほどランダムアクセスは細かくなり、圧縮率は落ちます。

検索を目的にすると、索引も要る

ブロック圧縮だけでは「途中から展開できる」までです。検索を速くしたいなら、展開の総量そのものを減らす必要があります。

私が作っている巨大ファイルビューア(UwView Pro)では、.uwvz という形式で次の3つを1つのファイルに入れています。

  1. ブロック単位に切った圧縮データ(ブロックごとに独立。展開はブロック単位)
  2. 行番号 → バイト位置の疎な索引(256行ごとに1点。2億行で約6MB)
  3. 文字コードや行数などのメタデータ

これで何が変わるか。50GB の検索で読むのは 5.74GB のブロックだけです。元の 51.25GB を読まないので、ディスクから流し込むバイト数が 1/9 になる。展開は CPU を食いますが、いまの CPU なら I/O の削減のほうが勝ちます。7.21秒という値はそこから来ています。

そして 2問目以降も 7秒のままです。gzip のほうは、展開したファイルを残しておけば2問目から rg の 55秒になりますが、それでは圧縮した意味がありません。

数字で並べるとこうなります。

圧縮率 展開せずに探せるか 元ファイルを消せるか
gzip / zip 1/8.9 ✕(毎回全展開) ○(戻せる)
BGZF / dictzip / seekable zstd やや落ちる △(途中から読めるが、探すのは自前)
ブロック圧縮+索引(.uwvz 1/8.9 ○(バイト一致で復元)

ログの保管形式を選ぶ3つの軸

一般化すると、選択の軸は3つです。

  1. 圧縮率 — 保管費用に直結する。gzip で 1/9、専用形式(OSM の PBF など)なら 1/20 まで行くこともある
  2. 展開せずに探せるか — 調べる回数が多いほど効く。「年に1回しか触らない」なら gzip で十分です
  3. 元に戻せるか — 監査や再処理のために原本が要るなら、可逆であることと、その復元にライセンスや特殊なツールが要らないことを確認しておく

3番目は見落としがちです。特殊な形式で固めた結果、「読むのに有償ツールが要る」状態になると、保管としては失格になりかねません。

再現

# 固める
time gzip -6 -c big.log > big.log.gz
time zip -6 big.log.zip big.log

# 探す(毎回の展開が入る)
time sh -c 'gzip -dc big.log.gz | rg -n -F "東京" > /dev/null'
time sh -c 'unzip -p big.log.zip | rg -n -F "東京" > /dev/null'

キャッシュの影響を外すには、macOS なら各計測の前に sudo purge、Linux なら sync && echo 3 | sudo tee /proc/sys/vm/drop_caches を挟んでください。挟まないと2回目以降がメモリ上の勝負になり、ディスクから読むバイト数の差が見えなくなります。実際、3GB のファイルはメモリに収まるので、温めてしまうと今回の差はほとんど消えます。

まとめ

  • gzip が「途中から読めない」のは、LZ77 のウィンドウとハフマンのビット境界という形式の性質であって、ツールの手抜きではない
  • zgrep は圧縮の中を探しているのではなく、展開しながら探している
  • 途中から読みたいなら、ストリームを切って位置を覚える(BGZF / dictzip / seekable zstd / zran)。代償は圧縮率
  • 検索を速くしたいなら、そこに索引を足して読むバイト数自体を減らす。50GB で 120秒 → 7秒 の差はここから出た

実測の全数値とスクリプトはこちらに置いてあります: https://uvp.y42u.net/blog/uwvz-compressed-archive-search-extract/

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?