CLOVER🍀

That was when it all began.

Apache DirectMemory 0.2を使ってみる・Ehcacheとの統合

以前、Javaで使えるキャッシュライブラリということで、こんなエントリを書きました。

Javaで使えるオープンソース・キャッシュライブラリ
http://d.hatena.ne.jp/Kazuhira/20130723/1374587549

ここで紹介した、Apache DirectMemory。

Apache DirectMemory
http://directmemory.apache.org/

前回はバージョンも0.1で、System#exitしないと終了できないみたいな状態でしたが、そのあたりが改善された0.2がリリースされていたのでご紹介。

Apache DirectMemoryとは?

前回と被りますが、Apache DirectMemoryとはoff-heapメモリを前面に出したキャッシュライブラリです。キャッシュに保存するエントリは、JavaVMのヒープではなく、Javaヒープ外のメモリに格納されます。

NIOのByteBuffer#allocateDirectで確保する、あのダイレクトメモリですね。

この方法のメリットは、ヒープ内のキャッシュと比べてGCの影響を受けないという点です。デメリットは、シリアライズ/デシリアライズのコストがかかるということですね。

比較的サイズの大きなエントリをキャッシュする時に、使うんじゃないなぁ?と思います。

また、こちらにApache DirectMemoryについて紹介してある資料があります。もうちょっと詳しく書いているので、読んでみるといいかも?

http://www.slideshare.net/benoitperroud/direct-memory-jugl20120308-12607297

まあ、正直なところ、Subversionで管理されているソースのコミット頻度とか見ていると、どこまで頑張るのかよくわからないのですが、発想自体は面白いので再度ご紹介です。

使ってみる

Getting Started的なページと、前回書いたコードを元に簡単な使い方を紹介します。

Simple Usage
http://directmemory.apache.org/simple-usage.html

Maven依存関係。

    <dependency>
      <groupId>org.apache.directmemory</groupId>
      <artifactId>directmemory-cache</artifactId>
      <version>0.2</version>
    </dependency>

サンプルコードです。
src/main/java/DirectMemoryExample.java

import java.io.IOException;

import org.apache.directmemory.DirectMemory;
import org.apache.directmemory.cache.CacheService;
import org.apache.directmemory.measures.Ram;

