読者です 読者をやめる 読者になる 読者になる

その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

thin-cacheというJava向けのキャッシュライブラリ書いた

java

thin-cacheというキャッシュライブラリ書いた.いつものようにmaven centralにも置いてある.そしていつものようにJava 8以降が対象となっている.

maven central

Microservicesみたいな感じでサービスを作っていると,他のサーバにrequestを飛ばして得られた値を加工して,あるいはそのまま利用するみたいなコードを書く事が多くなってくる.毎回毎回他サーバにrequestを飛ばして良いのであればまあ飛ばせば良いのだけれど,そうもいかない場合も多い (トラフィックなり応答速度なりの理由で).そういう場合に対策として考えうる手段のひとつとしてキャッシュが挙げられると思う.
今まではそういったキャッシュを良い感じでコントロールするコードを都度都度書いていたのだけれど,それにもそろそろ飽きてきたのでいい感じに抽出してライブラリにした.

Javaでキャッシュ回りを司るライブラリは色々あると認識していて,例えばguava のcacheなどが挙げられる.最初からguavaが入っているのであれば良いんだけどそうではない場合にキャッシュの用途のみでguavaを入れるのもオーバーキルな感じを抱いており,シンプルなキャッシュ機能だけを提供するライブラリが欲しくなったのでthin-cacheを書いた次第.
シン・ゴジラが流行っているからこういう名前にしたわけではなく,薄くてシンプルなライブラリということでこういう名前にした.

機能

シンプルな使い方としては以下の通り.

final AutoRefreshCache<Long> autoRefreshCache = new AutoRefreshCache<>(10, false, new Supplier<Long>() {
    private long i = 0;

    @Override
    public Long get() {
        return ++i;
    }
});

autoRefreshCache.get(); // => 1L (initialize cache)
autoRefreshCache.get(); // => 1L (hit cache)

// 10 seconds spent...

autoRefreshCache.get(); // => 2L (expired, refresh cache)
autoRefreshCache.get(); // => 2L (hit cache)

第三引数として渡したSupplier<T>の実装により得られた値を,第一引数で指定した秒数分だけ保持するという,見ての通りの感じ (第二引数については後述).
キャッシュが効いている間にAutoRefreshCache#get()が呼ばれるとキャッシュ内容がそのまま返却され,キャッシュが期限切れになってから (つまり保持期限が過ぎてから) AutoRefreshCache#get()が呼ばれると supplier が実行されてその値を結果として返すと同時に,キャッシュに supplier の結果を格納しつつ保持期限の更新をしている.シンプル.

キャッシュを操作するメソッドとしては以下の4種類がある.

  • get()
  • forceGet()
  • getWithRefreshAheadAsync()
  • forceGetWithRefreshAheadAsync()
get()

Read throughにキャッシュを取得してくるメソッド.
キャッシュが存在しない (初期化されていないとか,expireしたとか) 時に,キャッシュコンテンツを生成しそれをキャッシュすると同時に返却する.

forceGet()

Write throughにキャッシュを取得してくるメソッド.
キャッシュが存在しているか否かに関わらず,常にキャッシュコンテンツを生成しつつそれをキャッシュすると同時に返却する.

getWithRefreshAheadAsync()

Refresh aheadにキャッシュを取得してくるメソッド.
キャッシュが存在しない場合,即座に古い (一世代前の) キャッシュコンテンツを取得・返却しつつ,そのバックグラウンドでキャッシュコンテンツの生成及びストアを非同期に行なう.戻り値としてはキャッシュオブジェクトとFutureのタプルを返しており,キャッシュコンテンツの生成とストアが完了するとそのFutureは完了状態になる.

なおキャッシュが初期化されていない場合はget()と同じ動き,つまり同期的に動作する.

forceGetWithRefreshAheadAsync()

Refresh aheadにキャッシュを取得してくるメソッド.
キャッシュが存在していようがいまいが,即座に古い (一世代前の) キャッシュコンテンツを取得・返却しつつ,そのバックグラウンドでキャッシュコンテンツの生成及びストアを非同期に行なう.以下略.

なおキャッシュが初期化されていない場合はforceGet()と同じ動き,つまり同期的に動作する.

工夫した点

工夫した点 (というか欲しかった機能) として,キャッシュ生成時に例外が発生した場合の動作を指定出来るようにしている.「例外が発生した時にそのまま例外を上流に放る」か,「例外内容をエラーログに出しながら (slf4jを使っている) キャッシュ済みの古いオブジェクトを返す」かを制御できるようになっている.例えば,他のサーバから値を取ってこようとする際に相手が500を返してくるケースとかは事実あると思っていて,そういう状況でこちらまで巻き込まれていては良くない.そういう場合でもサービスを継続できるようにすべく,このような状況下での動作をコントロールしたかったのでこういう機能を付けた.

所感

こういう小粒なライブラリでも,非同期性とかスレッドセーフとかを意識しつつコードを書くと少し複雑になることが分かった.毎度毎度こういうコードを書いていては身体や精神が持たないので,ライブラリにすることで再利用できるようにした.最近こういう小さなライブラリばかり書いている気がするのだけれど,どんどん小さなライブラリを作りつつ組み合わせて作っていくぞという精神世界になっている.

ところで,read through とか write through とか refresh ahead とか,そういう概念は学生時代に勉強したりパタヘネ本で勉強したりしていたはずなのにすっかり忘れていて厳しい気持ちになった.一方でこういうライブラリを実装しつつドキュメントを書くことで知識を定着させることが出来て良かったと思う.実感を伴わせることで知識が身につくという事を久々に実感できて体験があった.