tar.gzをそのままgrep出来るツールを作った話
TL;DR
tarのgrepを2000倍高速化した
$ time tar xf test_data.tar -O | rg hogeP # 速いがファイル名が分からない
gRRay4bho5P4hZZWvBDCX50cX2fJAyLNhogePvGaFWwaPFdmi3Y8zvJai2OLpQ13+tZB2zm8KbAI
real 0m1.392s
user 0m0.828s
sys 0m1.513s
$ tar xf test_data.tar '--to-command=grep --label=$TAR_FILENAME -H hogeP; true' # ファイル名は分かるがすごく遅い
test_data/863227.txt:gRRay4bho5P4hZZWvBDCX50cX2fJAyLNhogePvGaFWwaPFdmi3Y8zvJai2OLpQ13+tZB2zm8KbAI
real 24m21.333s
user 20m9.365s
sys 4m32.069s
$ tzgrep hogeP test_data.tar # ファイル名も分かるし速い (今回作ったツール)
test_data/863227.txt:gRRay4bho5P4hZZWvBDCX50cX2fJAyLNhogePvGaFWwaPFdmi3Y8zvJai2OLpQ13+tZB2zm8KbAI
real 0m0.644s
user 0m0.475s
sys 0m0.168s
cargo install tzgrep
でインストール出来る。
動機
小さいテキストファイルが大量に(数百万、数千万オーダー)入った.tar.gz
に対してgrep
をかけようとすると上のようになってファイル名が分からないかすごく遅いということが分かった。かと言って展開するのも時間がかかる上にディスク容量の無駄である。
なんとかならないか調べているとtarファイルの構造は結構単純であることが分かった。
なので、tar.gzをそのままgrep出来るツールを作って見ることにした。
ちなみに --to-command
を使うバージョンがなぜ遅いかと言うとファイルごとにgrep
コマンドを起動しているためである。つまりgrep
の文字検索自体は速いが、コマンドの起動回数が多いため時間がかかっている。
1番目と3番目はそのようなことはしていないので速い。
tarのフォーマット
512バイトのブロックがいくつか連なった形をしている。ファイルごとにヘッダブロックが存在し、それに続いてファイルの中身のブロックが0個以上存在する。tarの末尾2ブロックは0で埋められている。
headerのフォーマット
詳しくは man 5 tar参照
tarと言っても何種類かフォーマットが存在するが、今回はgrepしたいだけなのでname
とsize
とtypeflag
だけパースしている。幸いこの3つはどのフォーマットでも位置が変わらないので特に難しいことはなかった。
若干ハマったポイントとしてはsize
などが8進数のテキストで表現されている点がある。
実装していて気がついたことなど
最初はnom
でパースしようと思ったが、欲しい部分は上で書いたように単純だったのとnom
の使い方がよく分からなかったので結局自前でパースした。
Readトレイトのtakeを知らなくて自前で同様のことをするstruct
を実装してしまったが要らなかった。
grepの結果と一緒にテキストファイルの後にNUL文字で埋められている領域も表示してしまっていたが、普通に端末に表示すると表示されないため気づくのに時間がかかった。
tarにバイナリファイルが含まれている場合の処理が少し面倒だった。
圧縮ファイルの展開はそれぞれ簡単に使えるクレートがあったので簡単だった。
clapの#[derive(Parser)]
はfeatureを追加する必要があるという所で少しハマった。
zstdの展開が思ってた以上に速かった。
おわりに
tarをそのままgrepする機会があれば是非使ってみて下さい。
バグや要望があれば気軽にissueを立てて下さい。
Discussion