一日一回 大きくてスパースなファイルを gzip -d してるあなたへ

忙しい人向け

タイトルの通りですが、N GBオーダのスパースなファイルを gzip -d して開発マシンに解凍している人に向けた spdc というツールを作成しました。 このツールを使うと、gzipで圧縮されたファイルをスパースなファイル として解凍できます。

github.com

スパースファイルとは?

初めにスパースなファイルについて説明します。

面倒なので ChatGPT-4.0 に任せます(隙あらば課金自慢)

スパースファイルとは、実際には空であるが、システムに対してはデータが存在するかのように振る舞う特殊なファイルのことを指します。この種のファイルは、大量の0の連続部分(つまり、何も情報を持たない部分)をディスク上に実際には書き込まず、空いていると報告することでディスクスペースを節約します。そのため、非常に大きなファイルを作成する場合でも、スパースファイルを使用すれば実際には少量のディスクスペースしか使用しないことができます。

例えば、100GBのスパースファイルを作成したとします。しかし、そのうち実際にデータが書き込まれている部分は1GBだけだとします。この場合、そのファイルは100GBのサイズがあるとシステムに報告しますが、実際にディスク上に占有しているスペースは1GBだけです。このようにスパースファイルは、ディスクスペースを効率的に利用するための手段となります。

しかし、すべてのファイルシステムやアプリケーションがスパースファイルをサポートしているわけではないため、スパースファイルを取り扱う際には注意が必要です。スパースファイルをサポートしていないシステムでスパースファイルを扱うと、ファイルが実際のサイズ(この場合、100GB)に膨らんでしまう可能性があります。

らしいです。非常に的を射てるというか自分より詳しいまであります。

なにが嬉しいの?

あるあるなのは ubuntu-2204.img.gz など 仮想マシンイメージや、raw イメージをgzipで受け取りそれを解凍する際にディスクフルになってイラッとしがちです。

今回は gzip -d の代わりとなるツールを作成しました。

ちなみに README もChatGPTに書いてもらいました。実質コピペしかしてないまであります。

以下実際に利用したイメージです。READMEにほぼ同様のこと書いてます。

spdc(本ツール) を利用した場合

spdcで解凍

gzip -d を利用した場合

gzip を利用した場合

見てもらいたいのは du -h の結果です。これは実際にストレージとして利用しているサイズになります。 spdc を利用して解凍した場合は、ファイルのサイズとしては 40 GB ですが、実際にディスク容量を消費しているのが 1.8 GB というかなりディスクに優しいデータになっていることがわかります。

書き込み時に並列化しているので spdc で解凍した方が早いのですが、 自分が gzip -d のオプションを知らないだけ説が濃厚です。 また、cp コマンドを利用することで、 スパースなファイルに変換できるらしいということは知っているのですが、調査するより作った方が早かったので作りました。

実装について

あんまり語ることがありません。ChatGPTにgolangでスパースなファイル作れたりする?と聞いたら作れるらしいと知ったので、開発しました。 大事なのはここの部分だと思います。

さらにすることがあるのか?

他の圧縮に対応

内部的には各圧縮アルゴリズムの io.Reader でラップしたものを DecompressSparseReader に渡せばいいので、だれかPR送ってください。 いまさらだけど、ここ Reader じゃないっすね。Copy とかそんな感じの名前の方が良さそう。。。(いつか直す)

func DecompressSparseGzip(src, dst string) error {
    w, err := os.Create(dst)

    if err != nil {
        return err
    }
    defer w.Close()

    f, err := os.Open(src)
    if err != nil {
        return err
    }
    defer f.Close()

    gr, err := gzip.NewReader(f)
    if err != nil {
        return err
    }

    return DecompressSparseReader(gr, *w)
}

高速化

現状は、golang の標準ライブラリの gzip.Reader を利用しているのですが、こいつが解凍後の size を private な変数にしているため、ちょっと面倒な実装になってます。gzip.Readerの実装を見ると簡単そうなので、ここら辺を作り直すとより早くできるかも...? という印象でした。

また、いまだに os.File に対する Write の lock についてよくわかってないので、誰か教えてください。

最後に

スパースなファイルについて検証しているときに 「はー謎」ってなっていたことがあったのですが、 SNSソーシャルネットワークサービス)という文明の力を使ったところ一瞬で解決しました。

ていう話を追記しているときに、さらに知恵が舞い込んできたので、すごいなーっと思いました。