FreeCache

キャッシュライブラリGoGolangインメモリキャッシュ高性能ゼロGC

GitHub概要

coocood/freecache

A cache library for Go with zero GC overhead.

スター5,309
ウォッチ112
フォーク401
作成日:2015年4月29日
言語:Go
ライセンス:MIT License

トピックス

なし

スター履歴

coocood/freecache Star History
データ取得日時: 2025/10/22 08:07

キャッシュライブラリ

FreeCache

概要

FreeCacheは、GoのメモリキャッシュライブラリでGCオーバーヘッドをゼロにする設計が特徴です。長時間保持されるオブジェクトによって発生する高価なGCオーバーヘッドを解決し、遅延の増加やスループットの低下なしに無制限のオブジェクトをメモリにキャッシュできます。

詳細

FreeCacheは、ポインタ数を減らすことでGCオーバーヘッドを回避します。エントリ数に関係なく、わずか512個のポインタしか存在しません。データはハッシュ値によって256のセグメントに分割され、各セグメントはリングバッファ(キー/値を格納)とインデックススライス(検索用)の2つのポインタのみを持ちます。

各セグメントは独自のロックを持ち、高い並行アクセスをサポートします。マルチスレッド環境では、FreeCacheは単一ロックで保護されたビルトインマップよりも何倍も高速になります。パフォーマンス面では、Set操作がビルトインマップの約2倍、Get操作が約1/2倍の速度を実現しています。

メリット・デメリット

メリット

  • ゼロGCオーバーヘッド設計により高いパフォーマンス
  • 高並行性サポート(セグメント別ロック機構)
  • Set操作がビルトインマップの約2倍高速
  • 500万エントリまでの大規模キャッシュに適している
  • 有効期限サポート(TTL機能内蔵)
  • ランタイムでのキャッシュサイズ変更対応
  • ファイルへのダンプ/ロード機能
  • 遅延に敏感なGoアプリケーションに最適

デメリット

  • Get操作がビルトインマップの約1/2倍の速度
  • メモリは事前割り当てされ、大容量割り当て時にはdebug.SetGCPercent()の調整が必要
  • 最小キャッシュサイズが512KB
  • キーとしてbyte sliceを使用するため、文字列キーより不便
  • エントリの明示的削除や更新機能がない

参考ページ

書き方の例

基本的なセットアップ

package main

import (
    "fmt"
    "runtime/debug"
    "github.com/coocood/freecache"
)

func main() {
    // 100MBのキャッシュを作成
    cacheSize := 100 * 1024 * 1024
    cache := freecache.NewCache(cacheSize)
    
    // 大容量メモリ使用時のGC頻度調整
    debug.SetGCPercent(20)
}

基本的なSet/Get操作

// キーと値を設定
key := []byte("user:123")
value := []byte(`{"name":"John","age":30}`)
expire := 60 // 60秒で有効期限切れ

err := cache.Set(key, value, expire)
if err != nil {
    fmt.Printf("Set error: %v\n", err)
}

// 値を取得
result, err := cache.Get(key)
if err != nil {
    fmt.Printf("Get error: %v\n", err)
} else {
    fmt.Printf("Retrieved: %s\n", string(result))
}

TTL(有効期限)の操作

// 異なる有効期限でのエントリ設定
cache.Set([]byte("short-lived"), []byte("data1"), 10)  // 10秒
cache.Set([]byte("long-lived"), []byte("data2"), 3600) // 1時間
cache.Set([]byte("permanent"), []byte("data3"), 0)     // 永続(GCされるまで)

// TTLの取得
ttl, err := cache.TTL([]byte("short-lived"))
if err == nil {
    fmt.Printf("Remaining TTL: %d seconds\n", ttl)
}

// エントリの削除
affected := cache.Del([]byte("short-lived"))
fmt.Printf("Deleted entries: %d\n", affected)

キャッシュ統計の確認

// エントリ数の確認
entryCount := cache.EntryCount()
fmt.Printf("Current entries: %d\n", entryCount)

// 平均エントリサイズ
if entryCount > 0 {
    avgSize := cache.AverageEntrySize()
    fmt.Printf("Average entry size: %.2f bytes\n", avgSize)
}

// ヒット率の計算
hitCount := cache.HitCount()
missCount := cache.MissCount()
totalRequests := hitCount + missCount
if totalRequests > 0 {
    hitRate := float64(hitCount) / float64(totalRequests) * 100
    fmt.Printf("Hit rate: %.2f%%\n", hitRate)
}

高度な操作

// 特定の値があるかチェック(データを取得せずに)
exists := cache.Get([]byte("key")) != nil

// キャッシュの完全クリア
cache.Clear()

// エントリの更新(既存の場合のみ)
key := []byte("update-key")
if cache.Get(key) != nil {
    cache.Set(key, []byte("new-value"), 300)
}

// バッチ操作の例
keys := []string{"batch1", "batch2", "batch3"}
for i, k := range keys {
    key := []byte(k)
    value := []byte(fmt.Sprintf("value-%d", i))
    cache.Set(key, value, 600)
}

エラーハンドリング

key := []byte("test-key")
value := []byte("test-value")

// Set操作のエラーハンドリング
err := cache.Set(key, value, 60)
switch err {
case nil:
    fmt.Println("Set successful")
case freecache.ErrLargeKey:
    fmt.Println("Key too large")
case freecache.ErrLargeEntry:
    fmt.Println("Entry too large")
default:
    fmt.Printf("Unexpected error: %v\n", err)
}

// Get操作のエラーハンドリング
result, err := cache.Get(key)
switch err {
case nil:
    fmt.Printf("Found: %s\n", string(result))
case freecache.ErrNotFound:
    fmt.Println("Key not found")
default:
    fmt.Printf("Get error: %v\n", err)
}

設定の最適化

// 異なるサイズでのキャッシュ作成
smallCache := freecache.NewCache(1024 * 1024)      // 1MB
mediumCache := freecache.NewCache(50 * 1024 * 1024) // 50MB
largeCache := freecache.NewCache(500 * 1024 * 1024) // 500MB

// メモリ使用量に応じたGC調整
if cacheSize >= 100*1024*1024 { // 100MB以上
    debug.SetGCPercent(10) // より低い頻度
} else {
    debug.SetGCPercent(20) // 標準的な頻度
}