👾

Go言語で爆速キャッシュ!bigcacheの活用入門

に公開

bigcache とは何か

bigcache は、Go 言語向けのインメモリ型キャッシュライブラリです。特徴的なのは、Garbage Collection(GC)によるパフォーマンス低下を最小限に抑える設計がなされている点です。Go の GC は非常に優れた仕組みですが、扱うデータ量が増えるほどオブジェクトの生成・破棄が頻繁に起こり、最終的に GC 処理のコストが大きくなる可能性があります。bigcache は、そのコストを低減するための内部構造を持ち合わせています。

具体的には、 固定サイズのバケット(シャード) をあらかじめ複数用意し、そこにキーとバリューをハッシュマップのように格納する仕組みを採用しています。また、バリューをバイトスライス形式で保存することで、GC が追跡すべきポインタの数を減らします。これにより、キャッシュが大規模になっても GC によるパフォーマンスの劣化が起こりづらく、高いスループット を実現できます。

さらに、マルチスレッド/マルチゴルーチン環境における性能向上のためにシャーディングを活用しています。これにより、一か所だけでなく複数のバケットにデータを分散して格納できるため、同時アクセスによるロック競合のリスクを軽減できます。アクセスパターンによってはロックの待ち時間がほとんど発生せず、高スループットを保ちながら同時アクセスをさばくことが可能です。

設定面では、LifeWindow(存命期間)CleanWindow(クリーンアップ間隔) といったパラメータをカスタマイズし、キャッシュされたデータをどのタイミングで削除するかなどを細かく制御できます。例えば、 LifeWindow を 10 分に設定すれば、格納されたデータは最長 10 分間保持され、その後は自動的に削除される仕組みです。

一方、メモリにすべてのデータを格納するため、ホストのメモリ容量には十分に気を配る必要があります。データサイズが大きくなりすぎるとシャードの数やエントリサイズを適宜調整しなければいけません。とはいえ、外部サービス(Redis や Memcached など)に依存せず、Go アプリケーション内で高速にキャッシュを完結できる点は大きな利点です。単一プロセス内で処理を済ませたい、もしくは外部への通信遅延を避けたいユースケースにおいて、有効な選択肢となります。

総じて bigcache は、GC 負荷の軽減とロック競合の緩和を重視した設計により、高スループットなキャッシュ処理を必要とする Go アプリケーションで多く採用されています。たとえば、頻繁にアクセスされるデータをローカルメモリにキャッシュして、外部ストレージや API へのアクセスを減らすことで、サービス全体の応答速度向上に繋げる使い方が一般的です。

最小限のサンプルコード

package main

import (
	"context"
	"fmt"

	"github.com/allegro/bigcache/v3"
)

func main() {
	// 1. bigcache 設定の作成
	config := bigcache.Config{
		Shards: 1,
	}

	// 2. bigcache インスタンスの生成
	cache, err := bigcache.New(context.Background(), config)
	if err != nil {
		fmt.Println("Failed to create bigcache: ", err)
		return
	}

	// 3. キャッシュにデータを保存(Set)
	err = cache.Set("myKey", []byte("Hello Bigcache!"))
	if err != nil {
		fmt.Println("Failed to set cache: ", err)
		return
	}

	// 4. キャッシュからデータを取得(Get)
	entry, err := cache.Get("myKey")
	if err != nil {
		fmt.Println("Failed to get cache: ", err)
		return
	}

	fmt.Println("Got Value: ", string(entry))
}

上記のコードは大まかに以下の流れで動作します。

  1. Config 構造体にシャード数などを設定
  2. bigcache.New(context.Background(), config) でキャッシュインスタンスを生成
  3. Set でデータを格納し、Get でキーに対するバイト列を取得
  4. 取得した値を文字列に変換して出力

Shards を 1 に設定すると動作自体は可能ですが、実運用でパフォーマンスを期待するには シャード数を増やす(例:64, 256, 1024 など)ほうが望ましいです。

基本設定パラメータの解説

bigcache.Config は複数のフィールドから構成されますが、その中でも主要なものを紹介します。

