💹

データ圧縮でElastiCacheの料金を節約する

2024/09/30に公開

はじめに

Redisはインメモリデータベースであり、データをメモリ(RAM)上で管理します。HDD(ハードディスク)やSSD(ソリッドステートドライブ)と比較して、データの読み取りや書き込みの待ち時間が大幅に短縮されます。

しかし、同じ容量で比較した場合、RAMのコストはHDDやSSDよりもはるかに高価です。本記事では、大量のキャッシュデータを保存しながらも、ElastiCacheの料金を抑えることができる方法をご紹介します。

データ圧縮とは

Wikipediaによると、データ圧縮とは「あるデータを、その実質的な内容(情報、あるいはその情報量)を可能な限り保ったまま、データ量を減らした別のデータに変換すること」と定義されています。データ圧縮には主に次の2つの種類があります:

  1. 可逆圧縮(ロスレス圧縮)
    データを圧縮後、元のデータが完全に復元できる圧縮方法です。
    例:ZIPファイル、PNG画像、FLAC音声

  2. 非可逆圧縮(ロッシー圧縮)
    圧縮後にデータの一部が失われるため、元のデータを完全には復元できませんが、その分サイズを大幅に削減できます。画像、音声、動画などでよく使われている圧縮方法です。
    例:JPEG画像、MP3音声、MP4動画

データを圧縮することで、データサイズが小さくなり、同じ容量でもより多くのデータを保存できるようになります。さらに、ネットワーク上でのデータ転送の効率も向上させることが可能になります。

Goでデータ圧縮を実装する

圧縮アルゴリズムの選択

データ圧縮アルゴリズムには様々な種類がありますが、適切なアルゴリズムの選択は目的によって異なります。一般的に、処理速度が速いほど圧縮率は低くなり、逆に圧縮率が高いほど処理時間が長くなる傾向があります。

今回ご紹介するのは、Googleが開発したSnappy圧縮アルゴリズムです。このアルゴリズムはgzipと比較すると圧縮率は劣りますが、処理速度が数倍も速いという特徴があります。つまり、Snappyは速度と圧縮率のバランスが取れたアルゴリズムと言えます。

GoでSnappy圧縮アルゴリズムを使用する場合は、golang/snappyライブラリを利用できます。

インストール方法:

go get github.com/golang/snappy

使用方法は以下の通りです:

func main() {
    src := []byte("Hello, 世界")

    // 圧縮
    encoded := snappy.Encode(nil, src)

    // 解凍
    decoded, err := snappy.Decode(nil, encoded)
    if err != nil {
    	log.Fatal(err)
    }

    fmt.Println(string(decoded))
}

データ圧縮による容量の節約効果は、元のデータの性質に大きく依存します。例として、以下のダミーテキストをSnappy圧縮アルゴリズムで処理したところ、元のサイズから6.5%のバイト数を削減することができました。

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    str := []byte(`Lorem ipsum dolor sit amet...`)
    fmt.Printf("圧縮前: %d\n", len(str)) // 445

    encoded := snappy.Encode(nil, str)
    fmt.Printf("圧縮後: %d\n", len(encoded)) // 416

シリアライズフォーマット

バイトデータの場合はすぐに圧縮できますが配列や構造体(struct)の場合シリアル化する必要があります。一般的に使われているシリアル化する方法はJSONシリアライズです。他のシリアライズフォーマットはGobMessagePackです。

それぞれシリアル化のライブラリは以下になります。

  • JSON: encoding/json Go標準ライブラリ)
  • Gob: encoding/gob(Go標準ライブラリ)
  • MessagePack: github.com/vmihailenco/msgpack/v5

この3つのライブラリをベンチマークしてみました。

BenchmarkJSONMarshal-12               	   45304	     25614 ns/op	   17984 B/op	      35 allocs/op
BenchmarkJSONUnmarshal-12             	   14763	     81072 ns/op	   31334 B/op	      74 allocs/op
BenchmarkGobMarshal-12                	  120536	     10011 ns/op	   29876 B/op	      51 allocs/op
BenchmarkGobUnmarshal-12              	   56109	     21446 ns/op	   36464 B/op	     243 allocs/op
BenchmarkMsgpackMarshal-12            	  373802	      2888 ns/op	   28005 B/op	       5 allocs/op
BenchmarkMsgpackUnmarshal-12          	  369746	      3261 ns/op	   15286 B/op	      34 allocs/op

ベンチマークの結果に基づいて、以下のような考察をまとめました:

  • MessagePackは処理速度が最も速く、かつメモリ使用率も最も低い。
  • JSONの処理速度は3つの中で最も遅い。
  • GobのUnmarshal処理のメモリ使用率はJSONと比べて若干高いものの、処理速度はJSONの約4倍速い。

また、各シリアル化フォーマットのバイトサイズも比較しました:

フォーマット バイトサイズ
JSON 14,602
Gob 13,441
MessagePack 13,304

MessagePack + Snappy の組み合わせ

MessagePackとSnappy圧縮アルゴリズムの組み合わせについても検証を行いました。 結果は以下の通りです:

BenchmarkMsgpackSnappyMarshal-12      	   55236	     21330 ns/op	   44386 B/op	       5 allocs/op
BenchmarkMsgpackSnappyUnmarshal-12    	  140824	      8507 ns/op	   28951 B/op	      35 allocs/op
  • パフォーマンス面では、GobのMarshall処理と比較して若干劣るものの、JSONよりは依然として優れています。
  • バイトサイズに関しては、7,789バイトまで削減されました。これはJSONやGobの約半分のサイズです。

注意点

上記の結果を見ると、「すべてのデータを圧縮すればよいのではないか」と考えてしまうかもしれません。しかし、データ圧縮にはデメリットも存在します。

  1. CPU使用率の増加
    データの圧縮および解凍にはCPUリソースを使用します。データのサイズが大きくなるほど、CPU使用率も上昇します。

  2. 圧縮効果の限界
    元のデータサイズがすでに小さい場合、圧縮後にかえってファイルサイズが大きくなる可能性があります。これは、データを圧縮する際に解凍に必要な情報などが追加されるためです。追加された情報のサイズが圧縮による削減分を上回ると、結果的にファイルサイズが増加してしまいます。同様の理由から、一度圧縮されたデータを繰り返し圧縮することは逆効果となります。

まとめ

キャッシュデータを圧縮することで、ElastiCacheのメモリ使用量を削減することができます。これにより、より小さいスペックのインスタンスへのダウングレードが可能となり、結果としてコスト削減につながります。 ただし、データ圧縮にはデメリットもあることを忘れないでください。

実際の導入を検討する際は、以下の手順を推奨します:

  1. 対象のデータセットに対して十分なベンチマークを実施する
  2. 圧縮前後のパフォーマンスと使用リソースを比較分析する
  3. 分析結果に基づいて、データ圧縮の必要性と効果を慎重に判断する

これらのステップを踏むことで、最適な選択が可能となります。

Discussion