HTTP通信データの圧縮アルゴリズムについて

はじめに
こんにちは
アルバイトのMarikaです。
今回はHTTP通信データの圧縮アルゴリズムについて調査しました。
背景
脆弱性診断において大量のHTTP通信データが生成されます。
そのため診断ではHTTP通信データをいかに小さくし、アーカイブ等の運用面でのコストを削減するかという工夫が必要になります。
現状、gzipやbizp2などを使用し圧縮して保管していますが、最近ではwebコンテンツ向けの圧縮アルゴリズム、つまりHTTP通信を圧縮することに適したアルゴリズムが開発されています。
よりよい圧縮率を求めて圧縮アルゴリズムを調査しました。
調査の概要
まず現状のgzip圧縮の圧縮率を調査し、現状把握とします。
次にgzip以外の3つの圧縮アルゴリズムの候補と比較します。
圧縮アルゴリズムの候補はbzip2、brotli、zstandardです。
今回は圧縮率と圧縮にかかる時間について比較しました。
gzipとbzip2はともに汎用的に使うために開発された圧縮アルゴリズムですが、brotliとzstandardはwebコンテンツ向けに開発された圧縮アルゴリズムです。
調査の詳細
使用したデータセットは以下のようになっています。

今回のデータセットは1つの診断案件の中に6つのセッション(1つの診断対象リクエストについてクロール~スキャンまでを行う単位)があり、その中にクロールやスキャン時のHTTP通信が含まれています。
セッションごと通信内容の特徴は以下の通りです。
| 1bba3516c5c9bea615ed0cd430d2f37a/ | 小さめのJSONを返す単一通信をスキャン |
| 3632b9f842e0b905fd72979c481c7c64/ | HTMLを返す複数通信をスキャン |
| 5603397aa5f4f43641ae280d06b101bc/ | ランダムな英数字を返す単一通信をスキャン |
| 7c11aac069d56b7a309d85d181d5597c/ | 58KBほどのHTMLを返す単一通信をスキャン |
| ce2c2ea2a4a5dcca1cf0304ba53db3d7/ | HTMLを返す単一通信をスキャン |
| f435b2e81c23442f8519998421db66a3/ | JPEG画像を返す単一通信をスキャン |
現状はHTTPリクエスト1つにつき1ファイル、HTTPレスポンス1つにつき1ファイルずつgzipで圧縮しています。
今回の調査で使用した環境について説明します。
- OS:Windows 11
- Java:21.0.5
- 使用したJavaライブラリ:
- brotli4j 1.17.0
- bzip2 0.9.1
- zstd-jni 1.5.6-4
作成したコード:
圧縮
public static void show(final File srcDir, final Path destDirPath) throws IOException {
System.out.println("## " + srcDir + " -> " + destDirPath);
for (File f : srcDir.listFiles()) {
if (f.isDirectory()) {
final File nextSrcDir = f.getAbsoluteFile();
final Path nextDestDirPath = Paths.get(destDirPath.toString(), f.getName());
Files.createDirectory(nextDestDirPath);
show(nextSrcDir, nextDestDirPath);
} else {
if (f.toString().contains("_")) {
continue;
}
final Path zipFilePath = destDirPath.resolve(f.getName() + ".gz");
try (
InputStream is = Files.newInputStream(f.toPath());
GZIPOutputStream bos = new GZIPOutputStream(Files.newOutputStream(zipFilePath, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE))) {
is.transferTo(bos);
}
}
}
}
展開
public static void show(final File srcDir, final Path destDirPath) throws IOException {
System.out.println("## " + srcDir + " -> " + destDirPath);
for (File f : srcDir.listFiles()) {
if (f.isDirectory()) {
final File nextSrcDir = f.getAbsoluteFile();
final Path nextDestDirPath = Paths.get(destDirPath.toString(), f.getName());
Files.createDirectory(nextDestDirPath);
show(nextSrcDir, nextDestDirPath);
} else {
if (!f.toString().endsWith(".gz")) {
continue;
}
final Path ungzipFilePath = destDirPath.resolve(f.getName().replace(".gz", ""));
try (
GZIPInputStream gis = new GZIPInputStream(Files.newInputStream(f.toPath()));
OutputStream os = Files.newOutputStream(ungzipFilePath, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);) {
gis.transferTo(os);
}
}
}
}
例はgzipで圧縮、展開するものです。
その他の場合も同様ですが、ライブラリを変更し、それぞれのInputStream、OutputStreamを使用します。
関数内では対象のディレクトリ以下にあるものがディレクトリだった場合、対象を更新し、再帰的に調べるようになっています。
調査結果
ファイル単位での圧縮
| 小さめのJSONを返す単一通信をスキャン | HTMLを返す複数通信をスキャン | ランダムな英数字を返す単一通信をスキャン | 58KBほどのHTMLを返す単一通信をスキャン | HTMLを返す単一通信をスキャン | JPEG画像を返す単一通信をスキャン | |
|---|---|---|---|---|---|---|
| sessionId | 1bba3516c5c9bea615ed0cd430d2f37a | 3632b9f842e0b905fd72979c481c7c64 | 5603397aa5f4f43641ae280d06b101bc | 7c11aac069d56b7a309d85d181d5597c | ce2c2ea2a4a5dcca1cf0304ba53db3d7 | f435b2e81c23442f8519998421db66a3 |
| unpacked(a) | 4,556,362 | 10,708,363 | 2,694,324 | 92,831,189 | 2,672,359 | 23,861,782 |
| gz(b) | 3,392,310 | 6,355,647 | 1,999,613 | 16,430,489 | 1,781,604 | 20,301,764 |
| bzip2(c) | 4,433,422 | 7,925,849 | 2,186,239 | 12,847,486 | 2,440,012 | 20,505,975 |
| brotli(d) | 2,869,636 | 5,216,632 | 1,911,398 | 10,937,152 | 1,438,902 | 19,918,599 |
| zstandard(e) | 3,503,913 | 6,538,763 | 1,997,627 | 14,898,578 | 1,832,019 | 20,573,608 |
| 圧縮率(gz) | 74.45% | 59.35% | 74.22% | 17.70% | 66.67% | 85.08% |
| 圧縮率(bzip2) | 97.30% | 74.02% | 81.14% | 13.84% | 91.31% | 85.94% |
| 圧縮率(brotli) | 62.98% | 48.72% | 70.94% | 11.78% | 53.84% | 83.47% |
| 圧縮率(zstd) | 76.90% | 61.06% | 74.14% | 16.05% | 68.55% | 86.22% |
セッション単位での圧縮
| 小さめのJSONを返す単一通信をスキャン | HTMLを返す複数通信をスキャン | ランダムな英数字を返す単一通信をスキャン | 58KBほどのHTMLを返す単一通信をスキャン | HTMLを返す単一通信をスキャン | JPEG画像を返す単一通信をスキャン | |
|---|---|---|---|---|---|---|
| sessionid | 1bba3516c5c9bea615ed0cd430d2f37a | 3632b9f842e0b905fd72979c481c7c64 | 5603397aa5f4f43641ae280d06b101bc | 7c11aac069d56b7a309d85d181d5597c | ce2c2ea2a4a5dcca1cf0304ba53db3d7 | f435b2e81c23442f8519998421db66a3 |
| unpacked(a) | 4555079 | 2842402 | 2689306 | 92772478 | 2671025 | 23798937 |
| gz(b) | 167,301 | 1,016,581 | 1,602,729 | 16,326,552 | 110,716 | 20,186,295 |
| bz2(c) | 89,752 | 917,229 | 1,502,407 | 10,019,523 | 55,643 | 2,523,037 |
| br(d) | 94,321 | 713,226 | 1,430,503 | 7,930,658 | 57,316 | 77,500 |
| zstd(e) | 138,848 | 848,406 | 1,444,978 | 14,058,027 | 79,591 | 128,635 |
| 圧縮率(gz) | 3.67% | 35.76% | 59.60% | 17.60% | 4.15% | 84.82% |
| 圧縮率(bz2) | 1.97% | 32.27% | 55.87% | 10.80% | 2.08% | 10.60% |
| 圧縮率(br) | 2.07% | 25.09% | 53.19% | 8.55% | 2.15% | 0.33% |
| 圧縮率(zstd) | 3.05% | 29.85% | 53.73% | 15.15% | 2.98% | 0.54% |
圧縮する単位による違い
| 案件単位でtarでアーカイブした場合 | セッション単位でtarでアーカイブした場合 | ファイルごとに圧縮した場合 | |
|---|---|---|---|
| 展開されたファイル(a) | 137,324,379 | 137,324,379 | 137,324,379 |
| gz(b) | 39,428,300 | 39,410,174 | 45,534,450 |
| bz2(c) | 15,136,886 | 15,107,591 | 44,442,653 |
| br(d) | 10,304,951 | 10,303,524 | 38,407,974 |
| zstd(e) | 16,657,555 | 16,698,485 | 44,482,611 |
| 圧縮率(gz) | 28.71% | 28.70% | 33.16% |
| 圧縮率(bz2) | 11.02% | 11.00% | 32.36% |
| 圧縮率(br) | 7.50% | 7.50% | 27.97% |
| 圧縮率(zstd) | 12.13% | 12.16% | 32.39% |
所要時間
| gz | bz2 | br | zstd | |
|---|---|---|---|---|
| 各ファイルの圧縮 | 28,732 | 273,057 | 328,368 | 32,259 |
| 各ファイルの解凍 | 21,324 | 197,194 | 22,337 | 20,809 |
| セッションごとの圧縮 | 4,572 | 70,522 | 243,830 | 1,004 |
| セッションごとの解凍 | 1,910 | 59,788 | 804 | 819 |
| 案件単位の圧縮 | 3,609 | 57,174 | 200,820 | 918 |
| 案件単位の解凍 | 1,595 | 45,522 | 723 | 672 |
圧縮率はgzip > bzip2 >>> brotli, zstandard という印象でした。
圧縮にかかる時間はbrotli > bzip2 > gzip > zstandard、
解凍にかかる時間はbzip2 > gzip > zstandard > brotliとなりましたが、数回の計測なので作業環境等により変わる可能性があります。
まとめ
圧縮率はやはりwebコンテンツ向けの2つが目立つ結果となりました。
圧縮時間はbrotliが異常なほど時間がかかりましたが、tarでアーカイブして圧縮する回数を減らすことで時間短縮になりました。
圧縮率に差が出た要因として、同じ要素が多く含まれるものだとまとめて圧縮できるそうなのでファイル単位よりフォルダ単位で大きく圧縮するほうが圧縮率が高くなりました。
圧縮時間はbrotliが長かったため原因追跡までできればなおよかったと思いました。
調べた限りではセキュリティ対策ソフトが原因のことがあるようです。