パラメータ名 概要
Shards int 内部構造を分割する数。2 の累乗 で指定する必要がある(例:64, 256, 1024 など)。少ないとロック競合が増えてパフォーマンス低下を招く。
LifeWindow time.Duration キャッシュエントリの有効期限。指定した時間を過ぎると、クリーンアップ時に削除対象になる。
CleanWindow time.Duration 期限切れデータを削除する間隔。定期的に古いエントリを削除するために利用。
MaxEntrySize int 1エントリあたりの最大サイズ(バイト)。値が大きいほどメモリを多く消費する。
Verbose bool ログを詳細表示するかどうか。

Shards に関する注意点

  • Shards number must be power of two
    • bigcache は内部でシャーディング(分割)してロック競合を低減する仕組みを使っているため、シャード数は 2 の累乗(1, 2, 4, 8, 16, ... など)でなければいけません。
    • もし Shards: 3 のように 2 の累乗ではない値を指定すると、Failed to create bigcache: Shards number must be power of two というエラーが出て初期化に失敗します。
    • なお、1 は数学的には 2^0 なのでエラーにはなりませんが、実運用時に 1 シャードしかないとロック競合が頻発し、パフォーマンス低下につながる可能性が高いです。

上記の修正後コードとパラメータの理解を組み合わせて使うことで、バージョン v3 系の bigcache を正しく動作させながらアプリケーションに組み込めるようになります。シャード数やパラメータを適切に設定することで、高いパフォーマンス を期待できるローカルインメモリキャッシュを構築できるでしょう。

キャッシュ操作の基本(Set / Get / Delete)

bigcache はキーと値の組み合わせによるキャッシュ格納・取得・削除が可能です。値は []byte 型で格納されるため、文字列や数値などのデータは適宜バイト列に変換して扱います。

1. Set(データの書き込み)

err := cache.Set("myKey", []byte("user_data"))
if err != nil {
    fmt.Println("Failed to set data:", err)
}
  • 処理内容: 指定したキー("session_12345")に対応する値("user_data" のバイト列)をキャッシュに保存します。
  • エラー発生要因:
    • MaxEntrySize を超えるサイズのデータを保存しようとした場合
    • 内部的なメモリアロケーションに問題が起こった場合
  • 成功時: errnil、指定キーで後から Get できるようになります。

2. Get(データの読み取り)

data, err := cache.Get("myKey")
if err != nil {
    if err == bigcache.ErrEntryNotFound {
        fmt.Println("Key not found in cache")
    } else {
        fmt.Println("Failed to get data:", err)
    }
    return
}

fmt.Println("Got Value:", string(data))
  • 処理内容: 指定したキーに対応する値を取得します。
  • 取得結果:
    • 値が存在する場合、[]byte が返り、文字列などにキャストして利用可能です。
    • 値が存在しない、あるいは有効期限切れ等で削除済みの場合、ErrEntryNotFound が返ってきます。
  • 用途: キャッシュヒットを利用して高速なレスポンスを行い、外部APIやデータベースアクセスを減らすことが可能。

3. Delete(データの削除)

err := cache.Delete("myKey")
if err != nil {
    if err == bigcache.ErrEntryNotFound {
        fmt.Println("Key does not exist, cannot delete")
    } else {
        fmt.Println("Failed to delete key:", err)
    }
}
  • 処理内容: 指定キーに対応するキャッシュエントリを即時削除します。
  • 利用シナリオ:
    • セッション終了時にユーザーデータをクリア
    • 有効期限を待たずに不要データを積極的に取り除きたい場合
  • エラー要因: キーが存在しない場合は ErrEntryNotFound が返ってきますが、これは想定内の挙動であり致命的エラーではありません。

まとめ

本記事では、Go 言語向けのインメモリ型キャッシュライブラリ「bigcache」の特徴、基本的な使い方を紹介しました。

  • bigcacheの特徴:
    GC 負荷を軽減し、ロック競合を最小化する設計によって、高スループットなキャッシュ処理を実現します。外部サービスを用いず、Go アプリケーション内で高速なキャッシュを完結できる点が大きな強みです。

  • 基本的な操作:
    Set / Get / Delete といった単純なAPIで直感的にキャッシュを扱えます。コードサンプルを通じて、実際にデータを格納・取得する流れや、データ削除の手順を確認しました。

総じて、bigcache はシンプルな実装ながら柔軟なカスタマイズが可能であり、Go を用いた高速なサービス開発・運用にとって有力な選択肢となり得ます。この記事を参考にして、実際のアプリケーションに合わせたパラメータチューニングや活用方法を模索してみてください。

Discussion