public class DirectMemoryExample {
    public static void main(String[] args) {
        try (CacheService<String, String> cacheService =
             new DirectMemory<String, String>()
             .setNumberOfBuffers(10)
             .setInitialCapacity(100000)
             // キャッシュサイズを10MBに
             // この設定を入れないと、保存されない!!
             .setSize(Ram.Mb(10))
             .setConcurrencyLevel(4)
             .newCacheService()) {

            // キャッシュへのエントリ追加
            cacheService.put("key1", "value1");
            cacheService.put("key2", "value2");

            // キャッシュのエントリ数
            System.out.println("entries = " + cacheService.entries());

            // キャッシュからのエントリ取得
            String value1 = cacheService.retrieve("key1");
            System.out.println("key1 = " + value1);

            // キャッシュからのエントリ全削除
            cacheService.clear();

            // キャッシュのエントリ数
            System.out.println("entries = " + cacheService.entries());

            // Expireの確認
            cacheService.put("key3", "value3", 3 * 1000);
            cacheService.put("key4", "value4", 3 * 1000);

            try { Thread.sleep(2 * 1000L); } catch (InterruptedException e) { }

            // キャッシュのエントリ数
            System.out.println("entries = " + cacheService.entries());

            // ひとつだけアクセス
            System.out.println("key3 = " + cacheService.retrieve("key3"));

            try { Thread.sleep(2 * 1000L); } catch (InterruptedException e) { }

            // アクセスする、しないに関わらず、両方共有効期限切れになる
            // *アクセスによる、有効期限の延長は行われない
            System.out.println("key3 = " + cacheService.retrieve("key3"));
            System.out.println("key4 = " + cacheService.retrieve("key4"));

            // キャッシュのエントリ数
            System.out.println("entries = " + cacheService.entries());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

キャッシュのフロントエンドとなるのはCacheServcieインターフェースで、こちらはDirectMemoryクラスを使用して作成します。

        try (CacheService<String, String> cacheService =
             new DirectMemory<String, String>()
             .setNumberOfBuffers(10)
             .setInitialCapacity(100000)
             // キャッシュサイズを10MBに
             // この設定を入れないと、保存されない!!
             .setSize(Ram.Mb(10))
             .setConcurrencyLevel(4)
             .newCacheService()) {

CacheServiceインターフェースはCloseableなので、try-with-resources文の利用が可能です。というか、0.1の頃はcloseができなくてプログラムが終わらなくなったという話が…。

で、DirectMemoryのインスタンスに対してキャッシュの設定を行っていきます。

ここで注意点は、DirectMemory#setSizeで、チュートリアルには書いていませんがここを指定しないとメモリをアロケートしてくれないので、キャッシュに入れても何も保存されません…。

最初、えーって感じでした。

キャッシュへの保存は、CacheService#put、

cacheService.put("key1", "value1");

キャッシュからの取得は、CacheService#retrieveです。

String value1 = cacheService.retrieve("key1");

clearやentriesでエントリ数が取得できたりするので、その他はAPIを。一応、Javadocありますからね。

Apache DirectMemory API
http://directmemory.apache.org/apidocs/reference/packages.html

その他、Webアクセスできるサーバ(サーブレット)やクライアント、2nd Level CacheとしてGoogle Guavaが使えるとかユニークな機能があります。

が、今回は、この中でもちょっと面白いと思った、Ehcacheとの統合をご紹介します。

Ehcacheとの統合

キャッシュライブラリを統合してどうするんだよ?という話ですが、DirectMemoryにはEhcacheのoff-heapになれる機能があります。

は?って感じですが、off-heapは他のキャッシュライブラリではEnterprise版のみで提供されている機能です。例えば、Ehcacheの商用版であるBigMemory、Data GridのHazelcastのEnterprise Editionなどです。

まずはEhcacheを単体で使ってみます。

off-heapを使用するようにした設定ファイル。

Maven依存関係。

    <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache</artifactId>
      <version>2.7.4</version>
    </dependency>

off-heapを使用するようにした、Ehcacheの設定ファイル。
src/main/resources/ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
  <defaultCache />

  <cache name="offHeapCache"
     maxEntriesLocalHeap="100"
     eternal="false"
     overflowToOffHeap="true"
     maxElementsInMemory="30"
     maxBytesLocalOffHeap="100M"
     memoryStoreEvictionPolicy="LFU">
    <persistence strategy="none" />
  </cache>
</ehcache>

Ehcacheを使ったコード。
src/main/java/EhcacheOffHeapExample.java

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;

public class EhcacheOffHeapExample {
    public static void main(String[] args) {
        CacheManager cacheManager = CacheManager.getInstance();
        Cache cache = cacheManager.getCache("offHeapCache");

        cache.put(new Element("key1", "value1"));

        System.out.println("key1 = " + cache.get("key1").getObjectValue());

        stats(cache);
   
        cacheManager.shutdown();
    }

    private static void stats(Ehcache ehcache) {
        System.out.println( "OnHeapSize=" + ehcache.calculateInMemorySize() + ", OnHeapElements="
                            + ehcache.getMemoryStoreSize() );
        System.out.println( "OffHeapSize=" + ehcache.calculateOffHeapSize() + ", OffHeapElements="
                            + ehcache.getOffHeapStoreSize() );
        System.out.println( "DiskStoreSize=" + ehcache.calculateOnDiskSize() + ", DiskStoreElements="
                            + ehcache.getDiskStoreSize() );
    }
}

では、実行してみます。

Caused by: net.sf.ehcache.CacheException: Cache offHeapCache cannot be configured because the enterprise features manager could not be found. You must use an enterprise version of Ehcache to successfully enable overflowToOffHeap.

すると、Enterprise版じゃないと、off-heapは使えないよと怒られます。

ここで、pom.xmlに以下の依存関係を追加します。

    <dependency>
      <groupId>org.apache.directmemory</groupId>
      <artifactId>directmemory-ehcache</artifactId>
      <version>0.2</version>
      <exclusions>
        <exclusion>
          <groupId>net.sf.ehcache</groupId>
          <artifactId>ehcache</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

注)ここでは、Ehcache 2.7.4を使うためにdirectmemory-ehcacheが依存しているEhcache 2.6.6を除外しました
問題があったら、先に定義したEhcacheの依存関係とexclusionsを削除してください

では、コードは一切変更せずに実行してみます。

key1 = value1
OnHeapSize=0, OnHeapElements=0
OffHeapSize=243, OffHeapElements=1
DiskStoreSize=0, DiskStoreElements=0

これだけで、Ehcacheのoff-heap機能が使えるようになりました。

いろいろ面白いのですが、今後開発がどうなるのかが気になるところですね。1.0まで開発が進むといいなぁ。