#chiroito ’s blog

Java を中心とした趣味の技術について

Github上の最新のInfinispanを使う

Infinispanを使う時はDownload - Infinispanのようなダウンロードサイトで必要なファイルをダウンロードして使います。しかし、開発しているGithub上の最新バージョンを使って動かしたい時もあるでしょう。そんな時はビルドした後に出来上がる成果物を使います。

ビルド環境の準備の仕方はこちらをご覧ください

Windows 上に Infinispan 11 のビルド環境を構築してみた - #chiroito ’s blog

infinispanディレクトリにソースコードを落としてきてとして、以下の様にビルドします。

$ cd infinispan
$ mvn -s maven-settings.xml package -DskipTests=true

100モジュールほどのビルドが掛かるのでしばらく待ちます。無事に終了すると infinispan\server\runtime\target\infinispan-server-11.0.0-SNAPSHOTディレクトリに成果物が出来上がっています。 これがダウンロードサイトでZipされているものと同一になりますので、これを使うことでGithub上の最新バージョンを使えます。

OpenJDK の Author から Committer になりました

これまで通算11個のパッチを書いたので、推薦してもらい、OpenJDKコミュニティ内での信任投票の結果 OpenJDK の Committer になりました。

※推薦してもらった時点では12個でしたが、その間に1個ダメになったので11個でした。

他のOSSだと1個パッチを書くだけでCommitterと呼ばれるようですが、OpenJDKはしっかりとした任命プロセスがあります。プロセスについては以下のリンクに詳細が記載されています。

http://openjdk.java.net/projects/#project-committer

それでは任命プロセスを踏まえて私の例を振り返ってみましょう。私はまず3つのパッチを書いて、Author というロールになりました。これによって、Java Bug SystemやReview用のサーバなどのOpenJDKを開発するためのツールの一部が使えるようになります。

Author になった時のブログはこちらです。

OpenJDK の Author になりました - #chiroito ’s blog

Authorに任命された後もさらにパッチを書き続け、エンハンスも含めて20個ぐらい書きました。しかし、JFRのエンハンスはほとんど不採用となり、8個のパッチが採用され、通算11個のパッチとなりました。

ここに至るまで、ほんとReviewerの末永恭正さん(@YaSuenag)には大変お世話になりました。たくさんアドバイスやレビューをしていただいたので、ほんと足を向けて寝られません。

十分な修正量のパッチが揃ったら、OpenJDKコミュニティにいる誰かに推薦してもらいます。だれに推薦してもらうかによってコミュニティでの信頼度が高まりますので、政治力が非常に重要になります。私の場合は所属している Red Hat の OpenJDK チームのリーダーに推薦してもらいました。

Committerになるには十分な修正量のパッチが通算8個以上必要です。そのため、自信がある人は8個のパッチを書くだけでCommitterになれます。私はそんなにスキルが高くないので、通算11個になるまで推薦してもらうのを控えてました。

こんな感じの推薦メールを出してもらいます。

CFV: New JDK Committer: Chihiro Ito

I hereby nominate Chihiro Ito <xxxx at redhat.com> to JDK Committer.

Chihiro Ito has contributed 12 significant fixes to OpenJDK.

Votes are due by March 27, 2020.

Only current JDK Committers [1] are eligible to vote
on this nomination. Votes must be cast in the open by replying
to this mailing list.

For Lazy Consensus voting instructions, see [2].

Thanks,
Anton // http://openjdk.java.net/census#ant

[1] http://openjdk.java.net/census
[2] http://openjdk.java.net/projects/#committer-vote
[3] List of changes:

[2] List of changes:
http://hg.openjdk.java.net/jdk/jdk/search/?rev=keyword%28chihiro.ito%29%20%7C%20author%28cito%29&revcount=20

(以下パッチの一覧を記載)

このメールに返信する形で投票が始まります。Committer以上の人が良いかダメかを返信することで投票します。投票には期限があり、全てのCommitterが投票をするか、この期限を迎えることで投票が完了します。

私の投票の期限は2020/3/27でした。投票が完了するとこんな感じの結果発表メールを出してもらいます。

Result: New JDK Committer: Chihiro Ito

Voting for Chihiro Ito [1] is now closed.

Yes: 15
Veto: 0
Abstain: 0

According to the Bylaws definition of Lazy Consensus, this is
sufficient to approve the nomination.

[1] https://mail.openjdk.java.net/pipermail/jdk-dev/2020-March/004044.html

この結果がOKだとOpenJDKコミュニティからCommitterとして認められたということになります。(ここから先のプロセスがないのでたぶん)

