OSCache#getElementCountInMemory が -1 を返す理由を考えてみた

com.opensymphony.oscache.hibernate.OSCache#getElementCountInMemory が -1 を返す理由と、自分が実装するとしたらどうするかを考えてみました。

第一印象

おいらが最初に OSCache を使った時には、getElementCountInMemory が -1 を返すなんてダメダメだと思いました。EhCache はきちんと値を返していたので好感が持てました。

java.util.Map#size() の戻り値は int 型

しかし、ソースを追って気づいたのですが、getElementCountInMemory の戻り値は long であり、java.lang.Map#size() の戻り値は int なんですね。ということは、内部で使用している Map 実装の Map#size() メソッドの値を直接返すことはできません。

EhCache の getElementCountInMemory() 実装

では、EhCacheはどうやって実装しているのだろうと疑問に思い、ソース見てみました。Map#size() をそのまま使って値を返していました orz

理論上は

実装上 long でエントリ数を返すことが難しいことと、hiberante の Cache#getElementCountInMemory の仕様としては、サポートしないなら -1 を返せばよいことになているので、OSCache の実装は全く問題なしです。逆にEhCacheのほうは、Integer.MAX_VALUE を超えるとバグということになりますね。

実際問題

Integer.MAX_VALUE は 2,147,483,647 です。ここで、Integer.MAX_VALUE の数だけエントリ数を持たせることを考えてみます。

32bitOS環境でのオンメモリキャッシュを仮定

java 実装の参照のメモリサイズを 4 バイトであると仮定すると、エントリ、キー、値の参照のサイズの合計だけで 25,769,803,764 バイトになります。32ビット環境では1プロセスが扱えるメモリの上限はせいぜい 2GB や 4GB 程度であるため、エントリ数が int の上限を超えることは考えられません。

64bitOS環境でのオンメモリキャッシュを仮定

java 実装の参照のメモリサイズを 8 バイトであると仮定すると、エントリ、キー、値の参照のサイズの合計だけで 51,539,607,528 バイトになります。64ビット環境では1プロセスが扱えるメモリの上限が 100GB 超であるため、エントリ数が int の上限を超えることは考えられないこともないです。しかし、エントリ、キー、値の実際の情報まで含めて考えると現実的ではありません。(現状のOSCacheやEhCacheのエントリが含むメタ情報から考えると、ありえないと言えます)

ディスクキャッシュを仮定

ディスクキャッシュの場合、値はディスク上に保存するとしても、エントリ用のメタ情報とキー情報はインデックスとしてメモリ上に配置しておく必要があります。となると、上記のオンメモリキャッシュの場合と同様、ディスクキャッシュでもエントリ数が int の上限を超えることは現実的ではありません。

結論

おいら的には、hibernate のインタフェースに合わせるためには、javadocに制約事項を書いた上で、値が Integer.MAX_VALUE 未満の時はその値を返し、Integer.MAX_VALUE の場合は -1 を返すようにするかな。そうすれば、ユーザーの要求も満たすし、仕様もみたすかな、と。バックエンドのキャッシュの設計という視点では、エントリ数は int として扱うほうがよさそう。

(おまけ)ConcurrentHashMap の場合

ConcurrentHashMap は、内部では long で総数を計算しているのに、return する直前にわざわざ Integer.MAX_VALUE を超えた場合は Integer.MAX_VALUE を返す処理をしています。もったいない、、、。ちなみに、継承して long で総数を返す処理を追加しようと思ったところ、パッケージプライベートで情報を隠蔽しているため、アクセスできません o...rz