インメモリデータグリッドIgniteのファイルシステムへの永続化を試してみる

# はじめに

Igniteはデフォルト設定ではすべてのデータはメモリに保存され永続性はありません。
ただし、標準でディスクに永続化できる「Ignite native persistence（Igniteネイティブ永続化？）」とう機能を持っており、永続化するかを設定によって簡単に切り替えることができます。

Ignite native persistenceの主な特徴は以下のとおりです。

・データはクラスタ内のノードに分散されてディスクストアに保存されます。
・トランザクションのACIDが保証され、クラスタがダウンしても正常にコミットされたトランザクションでリカバリできます。
・永続化を有効にすると常にディスクにすべてのデータが保存され、メモリには容量の限りデータが格納されます。
・クラスタ再起動してもメモリにロードせず、すぐに利用可能です。データは遅延してメモリにロードされます。
・Ignite中のデータとインデックスはメモリとディスクで同様のフォーマットで保存されます。そのため、メモリ・ディスク間のデータの移動の効率が良いです。
・キャッシュごとに永続化するかどうかを決めることができます。また、キャッシュごとに使用するメモリ量を制限することができます。


## Write-Ahead Log

Write-Ahead Log（WAL）は、通常のデータベースではジャーナル、Oracleデータベースで言えばREDOログに相当します。Igniteのメモリ上のデータが更新されるとWALに更新内容が書き込まれます。
WALにより、クラスタの障害でダウンした場合でも次回起動時にデータが自動でリカバリされます。

WALはセグメントと呼ばれる複数のファイルに分割して書き込まれます。1つ目のセグメントファイルがいっぱいになるとWALアーカイブにコピーされ、2番目のセグメントファイルがアクティブになります。デフォルトでセグメントファイルは10個作成されます。

## チェックポイント

チェックポイントは、メモリ上で更新されたがパーティションに書き込まれていないデータを、ディスク上のパーティションファイルにコピーするプロセスです。なお、メモリ上で更新されたがパーティションに書き込まれていないデータをダーティデータと呼びます。
チェックポイントが実行されることにより、パーティションに書き込まれていないデータが減り、クラスタダウン時にWALセグメントファイルからリカバリするデータ量が減るのと同時に、WALアーカイブの数を減らしディスクを節約することができます。

