Caffeineでキャッシュのエントリのキャッシュ有効時間に揺らぎを入れる方法

こんにちは。今日はCaffeineでキャッシュのエントリに対して キャッシュ有効時間に対してランダムな揺らぎを導入する方法をここに書いておきます。

キャッシュに対してランダムな揺らぎを入れる必要性に関してはこちらの記事を読むと一部書いてあると思います。

また、AWS Architecture Blogにある「Exponential Backoff and Jitter」を読むと良いかもしれません。

こちらはこちらで面白いですが、キャッシュの話は関係ないです。 アクセス負荷を平滑する手法について、シミュレーションとその結果から生成されるグラフについて紹介してくれています。

この記事ではキャッシュそのものについて説明しません。

Caffeineとは

CaffeineとはGuavaにあるCacheライクなAPIを持つインメモリキャッシュのためのライブラリです。 高速らしいです。

Caffeine自体の使い方については以下のブログを参照してください

実装方針

Caffeineにはいつからか、Expiryというインターフェースが用意されており以下のようなインターフェースになっています。

public interface Expiry<K, V> {
  // currentDurationは現在時点から見てexpireするまでの時間です。
  // 全ての単位は nanosecondsです。

 // エントリが作成された時点からどのくらいでexpireするかを戻り値で返す
  long expireAfterCreate(@NonNull K key, @NonNull V value, long currentTime);
 // エントリが更新された時点からどのくらいでexpireするかを戻り値で返す
  long expireAfterUpdate(@NonNull K key, @NonNull V value,
      long currentTime, @NonNegative long currentDuration);
 // エントリが読み込みされた時点からどのくらいでexpireするかを戻り値で返す
  long expireAfterRead(@NonNull K key, @NonNull V value,
      long currentTime, @NonNegative long currentDuration);
}

また、以下のように、CacheのビルダーにexpireAfterにExpiryを渡すことが出来るようになっています。

Caffeine.newBuilder()
   .expireAfter(new MyExpiry<>())
   .build();

実装してみます

今回の10分をベースに25%の範囲(7.5分〜12.5分)で揺らぎのあるExpiryを書いてみます。

以下のような形になりました。

/**
 * 7.5分〜12.5分の間で揺らぎのあるExpiry
 */
public class MyExpiry<K, V> implements Expiry<K, V> {

    public final Random = new Random();
    public final long base = TimeUnit.NANOSECONDS.convert(10, TimeUnit.MINUTES);
    public final double jitterFactor = 0.25;
    
    
    @Override
    public long expireAfterCreate(K key, V value, long currentTime) {
        return return (long) (base + jitterFactor * base * (2 * rand.nextDouble() - 1));;
    }
    
    @Override
    public long expireAfterUpdate(K key, V value, long currentTime,
            long currentDuration) {
        return currentDuration;
    }
    
    @Override
    public long expireAfterRead(K key, V value, long currentTime,
            long currentDuration) {
        return currentDuration;
    }

expireAfterCreateを実装して他はcurrentDurationを返すだけです。

今回はLRUではなく固定サイズ、固定期間キャッシュするようなイメージの物を実装したので expireAfterReadはcurrentDurationを返すようにしています また、今回自分の用途ではupdateをしないので、expireAfterUpdateもcurrentDurationを返すようにしています。

利用する際は、必要に応じてこれらのメソッドを適切に実装してください。

まとめ

今回は簡単にCaffeineでキャッシュの有効期間に対してランダム性をもたせることで 負荷を平滑化しようとしてこの記事で書いたような実装を書きました。

メモ書きに近いですが、誰かの訳に立てると幸いです。

負荷試験はこれからです。