1本、消えました。
まとめて探すために作った索引の、元のファイルです。app.log.9.gz が保存期間を過ぎて消えた——そのとき、索引はどうあるべきでしょうか。
以前のこちらは、作り直していました。それが正しくないと気づくまでの話です。
先に結論: 束ねる1回目は ripgrep に 1/1.58 の負け。2回目からは 9.77秒(ripgrep 22.16秒の 2.27倍)。そして元ファイルが1本消えても、全部消えても、索引を作り直さずそのまま探せます
ripgrep は状態を持ちません。同じログ群を何度探しても、毎回ゼロからファイルを読みます。こちらは1回目に束ねた .uwvz(uvp 用の圧縮形式+索引)を作り、2回目からはそれを読みます。gz 7 本で、1回目は 35.70秒 対 ripgrep 22.54秒=1/1.58 の負け、2回目は 9.77秒 対 22.16秒=2.27倍。さらに v1.7.2 から、元ファイルが減った・見つからない・読めないときは作り直さず、前の索引で探します(Keep)。全部消えても探せます。過去ログを消したあとでも、束ねた索引の中身は残っているからです。Mac(Apple M4・10コア)・メモリ32GB・外付け USB SSD・OpenStreetMap の XML。
uvf・uvpは私が作っている UwView(v1.7.3.6 “Wide Field”・2026年9月28日公開)の検索コマンドです。uvfは無料版、uvpは有料版 UwView Pro のものです。速さの数字は v1.7.3.6 を 2026年9月29日に測ったもの、作り直しの規則は v1.7.2 で入れたものです。
先に負けを書きます
- 束ねる1回目は、はっきり負けます。 gz 7 本で 1/1.58。索引を書くぶんです。1回しか探さないなら、無料の
uvfか ripgrep のほうが速い。 - ディスクを食います。 平文5本+gz7本=12 本を束ねた索引は約 8.2GB でした(
uvp1.7.3.5 での計測例)。元ファイルを残したままなら、その分が上乗せになります。 - 「増えた分だけ足す」経路の速さは測っていません。 仕様としては実装済みですが、追記が何秒で終わるかの計測が無いので、この記事では速さを書きません。
ripgrep は、毎回ゼロから読む
まず相手の設計を確認します。ripgrep は検索のたびにファイルを開いて読みます。前回の結果も、前回読んだ内容も、何も持ち越しません。
これは弱点ではなく、選んだ設計です。状態を持たなければ、古い状態と実体がずれる事故が起きません。ファイルが消えていようが増えていようが、そのときの実体がそのまま答えになります。インストールしてすぐ使えるのも、この設計のおかげです。
代わりに、同じログ群を10回探せば10回読みます。回数が増えるほど、同じ仕事を繰り返します。
こちらは逆を選びました。1回目に束ねた索引を作り、2回目からはそれを読む。そのかわり、「実体と索引がずれたときどうするか」という問題を自分で引き受けることになります。
束ねた索引は、名前で突き合わせる
uvp 'osm17/*.gz,osm17/*.osm' 東京 のように渡すと、1回目に対象を展開して1本の .uwvz にまとめ、そのまま探します。2回目に同じコマンドを打つと、索引が使えるかどうかを確かめてから読みます。
確かめ方が、最初は位置でした。1本目・2本目・3本目……と並び順で照合していたのです。
これが事故のもとでした。途中の1本が消えると、それ以降が全部「並びが変わった」に見えます。結果、まるごと作り直しに倒れる。logrotate を使っていれば、古いファイルが消えるのは日常です。
v1.7.2 で、名前で突き合わせるように直しました。1本消えても、残りは同じ名前のまま同じ部品として見つかります。
「消えたら作り直す」をやめた
名前で突き合わせられるようになったので、消えたときの振る舞いそのものを決め直しました。
| 状態 | 以前(v1.7.1.x) | いま(v1.7.2 以降) |
|---|---|---|
| 元ファイルが減った・見つからない・読めない | 作り直し(消えた中身を失う) | 作り直さず、前の索引で探す。警告を出して --rebuild を案内 |
| 対象が0本(全部消えた) | 「一致なし」でエラー | 前の索引で探せる |
| 増えたものと減ったものが同時 | 作り直し | 作り直さない(足しもしない) |
| 中身が変わった・並びが変わった(全部そろっている) | 作り直し(予告あり) | 同じ |
--rebuild |
— | 古い索引を .bak-日時 に退避してから作り直す |
zip の中のエントリが減ったときも同じ扱いです。
古い索引で探して、正しいのか
ここがいちばん疑われるところだと思うので、見出しを立てます。
正しくない場合があります。だから警告を出します。
索引は、束ねた時点の中身を持っています。元ファイルが消えたあとにその索引を探すと、消えたファイルの中身が結果に出ます。実体にはもう無い行です。
これを「間違い」と呼ぶか「保管」と呼ぶかは、使い方で変わります。保存期間を過ぎて消えた古いログを、あとから追いたいことはあります。作り直してしまうと、その中身は永久に失われます。
だから既定は「作り直さない」にしました。そのうえで、ずれていることを黙らない。消えたファイルの名前を出して警告し、実体に合わせたいなら --rebuild を、と案内します。--rebuild は古い索引を .bak-日時 に退避してから作り直すので、案内に従っても過去の中身は消えません。
壊れるかもしれない操作を、黙って既定にしない。 決めたのはそれだけです。
増えた分は、名前の並びの位置に足す
逆に、対象が増えたときは索引に足します。名前の並びで途中に入るファイルでも、索引の末尾に追加する形にしました(順序は探すときに解決します)。.ignore などで対象から外れた部品は、索引には残したまま探さない扱いにします。
繰り返しになりますが、この追記が何秒で終わるかは測っていませんので、速さの話としては書きません。書けるのは「全部作り直しではない経路がある」という仕様までです。
logrotate の下で、どう動くか
実際の運用に当てはめます。app.log が毎日ローテートされ、古いものから .gz になり、保存期間を過ぎたものが消えていく——よくある形です。
- 今日の分が増えた … 索引に足す
- 一番古い
.gzが消えた … 作り直さない。索引の中の古い行は、まだ探せる - 増えたものと消えたものが同時に起きた … 作り直さない(足しもしない)
- 実体に合わせたい …
--rebuildを明示的に打つ。古い索引は.bak-日時に残る
つまり、索引は「実体の写し」ではなく「探すための保管庫」として振る舞います。ripgrep の「実体がそのまま答え」とは、性格が違います。どちらが正しいというより、何を答えにしたいかの違いです。
数字
gz 7 本(osm17 の OpenStreetMap XML・当たり 122,441 件)を1コマンドで探したときの値です。
| ripgrep | uvp |
|
|---|---|---|
| 1回目(cold・索引を作りながら) | 22.54秒 | 35.70秒=1/1.58 |
| 2回目(hot・索引を読む) | 22.16秒 | 9.77秒=2.27倍 |
- 1回目に余分に払うのが約 13秒、2回目からは1回につき約 12秒ずつ縮むので、2回目でほぼ取り返します。
- 件数は ripgrep と一致しています。同じ日の統合テストでは、複数ファイルの判定 36 項目が不一致 0 件でした。
- 作り直しの規則そのものは、「1本消す」「全部消す」を含む複数ファイルの試験で 27 項目すべて合格しています(v1.7.2 の確認時)。
測り方
- ファイル:
osm17の OpenStreetMap(日本)XML・gz 7 本 - 語:
東京(固定文字列・行番号つき) - 状態:
sudo purge後の 1回目(cold) と、続けて走らせた 2回目(hot) を分けて記録 - 相手: ripgrep 15.2.0(
rg -z -n -F) - こちら:
uvf/uvpv1.7.3.6 “Wide Field”(2026年9月29日の統合テスト)。索引の大きさの例はuvp1.7.3.5・12 本の別計測 - Mac(Apple M4・10コア・メモリ32GB)/外付け USB SSD。秒数は環境によって変わります
持ち帰れる一般論
(1)状態を持つと決めたら、「ずれたとき」の規則まで含めて設計になる。 索引を持つ利点(2回目 2.27倍)と、実体とずれる問題は同じ選択の表と裏です。速さだけ取って、ずれの扱いを後回しにすると、以前のこちらのように「消えたら作り直す」といういちばん損な既定に落ち着きます。
(2)突き合わせの鍵に「位置」を使わない。 並び順で照合すると、1つ抜けただけで以降が全部ずれます。名前のような、要素そのものに属する鍵で照合すれば、抜けは抜けとして局所に収まります。これは索引に限った話ではありません。
(3)取り返しのつかない操作を、既定にしない。 作り直しは「消えた中身を失う」操作でした。失われる側に倒れる既定は、たとえ整合性のためでも既定にしない。ユーザーが明示的に頼んだとき(--rebuild)だけ実行し、それでも退避を残す。
(4)測っていない機能の速さは書かない。 追記の経路は実装済みですが、秒数が無いので速さとして書きませんでした。仕様と計測は別物で、混ぜると記事全体の数字が疑われます。
正直に書いておくこと
- 数字はすべて Mac(Apple M4・10コア)・メモリ32GB・外付け USB SSD・OpenStreetMap の XMLでの測定例です。環境が変われば変わります
- 索引の大きさ(約 8.2GB)は、平文5本+gz7本=12 本を束ねた別の計測例です。この記事の速さは gz 7 本のもので、同じ組の索引の大きさは記録していません
- 追記(増えた分だけ足す)の所要時間は未計測です。仕様としてのみ書いています
- 元ファイルが消えたあとに古い索引で探すと、実体に無い行が出ます。 警告は出しますが、実体と一致した結果が要るなら
--rebuildが必要です - 1.5倍未満は「差」と呼んでいません。この記事で「差」と書いたのは 1/1.58 と 2.27倍だけです
- Windows と Linux の結果は持ち込んでいません。 同じ日の統合テストで3 OS とも不一致 0 件でしたが、機械も媒体も違うので秒数は OS をまたいで比べません
- 私は比較の一方の開発者です。その分は割り引いてお読みください
使っている道具
UwView は、巨大なテキストファイル・巨大ログを調べるための CLI/GUI ツールです。v1.7.3.6 “Wide Field” では、複数のファイルと 7 つの圧縮形式を、外部のコマンドなしで1回で探せます。
uvp ならこう書く
# 1回目:束ねた .uwvz を作りながら探す(UwView Pro)
uvp 'app.log,app.log.*.gz' ERROR
# 2回目以降:同じコマンドで、索引を使う(UwView Pro)
uvp 'app.log,app.log.*.gz' ERROR
# 実体に合わせて作り直す(古い索引は .bak-日時 に退避される)
uvp 'app.log,app.log.*.gz' ERROR --rebuild
# 対象に何が入るかだけ先に見る(無料)
uvf 'app.log,app.log.*.gz' --files
# 索引を持たずに、その場で探す(無料)
uvf 'app.log,app.log.*.gz' ERROR
# 束ねた .uwvz から取り出す(平文・gz・zip は元の形、他は平文になる)
uvp app.uwvz -extract -out ./restored/
終了コードは grep と同じで、0=あり・1=なし・2=エラーまたは結果が不完全です。
無料版は GitHub Releases から。Mac は brew install --cask amru195704/uwview/uwview、Windows は Scoop(scoop bucket add uwview https://github.com/amru195704/scoop-uwview → scoop install uwview)でも入ります。
リンク
- 圧縮ログの検索を ugrep・ripgrep・zgrep と比べた(7 形式・他の道具も含めた一覧): https://uvp.y42u.net/blog/uwview-ugrep-ripgrep-zgrep-compressed-logs/
.uwvzを作って、中を探して、取り出す: https://uvp.y42u.net/blog/uwvz-compressed-archive-search-extract/- ログは圧縮したほうが、速く探せます(形式の選び方): https://uvp.y42u.net/blog/log-compression-format-choice/
- gz を展開せずに探す(1本の gz の話): https://uvp.y42u.net/blog/uwview-fl08-gz-without-extract/
- 読む回数だけを減らした13回のまとめ: https://uvp.y42u.net/blog/uwview-fl13-first-light-recap/
- 実測の全データ(ベンチマーク一覧): https://uvp.y42u.net/benchmarks/
- ソースコード(GitHub): https://github.com/amru195704/UwView
そして、ストレージを専有している巨大ログを圧縮して保管し、さらに高速に検索したいなら UwView Pro をどうぞ。永続索引・圧縮キャッシュ検索・約 1/9 保管で、開き直しも検索も一段速くなります(全 OS 対応・買い切り/月額プランあり)。
開発者より: アプリ・Kindle 本・公開プロジェクトの一覧は GitHub: amru195704 にまとめています。