OpenJDK開発者一覧にあるロールが更新されます。

OpenJDK Census (まだ変わってなかった)

なお、Author から Committer になるために書いたパッチはこちらです。

興味のある方はぜひOpenJDKへ貢献してみてください。

Infinispan Hot Rod の Distributed-cache で Cache Store/Loader を使う

キャッシュからデータを取得する時に、キャッシュにデータが乗っていないためキャッシュミスが発生し、RDBMSやオブジェクトストレージなどのデータストアからデータを取得し、次に備えてキャッシュに載せると言うことがあります。また、キャッシュの更新や他のデータストアの更新をした際には両者の整合性を保たなければなりません。

この様なケースで、キャッシュミスした場合に透過的に他のデータソースからデータを取り、データをキャッシュに載せて、クライアントにも返してくれたり、キャッシュに格納したデータとデータストアの整合性を保つため、キャッシュへの更新を透過的にデータストアへ適用してくれると非常に助かるのでは無いでしょうか。

Infinispanではこの様なよくある状況に備えて Cache Loader と Cache Storeという機能が使えます。Cache Loader と Cache Storeは独自に実装することもできますが、Infinispan では JPA、REST、ファイルを使った永続ストアが事前に実装されており、そちらを使うことが多いです。

今回は以下の流れで独自の実装を作る方法を紹介します。

  1. コーディング
  2. サーバの設定
  3. 実行

コーディング

Infinispan で永続ストアを実装するには以下のインターフェースを使用します。

  • CacheLoader
  • CacheWriter
  • AdvancedCacheLoader
  • AdvancedCacheWriter

マニュアルには以下の様に記載されています。

CacheLoader と CacheWriter は、ストアに対して読み書きを行う基本的なメソッドを提供します。CacheLoader は、必要なデータがキャッシュにない場合にデータストアからデータを取得します。 AdvancedCacheLoader と AdvancedCacheWriter は、基礎となるストレージを一括で処理する並列反復、失効したエントリーの削除、クリア、およびサイズ指定などの操作を提供します。 org.infinispan.persistence.file.SingleFileStore を使用すると、独自のストア実装を簡単に作成できます。

機能の必要さを考慮すると実際に使うのはAdvanced~になります。今回の実装ではこれらを全て継承しているAdvancedLoadWriteStoreを実装していきます。

今回は実際に使うことを想定して、永続化ストアで開発者が自作したエンティティを使います。自作したエンティティの使い方は以下を参照してください。

Infinispan Hot RodのDistributed-cacheで自作のエンティティを使う - #chiroito ’s blog

今回は、永続ストアを初期化する処理、データストアから読み書きする以下のメソッドだけを実装します。

  • void init(InitializationContext ctx)
  • void write(MarshallableEntry<? extends K, ? extends V> marshalledEntry)
  • MarshallableEntry loadEntry(Object bKey)

初期化処理では、与えられるコンテキストから各処理で必要なインスタンスを取得します。書込処理と読み込み処理は以下の流れで処理をしていくのが一般的です。

書き込み処理

  1. シリアライズされたエントリが渡されます
  2. そのエントリからシリアライズされたキーとバリューを取得
  3. それぞれをデシリアライズしてオブジェクトとして使えるようにする
  4. データストアへ書き込み

読み込み処理

  1. シリアライズされたキーが渡されます
  2. デシリアライズしてキーを取得
  3. データストアから読み込む
  4. 読み込んだデータからオブジェクトを作成
  5. オブジェクトをシリアライズ
  6. シリアライズされたエントリを作成

今回の実装では標準出力にデータを出力することでデータストアへの読み書きをしたとします。サンプルの実装は以下のとおりです。

CacheStoreSample.java

package chiroito.sample;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.commons.marshall.WrappedByteArray;
import org.infinispan.commons.persistence.Store;
import org.infinispan.persistence.spi.*;

import java.io.IOException;
import java.util.concurrent.Executor;

@Store
public class CacheStoreSample<K,V> implements AdvancedLoadWriteStore<K, V> {
    private MarshallableEntryFactory<K, V> entryFactory;
    private PersistenceMarshaller marshaller;

    @Override
    public void init(InitializationContext ctx) {
        this.entryFactory = ctx.getMarshallableEntryFactory();
        this.marshaller = ctx.getPersistenceMarshaller();
    }

