ScalaCache

キャッシュライブラリScalaメモ化非同期キャッシュRedisMemcached

キャッシュライブラリ

ScalaCache

概要

ScalaCacheは、最も人気のあるキャッシュ実装のファサードとなる、シンプルで慣用的なScala APIを提供するキャッシュライブラリです。

詳細

ScalaCacheは、様々なキャッシュライブラリに対する統一されたインターフェースを提供し、最小限の手間でScalaアプリケーションにキャッシュ機能を追加できます。Redis、Memcached、Guava Cache、Caffeine、EhCacheなど、幅広いキャッシュバックエンドをサポートしています。メソッド結果のメモ化機能により、同じ引数で呼び出されたメソッドのキャッシュ結果を自動的に使用できます。Future ベースの非同期APIもサポートし、キャッシュ操作を別スレッドで実行できます。型安全でScala らしい API設計により、開発者体験が向上しています。

メリット・デメリット

メリット

  • 統一API: 異なるキャッシュライブラリを統一インターフェースで操作可能
  • 豊富なバックエンド: Redis、Memcached、Guava、Caffeine、EhCacheをサポート
  • メモ化機能: メソッド結果の自動キャッシュによる簡単な実装
  • 非同期サポート: Futureベースの非同期キャッシュ操作
  • Scala慣用的: 関数型プログラミングスタイルに適合
  • TTL対応: キャッシュエントリの有効期限設定が可能

デメリット

  • Scala専用: Java では使用できない
  • 学習コスト: Scala の知識が必要
  • バックエンド依存: 選択したキャッシュライブラリの制約を受ける
  • オーバーヘッド: ファサードレイヤーによる軽微なパフォーマンス影響

主要リンク

書き方の例

基本的な使用方法

import scalacache._
import scalacache.caffeine._
import scalacache.memoization._

// キャッシュの初期化
implicit val cache: Cache[String] = CaffeineCache[String]

// 基本的なキャッシュ操作
val result: Option[String] = sync.get("key")
sync.put("key")("value")
sync.remove("key")

// TTL付きでの保存
import scala.concurrent.duration._
sync.put("key", ttl = Some(10.minutes))("value")

メモ化機能

import scalacache.memoization._

class UserService {
  implicit val cache: Cache[User] = CaffeineCache[User]
  
  // メソッド結果を自動的にキャッシュ
  def getUser(id: Int): User = memoize(None) {
    // データベースアクセスなどの重い処理
    fetchUserFromDatabase(id)
  }
  
  // TTL付きメモ化
  def getUserWithTTL(id: Int): User = memoize(Some(1.hour)) {
    fetchUserFromDatabase(id)
  }
}

非同期キャッシュ操作

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

// Futureベースの非同期操作
val futureResult: Future[Option[String]] = async.get("key")

async.put("key")("value").foreach { _ =>
  println("キャッシュに保存完了")
}

// 非同期メモ化
def fetchDataAsync(id: Int): Future[Data] = memoizeF(Some(30.minutes)) {
  // 非同期データ取得
  httpClient.fetchData(id)
}

Redis バックエンドの使用

import scalacache._
import scalacache.redis._
import redis.clients.jedis._

// Redis接続設定
val jedisPool = new JedisPool("localhost", 6379)
implicit val cache: Cache[String] = RedisCache[String](jedisPool)

// Redis特有の操作
val serializer = scalacache.serialization.binary._

class ProductService {
  def getProduct(id: String): Product = memoize(Some(1.day)) {
    productRepository.findById(id)
  }
}

Memcached バックエンドの使用

import scalacache._
import scalacache.memcached._
import net.spy.memcached._

// Memcached接続設定
val memcachedClient = new MemcachedClient(
  AddrUtil.getAddresses("localhost:11211")
)
implicit val cache: Cache[String] = MemcachedCache[String](memcachedClient)

// キャッシュ操作の例
def expensiveOperation(param: String): String = memoize(Some(2.hours)) {
  // 重い処理
  performComplexCalculation(param)
}

キャッシュの設定とカスタマイズ

import scalacache.caffeine._
import com.github.benmanes.caffeine.cache.Caffeine
import java.util.concurrent.TimeUnit

// Caffeineの詳細設定
val underlyingCache = Caffeine.newBuilder()
  .maximumSize(10000)
  .expireAfterWrite(1, TimeUnit.HOURS)
  .recordStats()
  .build[String, Entry[String]]()

implicit val cache: Cache[String] = CaffeineCache[String](underlyingCache)

// 統計情報の取得
val stats = underlyingCache.stats()
println(s"ヒット率: ${stats.hitRate()}")

エラーハンドリング

import scala.util.{Try, Success, Failure}

class CacheService {
  implicit val cache: Cache[String] = CaffeineCache[String]
  
  def safeGet(key: String): Try[Option[String]] = {
    Try(sync.get(key)) match {
      case Success(value) => Success(value)
      case Failure(ex) =>
        logger.error(s"キャッシュアクセスエラー: $key", ex)
        Failure(ex)
    }
  }
  
  def safeMemoize[T](key: String, ttl: Option[Duration] = None)
                   (f: => T): T = {
    try {
      memoize(ttl)(f)
    } catch {
      case ex: Exception =>
        logger.warn(s"メモ化エラー、直接実行: $key", ex)
        f
    }
  }
}