テキストログレコードを圧縮するのに適した圧縮ツールを検証した

テキスト形式のログレコードが巨大な場合に、
Fluentdで転送する際のネットワーク帯域とか、S3に配置した際のサイズがでかいとかそういうのが気になりだしました。

どうにかレコードを小さく出来ないか考えたのですが
gzipなどの圧縮ツールをログ1行単位でやっていけばマシになるんじゃね?と思ったんですが、
速さと圧縮率を兼ねそなえたものがなんなのかわからなかったので、検証してみました。

ちなみにログ1行単位で圧縮したいのは、圧縮したログをFluetndを使って転送を行いたいからです。
(こういう方法あるよ!っていうのがあれば教えてください。。)

前提

  • OS
    • Ubuntu16.04
  • 測定結果はすべて「Base64」でエンコードした後のサイズである
    • 圧縮ツールを使った場合、当然出力結果はバイナリになりますが
      圧縮後も文字列として扱いたいため、「Base64」でエンコードして文字列にします。
      圧縮後のバイナリをBase64でエンコードすると、サイズが増えます。
      以下に出す結果は、その結果の数値ですのでご注意ください。
  • 圧縮レベルは「最低」を指定
    • これは圧縮の速度を重視しているためです。

選定ツール

名前 特徴
gzip 圧縮ツールのデファクトスタンダード
xz Linuxに標準で入っている圧縮ツール。圧縮率の点でgzipより優れていそう
lz4 圧縮率に期待ができそうなので選定
zstd lz4と同じ作者が制作した圧縮ツール
brotli Googleが作成した圧縮ツール

単純な圧縮率の比較

速さとか気にせず、普通に圧縮した時の圧縮率を見てみたいと思います。
圧縮対象のファイルは以下のようなサイズです。

ファイル名 Size(KB)
data.json 86.49

結果

Name Size(KB) 圧縮率
gzip 21.20 24.51%
xz 18.31 21.17%
lz4 26.22 30.32%
zstd 18.58 21.48%
brotli 22.37 24.56%

単純な圧縮率を見てみると、「xz」が一番よかったです。
lz4は速さを優先しているようです。

この圧縮率を測ったコマンドは以下になります。

$ cat data.json | gzip -1 - | base64 -w 0 | wc -c
$ cat data.json | xz -0 - | base64 -w 0 | wc -c
$ cat data.json | lz4 -1 - | base64 -w 0 | wc -c
$ cat data.json | zstd -1 - | base64 -w 0 | wc -c
$ cat data.json | brotli -0 - | base64 -w 0 | wc -c

速度と圧縮率の比較

続いて速度を見ていきましょう。
上記の「単純な圧縮率の比較」で実行したコマンドを「1万回」実行した結果です。

(1) … 処理にかかった秒数
(2) … 1秒あたりの処理行数
(3) … 1行あたりにかかった時間
(4) … 出力サイズ(MB)
(5) … 圧縮率

Name (1) (2) (3) (4) (5)
未圧縮 11 909 1.1ms 844.63 -
gzip 39 256 3.9ms 207.07 24.51%
xz 69 145 6.9ms 178.84 21.17%
lz4 33 303 3.3ms 256.12 30.32%
zstd 33 303 3.3ms 181.47 21.48%
brotli 43 233 4.3ms 218.55 25.87%

速さを見てみると「lz4」と「zstd」が同値になりました。
圧縮率は「xz」が1番良かったですが、「zstd」もかなり競っていることがわかります。

速さと圧縮率を鑑みると、「zstd」の性能がかなり優れていることがわかりました。

検証に使ったスクリプトは以下になります。

#!/bin/bash
# 第1引数に圧縮ツール名
# 第2引数に実行回数
 
function loop_compress() {
    echo $1
    level=2
    start_date=$(date '+%s')
    for i in $(seq 1 $2)
    do
        echo ${i}
        if [ "$1" == "none" ]; then
            cat data.json >> $1.log
        else
            cat data.json | $1 -${level} - | base64 -w 0 | xargs echo >> $1.log
        fi
    done
    end_date=$(date '+%s')
 
    echo -e "time:\t$(expr $end_date - $start_date)"
    echo -e "line:\t$(cat $1.log | wc -l)"
    echo -e "size:\t$(cat $1.log | wc -c)"
    rm -f $1.log
}
 
tool=$1
loop_cnt=$2
 
loop_compress ${tool} ${loop_cnt}

最後に

ログ1行ごとに圧縮する際は、「zstd」がよさそうだ というのがわかりました。
が、ログ1行単位で圧縮する場合、CPUのリソースをかなり食うことが確実なので
トレードオフなのかなと思います。

このzstdをログ1行ずつ実行する場合は、
rsyslogでコマンド実行すると導入しやすいと思います。

rsyslogでコマンド実行をしてログに手を加える
https://blog.haramishio.xyz/post/000008/

そもそもこういうアプローチの仕方はあってるんだろうか…
と不安になりますが、とりあえず検証してみました!

では!