    @Override
    public void write(MarshallableEntry<? extends K, ? extends V> marshalledEntry) {
        // シリアライズされたデータを取得
        WrappedByteArray bKey = (WrappedByteArray)marshalledEntry.getKey();
        WrappedByteArray bValue = (WrappedByteArray)marshalledEntry.getValue();

        try {
            // デシリアライズ
            String key = (String) this.marshaller.objectFromByteBuffer(bKey.getBytes());
            CustomEntity value = (CustomEntity) this.marshaller.objectFromByteBuffer(bValue.getBytes());

            // 永続化処理
            System.out.println("Stored");
            System.out.println(key);
            System.out.println(value);

        } catch (IOException|ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public MarshallableEntry loadEntry(Object bKey) {
        try {
            // デシリアライズ
            String key = (String) this.marshaller.objectFromByteBuffer(((WrappedByteArray)bKey).getBytes());

            // 読込処理
            System.out.println("Load");
            System.out.println(key);
            
            // オブジェクトを作成
            CustomEntity value = new CustomEntity("りんご", 3);

            // シリアライズ
            byte[] bValue = this.marshaller.objectToBuffer(value).getBuf();

            // エントリを作成
            return this.entryFactory.create(bKey, new WrappedByteArray(bValue));
        } catch (IOException | InterruptedException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public boolean contains(Object o) {
        return false;
    }

    @Override
    public boolean delete(Object o) {
        return false;
    }

    @Override
    public int size() {
        return 0;
    }

    @Override
    public void clear() {
    }

    @Override
    public void purge(Executor executor, PurgeListener<? super K> purgeListener) {
    }

    @Override
    public void start() {
    }

    @Override
    public void stop() {
    }
}

サーバの設定

このソースコードをビルドして、その成果物をサーバへコピーします。そして、サーバ側で設定ファイルをキャッシュストアを使うように設定してから起動すれば完了です。今回は自作のエンティティクラスを使うため追加でシリアライズの設定も必要になります。

ソースコードのビルドはmvn packageなどで行います。

作成されたjarファイルを確認し、Infinispanのserver/lib にそのjarファイルを置きます

設定ファイルにキャッシュストアとシリアライズの設定をします。server/conf/infinispanに以下を追記します。

     <cache-container>
        <!-- シリアライズの設定 -->
        <serialization>
            <context-initializer class="chiroito.sample.CustomInitializerImpl" />
        </serialization>
        <distributed-cache mode="SYNC" name="mycache">
            <transaction mode="NONE"/>
            <!-- キャッシュストアの設定 -->
            <persistence>
                <store class="chiroito.sample.CacheStoreSample"/>
            </persistence>
        </distributed-cache>
    </cache-container>

ここまでできたらサーバを起動しましょう。

実行

それでは実行してみましょう。次のコードは、key1みかんの情報を入れてから取得し、続いてkey2の情報も取得します。

    public static void main(String[] args) {
        RemoteCacheManager manager = new RemoteCacheManager();
        RemoteCache<String, CustomEntity> c = manager.getCache("mycache");

        c.put("key1", new CustomEntity("みかん", 3));
        System.out.println(c.get("key1"));
        System.out.println(c.get("key2"));

        manager.close();
    }

key1のデータはみかんをputしてからgetしているため、これを実行したコンソールにはみかんが出力されます。サーバ側ではput処理によってCacheStoreが動くため、サーバ側のコンソールにみかんが出力されます。みかんをgetする時には既にキャッシュに乗っているため、CacheLoaderは動作しません。

次にkey2のデータをgetするとデータがキャッシュ上にありません。そのため、CacheLoaderが該当するデータを読み込みます。これによってクライアントにりんごが返されて、これを実行したコンソールには出力されます。サーバ側では、データがキャッシュに乗っていないものに対するgetが行われたのでCacheLoaderが実行され、サーバ側のコンソールにりんごが出力されます。

クライアント側のコンソールは以下の様になります。

3 24, 2020 5:56:27 午後 org.infinispan.client.hotrod.RemoteCacheManager actualStart
INFO: ISPN004021: Infinispan version: Infinispan 'Turia' 10.1.3.Final
3 24, 2020 5:56:27 午後 org.infinispan.client.hotrod.impl.protocol.Codec20 readNewTopologyAndHash
INFO: ISPN004006: Server sent new topology view (id=1, age=0) containing 1 addresses: [192.168.1.1:11222]
CustomEntity{name='みかん', num=3}
CustomEntity{name='りんご', num=3}

サーバ側にはみかんのデータが格納されたこと、key2のデータが読み込まれたことを表す以下のログが出力されます。

サーバ側のコンソールは以下の様になります。

Stored
key1
CustomEntity{name='みかん', num=3}
Load
key2

このようにCacheLoaderCacheStoreを使うことでキャッシュを使うだけで透過的にデータストアへもアクセスできるようになり、非常に便利になります。