![Ignite Persistence](https://qiita-image-store.s3.amazonaws.com/0/179607/f034840e-b49c-5eef-e859-f9d15ad3cd10.png)
画像の出典：[Ignite Persistence](https://ignite.apache.org/arch/persistence.html)

①クラスタノードがキャッシュの更新リクエストを受信すると、値を挿入または更新するRAM内のページを探します。ページが更新され、ダーティとマークされます。
②更新はWALの最後に追加されます。
③ノードは更新の成功を確認するレスポンスを送信する。
④チェックポイントは、パラメータで設定された頻度等に応じて定期的にトリガされます。ダーティページはRAMからディスクにコピーされ、特定のパーティションファイルに書き込まれます。

チェックポイント処理はバックグラウンドで定期的に実行され、以下がトリガーになります。
・バッファサイズがいっぱいになった場合（標準で256MB）
・ダーティページ数がtotalPages * 2 / 3値を超えた場合
　※totalPages の説明が見つけられなかったのですが、標準でページサイズが4KBで分割されており、ダーティページがバッファ内の最大ページ数の2/3を超えた場合ということでしょうか？


### WALの設定

WALモードの設定に応じて、Igniteは次の一貫性保証を提供します。
デフォルトはLOG_ONLYで、通常はFSYNCかLOG_ONLYを選択することになります。
データのロストが許されない場合はFSYNCですが、性能への影響も考慮しないといけないでしょう。

| 設定値 | 説明 | 障害発生時の影響範囲 |
|:-:|:-:|:-:|
| FSYNC  | コミットごとにfsyncが実行されます。  | fsyncによりメモリからファイルに書き込まれており、どのような障害からもリカバリできます。  |
| LOG_ONLY  | コミットごとにファイルに書き込まれます。ただし、同期されているかはOSに依存します。  | プロセスが死んでもリカバリできますが、OSダウン時はリカバリできないことがあります。 |
| BACKGROUND  | コミット時に何もしない（いまいちわからない）  | kill -9でいくつかの更新が失われるかもしれません。  |
| NONE  | WALが無効化されます。  | クラスタがgraceful shutdown（active(false)）された場合のみ完全にデータが永続化されます。  |


### WALのその他事項

・WALを無効化することができます。ただし、その場合トランザクションのACID保証が無くなりますので、通常は無効化しません。初期のデータロード時等に使用されることになります。
・WALアーカイブの圧縮を有効にして、必要なディスクサイズを削減することができます。
・WALアーカイブを無効化し、WALセグメントがアーカイブにコピーするオーバーヘッドを回避することができます。

## Ignite native persistenceを有効にしてみる

ここからはようやく実際に動かしてみます。
まずは、設定ファイルを以下のように修正してIgniteを起動します。
persistenceEnabledを有効にしています。

```default-config-persisted.xml
<bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
    <property name="dataStorageConfiguration">
        <bean class="org.apache.ignite.configuration.DataStorageConfiguration">
            <property name="defaultDataRegionConfiguration">
                <bean class="org.apache.ignite.configuration.DataRegionConfiguration">
                    <property name="persistenceEnabled" value="true"/>
                </bean>
            </property>
        </bean>
    </property>
</bean>
```

Ignite起動直後はクラスタが停止しているため、activateします。

```
# ./ignitevisorcmd.sh
visor> open
～
visor> top -activate
```

永続化されたファイルやWALファイルは、デフォルトで${IGNITE_HOME}/work/dbに出力されます。
以下は実際に出力されているファイルのリストです。

```
# tree
.
├── lock
├── node00-0adad078-24a8-4007-94e7-b7c23b03188f
│   ├── cache-ignite-sys-cache
│   │   ├── cache_data.dat
│   │   └── index.bin
│   ├── cache-put-get-example
│   │   ├── cache_data.dat
│   │   ├── index.bin
│   │   └── part-1.bin
│   ├── cp
│   │   ├── 1536779623034-node-started.bin
│   │   ├── 1536779623802-14c4aab4-59e5-4620-b253-ac26541f68e0-END.bin
│   │   ├── 1536779623802-14c4aab4-59e5-4620-b253-ac26541f68e0-START.bin
│   │   ├── 1536779803770-b03a3516-12ac-4073-8db9-e25bc7f1a9fa-END.bin
│   │   └── 1536779803770-b03a3516-12ac-4073-8db9-e25bc7f1a9fa-START.bin
│   ├── lock
│   └── metastorage
│       ├── index.bin
│       └── part-0.bin
└── wal
    ├── archive
    │   └── node00-0adad078-24a8-4007-94e7-b7c23b03188f
    └── node00-0adad078-24a8-4007-94e7-b7c23b03188f
        ├── 0000000000000000.wal
        ├── 0000000000000001.wal
        ├── 0000000000000002.wal
        ├── 0000000000000003.wal
        ├── 0000000000000004.wal
        ├── 0000000000000005.wal
        ├── 0000000000000006.wal
        ├── 0000000000000007.wal
        ├── 0000000000000008.wal
        └── 0000000000000009.wal

9 directories, 24 files

```

「cache-put-get-example」がテストで作成したキャッシュです。インデックスファイル「index.bin」やパーティションのファイル（？）「part-1.bin」がります。
「wal」ディレクトリの下には、「0000000000000000.wal」のようにデフォルトで10個のWALファイルが作成されています。



ここでは操作結果は載せませんが、クラスタを停止・起動させ、停止前のキャッシュがメモリ上にあることを確認しています。


# 参考

Ignite Persistence
https://ignite.apache.org/arch/persistence.html

おまけ

```default-config-persisted2.xml
<bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
    <property name="dataStorageConfiguration">
        <bean class="org.apache.ignite.configuration.DataStorageConfiguration">
            <property name="pageSize" value="#{4 * 1024}"/>

            <property name="defaultDataRegionConfiguration">
                <bean class="org.apache.ignite.configuration.DataRegionConfiguration">
                    <property name="persistenceEnabled" value="true"/>
                    <property name="checkpointPageBufferSize" 
                                        value="#{256L * 1024 * 1024}"/>
                </bean>
            </property>

            <property name="walCompactionEnabled" value="false" />
            <!-- Changing WAL Mode. --> 
            <property name="walMode" value="LOG_ONLY"/>
            <property name="storagePath" value="/var/lib/ignite/persistence"/>
            <property name="walPath" value="/var/lib/ignite/wal/path"/>
            <property name="walArchivePath" value="/var/lib/ignite/wal/path"/>
            <property name="walSegments" value="10"/>
            <property name="walSegmentSize" value="#{64L * 1024 * 1024}"/>
            <property name="walHistorySize" value="20"/>
            <!-- <property name="checkpointingFrequency" value="3"/> -->
            <!-- <property name="checkpointingThreads" value="1"/> -->
        </bean>
    </property>
</bean>

```