Zstandard ファイル形式
Zstandard で圧縮されたデータ。Meta 発の高速で現代的な圧縮形式。
- MIME タイプ
- application/zstd
- 拡張子
- .zst
- カテゴリ
- アーカイブ・圧縮
マジックナンバー(ファイル署名)
| 0x00000000 | 28B52FFD | (·/· | Zstandard magic |
Zstandard 形式について
Zstandard は gzip 並みの圧縮率をはるかに高速に達成し、レベルも細かく調整できるため、パッケージマネージャーやファイルシステムで広く採用されています。リトルエンディアンのマジックナンバーは 28 B5 2F FD です。.zst ファイルはフレームの連なりで、各フレームは 1 バイトの記述子で始まります。その記述子のビットが、後続のどのフィールドがそもそも存在するかを決めます。つまりヘッダが固定 10 バイトの gzip と違い、Zstandard のヘッダは 6 バイトから 18 バイトの間で伸縮し、5 バイト目より先に固定のオフセットは存在しません。手で読むならまずこの記述子をデコードするしかなく、近道はありません。
ヘッダーの構造(バイト単位)
| オフセット | 長さ | フィールド | 意味 |
|---|---|---|---|
| 0 | 4 | Magic number | ディスク上では 28 B5 2F FD です。仕様書はこれを 0xFD2FB528 と書きます。Zstandard の多バイト値はすべてリトルエンディアンなので、数値の表記とバイトの並びが互いに逆さまに見えるわけです。 |
| 4 | 1 | Frame header descriptor | 他のすべてが依存するバイトです。ビット 7-6 がフレームコンテンツサイズのフィールド幅、ビット 5 が single-segment フラグ、ビット 4 は未使用、ビット 3 は予約で 0 でなければならず、ビット 2 が末尾のコンテンツチェックサムを有効にし、ビット 1-0 が辞書 id の幅を与えます。コマンドラインツールからは A2 や A4 がよく出てきます。 |
| 5 | 1 | Window descriptor | 記述子のビット 5 の single-segment フラグが立っていないときだけ存在します。デコーダがバッファすべきウィンドウサイズを符号化しています。フラグが立っているとき、つまり入力全体が 1 セグメントに収まるときは、このバイトは存在せず、代わりにここからコンテンツサイズが始まります。この条件分岐を間違えるのが、zstd の解析における古典的なバグです。 |
| variable | 0-4 | Dictionary id | 記述子の下位 2 ビットが選ぶ、0・1・2・4 バイトのいずれかです。0 でない値は、そのフレームが共有辞書に対して圧縮されたことを意味し、バイトがどれだけ無傷でもその辞書なしにはデコードできません。 |
| variable | 0-8 | Frame content size | 展開後の長さをリトルエンディアンで、0・1・2・4・8 バイトのいずれかで表します。200000 バイトの入力は 40 0D 03 00、48 バイトの入力は 1 バイトの 30 として格納されます。エンコーダがストリーミングで長さを事前に知らなかった場合は、まるごと存在しません。 |
| variable | 3 | Block header | 各ブロックの先頭に置かれる 24 ビットのリトルエンディアンのフィールドです。ビット 0 が最終ブロックであることを示し、ビット 1-2 が種別(0 が raw、1 が RLE、2 が圧縮済み、3 が予約)、ビット 3-23 がサイズです。圧縮できない入力は raw ブロックになり、00 00 10 は 131072 バイトの raw ブロック、6D 00 00 は 13 バイトの最終圧縮ブロックです。 |
| -4 | 4 | Content checksum | 元データに対する XXH64 の下位 32 ビットで、フレームヘッダ記述子のビット 2 が立っているときだけ存在します。既定では有効なので、チェックを無効にして書かれたファイルは、単に最終ブロックの最後のバイトで終わります。 |
似た形式との見分け方
| 紛らわしい形式 | 見分け方 |
|---|---|
| skippable frames | .zst は必ずしも 28 B5 2F FD で始まりません。skippable フレームはマジックナンバー 0x184D2A50 から 0x184D2A5F を使い、リトルエンディアンで 50 2A 4D 18 から 5F 2A 4D 18 として格納され、そのあとに 4 バイトのリトルエンディアンのサイズが続きます。デコーダはこれを飛び越え、アプリケーションは独自メタデータの埋め込みに使います。したがってオフセット 0 に圧縮フレームのマジックを要求する検出器は、まったく正しいファイルを拒否します。 |
| XZ | 同じ仕事を巡る直接のライバルで、どちらを使うべきかという検索の大半はこの比較です。FD 37 7A 58 5A 00 と 28 B5 2F FD。最大設定での圧縮率は今も xz が勝ちますが、Zstandard は両端の速度で圧倒的に勝ち、レベル 19 でも十分渡り合います。 |
| GZIP | 1F 8B と 28 B5 2F FD。Zstandard は gzip の圧縮率を数倍の速度で出すことを明確な狙いとして設計されたので、同じニッチを占めていて、そのまま差し替えられます。ビルドスクリプトの中で .zst が .gz という名前になってしまうのは、まさにそういう経緯です。 |
| ZIP | ZIP の圧縮方式 93 は Zstandard なので、マジックがオフセット 0 ではなく ZIP エントリの中に現れることがあります。一部の書き手以外での対応は薄いため、そういうアーカイブは、他のあらゆる ZIP を問題なく読めるツールでもしばしば開けません。 |
| TAR | .tar.zst はパッケージのペイロードとしてごく普通の形です。フレームの中には、単一ファイルを圧縮したものと区別できるものが何もありません。展開して、出力のオフセット 257 が ustar かどうかを確認してください。 |
よくある壊れ方
- 長さが宣言されたままの途中切れ。フレームコンテンツサイズがヘッダにあった場合、ツールは想定される出力サイズをまるごと報告してから、途中でフレームの想定外の終端として失敗します。数値はデータが途切れる瞬間まで正しく見えます。
- チェックサムを無効にして書かれたフレーム。末尾の 4 バイトが単に無いので、出力を検証するものがありません。raw ブロックの内側の破損は、エラーも出さずに誤ったバイトを生みます。raw ブロックはそのままコピーされるだけで、脱線するエントロピー符号が存在しないからです。
- 辞書の欠落。辞書 id フィールドが 0 でないフレームは、ディスク上のバイトがすべて無傷でも、その辞書そのものが無ければデコードできません。破損エラーではなく辞書の不一致として失敗するので、存在しない損傷を探しに行く羽目になります。
- 連結されたフレームを 1 つとして読んでしまうケース。.zst の連結は合法で、標準ツールは全フレームを順に展開します。最初のフレームの最終ブロックで止めるリーダーは、黙って結果を切り詰めます。間に skippable フレームが挟まっていると事態はさらに悪化します。
- テキストモード転送。ヘッダは生き残り、宣言されたコンテンツサイズも正しく読め、最初の圧縮ブロックの中でデコードが失敗します。つまり実際に使う瞬間まで、ファイルは正しい Zstandard として認識され続けます。
この形式の現在地
Zstandard はここ数年で汎用圧縮の既定の座に就き、今も広がり続けています。Arch と Fedora のパッケージ、Debian のパッケージペイロード、btrfs と ZFS の透過圧縮、コンテナイメージのレイヤー、Kafka と RocksDB の内部、そして Linux が自前の initramfs イメージに使う圧縮まで、すべてこの上に乗っています。しかも単一プロジェクトの形式ではなく、RFC 8878 として公開されたインターネット標準です。理由はどれか 1 つの数字ではなく、トレードオフ曲線の形にあります。既定レベルで gzip の圧縮率をおおむね維持しながら圧縮は数倍速く、展開は速いうえにレベルにほとんど左右されません。つまり高いレベルを選んでも、代償を払うのは書き手だけで、読み手は一切払いません。他のよくある形式にはない辞書対応もあり、これが似たような小さいペイロードを大量に圧縮する場面を一変させます。今も負けているのは、保管用の最大圧縮率で xz と LZMA が先を行っている点と、長い尾を持つ互換性で gzip が無敵である点です。
Zstandard ファイルをこのサイトで開く
どれもブラウザ内で動きます。ファイルが端末から出ることはありません。
関連する形式
Zstandard と混同しやすい形式です。先頭バイトを見比べてください。