VMware GemFireでホスト間でデータ共有する

VMware GemFire(旧VMware Tanzu GemFire)を触って、インストールからホスト間でのデータ共有までを確認した際のメモ。
内容としては[VMware GemFire in 15 Minutes or Less](https://docs.vmware.com/en/VMware-GemFire/10.0/gf/getting_started-15_minute_quickstart_gfsh.html)をざっくり試したものになる。

## VMware GemFireとは
VMware GemFireとは分散型インメモリKey-Value Storeで[Apache GEODE](https://geode.apache.org/)の元となった製品である。
日本ではあまり知名度がない気がするが、10年以上の歴史がある製品で、用途としてはキャッシュ、セッション管理、データストア、地理的に分散したミッションクリティカルなワークロドなどで利用でき、以下の特徴を持っている。
- インメモリデータの格納により、データアクセスのレイテンシを大幅に低減
- 分散型アーキテクチャにより、スケーラビリティと可用性を向上
- 強力なデータ管理機能を備えており、データの整合性と可用性を保証
- 様々なプログラミング言語とフレームワークをサポート

整合性と可用性については[こちらの記事](https://blogs.vmware.com/vmware-japan/2021/05/tanzu-gemfire-deep-dive-%e3%83%87%e3%83%bc%e3%82%bf%e6%95%b4%e5%90%88%e6%80%a7%e3%81%a8gemfire-function.html)にも説明があるので参考にすると良さそう。
また、KVSながらSQLに似た[OQL](https://gemfire.dev/tutorials/java/oql/)というクエリ言語をサポートしている。

## GemFireのコンポーネント
基本的な構成は以下となる。
（参照元：[How Server Discovery Works](https://docs.vmware.com/en/VMware-GemFire/10.0/gf/topologies_and_comm-topology_concepts-how_server_discovery_works.html)）
![1694169112952.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/ab530911-1f2e-065a-a7a1-168ff7a8852a.png)

コンポーネントの役割は以下。

- Server：データ領域となるインメモリストレージを提供するサーバ。ホストではなくインスタンスであり、1ホスト上で複数起動させることも可能。
- Locator：GemFireのプロセスで、新しい接続メンバーに実行中のメンバーがどこにいるかを知らせ、サーバーを使用するためのロードバランシングを提供する
- Client：SpringやGem FireNative Clientからキャッシュにアクセスするクライアント

また、Server間ではRegionという概念が存在し、メンバー間でシャーディングされ、水平方向にスケールできる。

## GemFireのインストール

[インストール手順](https://docs.vmware.com/en/VMware-GemFire/10.0/gf/getting_started-installation-install_intro.html)を見ると、GemFireのインストールは2種類用意されている。
- tarファイルを展開してのインストール
- OVAを使ったインストール

OVAを使ったインストールはリンクが誤っているようで、VMware Marketplaceになっているが、こちらで公開されているイメージは9.15.7と最新版ではない。
最新版は[VMware Tanzu Network](https://network.tanzu.vmware.com/products/pivotal-gemfire/)から入手できる。
ただし、OVA版に関してはアップグレードの際に結局tarによるインストールが必要になり、OSのインストールの手間が省ける程度のメリットしか感じられないため、ここではtarでインストールする。

### 要件
プラットフォーム要件は[こちら](https://docs.vmware.com/en/VMware-GemFire/10.0/gf/getting_started-system_requirements-supported_platforms.html)に記載がある。
Linuxであれば、Kernelのバージョンが4.18以上であれば問題なく、ディストリビューションに関する指定もない。
ホストマシンの要件は[こちら](https://docs.vmware.com/en/VMware-GemFire/10.0/gf/getting_started-system_requirements-host_machine.html)によると以下となる。
- JDKが以下のいずれか
  - 8u361以上
  - 11.0.18以上　※推奨
  - 17.0.6以上
- NTPにより時刻同期済み
- TCP SYN Cookieの無効化（`/etc/sysctl.conf`に`net.ipv4.tcp_syncookies = 0`を書く）

JDK17についてはいくつか[注意点](https://docs.vmware.com/en/VMware-GemFire/10.0/gf/getting_started-system_requirements-java_support.html#how-jdk-17-encapsulation-affects-gemfire-1)がある。
TCP SYN Cookieを使ってシステムを保護している場合は、GemFireのサーバの前段にFWを置くなどして別手段で保護する必要がある。

### GemFireのインストール
#### OS側の事前設定
前提であるTCP SYN Cookieを無効化する。
```sh
sudo sysctl -w net.ipv4.tcp_syncookies=0 | sudo tee -a /etc/sysctl.conf
```
Ubuntuの場合、時刻同期も実施する。
```sh
sudo apt install chrony -y
sudo systemctl restart chrony
```
JDK11もインストールし、パスを通す。
- Ubuntuの場合
```sh
sudo apt install openjdk-11-jdk -y
echo 'export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64' >> ~/.bashrc
echo 'export PATH=$PATH:$JAVA_HOME/bin' >> ~/.bashrc
. ~/.bashrc
```
- [RHELの場合](https://access.redhat.com/documentation/ja-jp/openjdk/11/html-single/installing_and_using_red_hat_build_of_openjdk_11_on_rhel/index)
```
sudo yum install java-11-openjdk -y
```

#### インストール
[VMware Tanzu Network](https://network.tanzu.vmware.com/products/pivotal-gemfire/)にログインし、VMware GemFire(vmware-gemfire-10.0.1.tgz)をダウンロードする。

ダウンロードしたファイルを展開し、パスを通す。
```sh
export INSTALL_DIR=/opt/vmware
sudo mkdir -p $INSTALL_DIR
sudo tar xvzf vmware-gemfire-*.tgz -C $INSTALL_DIR
echo "export PATH=\$PATH:$INSTALL_DIR/vmware-gemfire-10.0.1/bin" >> ~/.bashrc
. ~/.bashrc
```
gfshを実行し、正常に動作することを確認する。
```sh
$ gfsh version
10.0.1
```

## GemFireの基本動作
### Locatorの起動
インストール確認時に実行したgfsh(発音：ジーフィッシュ)はGemFireの管理用コマンドラインであり、GemFireの各種操作はこのコマンドから実施する。
gfshを使ってLocatorを起動する。

workディレクトリを作成し、移動する（ここにログ等が保存される）
```sh
mkdir my_gemfire
cd my_gemfire
```
gfshを起動する
```sh
$ gfsh
    _________________________     __
   / _____/ ______/ ______/ /____/ /
  / /  __/ /___  /_____  / _____  /
 / /__/ / ____/  _____/ / /    / /
/______/_/      /______/_/    /_/    10.0.1

Monitor and Manage VMware GemFire
gfsh>
```
locatorをlocator1という名前で起動する
```sh
start locator --name=locator1
```
問題なければ以下のようなメッセージが出る。
```sh
Locator in /home/ubuntu/my_gemfire/locator1 on wdc-10-220-157-229.nimbus.eng.vmware.com[10334] as locator1 is currently online.
Process ID: 2357070
Uptime: 29 seconds
Geode Version: 10.0.1
Java Version: 11.0.20.1
Log File: /home/ubuntu/my_gemfire/locator1/locator1.log
JVM Arguments: --add-exports=java.management/com.sun.jmx.remote.security=ALL-UNNAMED --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED -Dgemfire.enable-cluster-configuration=true -Dgemfire.load-cluster-configuration-from-dir=false -Dgemfire.launcher.registerSignalHandlers=true -Djava.awt.headless=true -Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /opt/vmware/vmware-gemfire-10.0.1/lib/gemfire-bootstrap-10.0.1.jar

Successfully connected to: JMX Manager [host=wdc-10-220-157-229.nimbus.eng.vmware.com, port=1099]
```
なお、自環境では原因不明だが、途中から以下のエラーが出るようになった。
```sh
Exception in thread "main" com.vmware.gemfire.deployment.modules.internal.LauncherException: java.lang.RuntimeException: An IO error occurred while starting a Locator in /home/imurata/gem_cluster/locator1 on wdc-10-220-157-229.nimbus.eng.vmware.com[10334]: Network is unreachable; port (10334) is not available on localhost.
```
この場合、`--bind-address`を使ってlocalhostではなく自環境のIPを指すと動くようになった。
```sh
start locator --name locator1 --bind-address 10.220.157.229
```
また、使用するPortがデフォルトで10334のため、もし被る場合はこちらも`--port`で別Portを指定する必要がある。


### Pulseの起動（省略可）
PulseはGemFireを監視するためのグラフィカルなダッシュボードを提供するWebアプリケーションである。
ホスト側でブラウザが起動できる必要があり、Xを入れていないLinuxにsshした環境で[公式手順](https://docs.vmware.com/en/VMware-GemFire/10.0/gf/getting_started-15_minute_quickstart_gfsh.html#step-3-start-pulse-2)に従って起動しようとした際、以下のエラーが出た。
```sh
gfsh>start pulse
Running desktop applications is not supported on Linux.
```
ただ、これはブラウザで`http://localhost:7070/pulse`をopenしようとしただけで、`start pulse`を実行しなくても`start locator`の時点で内部的にはPulseが起動しているため、デスクトップから別途ブラウザで`http://<Locatorを立ち上げたホスト>:7070/pulse`にアクセスするとアクセスできる。
ユーザ名`admin`、パスワード`admin`でログインすると以下のような画面が表示され、Locatorが緑の四角で表示される。
![1694246702069.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/ce84ffb4-fed5-69a0-d35a-9ecb364e4633.png)

RHELの場合、ブラウザでアクセス出来ない場合はFirewallのデフォルト設定で許可されていない可能性が高いので、firewallに穴を開けるなり、`systemctl stop firewalld`なりで許可するようにすること。
なお、アクセスポートを変更したい場合はLocatorの起動時に`--http-service-port=9999`のようにPort番号を指定すると良い。

### Serverの起動
再掲となるが、GemFireのアーキテクチャは以下となる。
![1694169112952.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/ddf4590d-c6c0-3684-70c7-1bba3a3b7256.png)
この図の中のServerに当たる部分を起動する。
Locatorを起動していたホストで、gfshから以下を実行する。（`--server-port`は省略可能で、省略した場合は40404を利用する）
```sh
start server --name=server1 --server-port=40411
```
また、こちらも以下のようなエラーが出ることがある。
```
Exception in thread "main" com.vmware.gemfire.deployment.modules.internal.LauncherException: java.lang.RuntimeException: An IO error occurred while starting a Server in /home/imurata/gem_cluster/server1 on wdc-10-220-157-229.nimbus.eng.vmware.com[40411]: Network is unreachable; port (40411) is not available on localhost.
```
この場合は`--server-bind-address`で自身のIPを指定すると自環境の場合は解決した。
```
start server --name=server1 --server-port=40411 --server-bind-address=10.220.157.229
```

起動後、Pulseで緑の四角をクリックすると、LocatorとServerが存在していることが確認できる。
![1694247253459.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/ccd796ca-95e2-a480-7d0a-a345c1b464d5.png)

### リージョンの作成
次にリージョンを`regionA`という名前で作成する。なお、`--type`で指定しているのはリージョンの種類で[こちら](https://docs.vmware.com/en/VMware-GemFire/10.0/gf/basic_config-data_regions-region_shortcuts.html)に詳細説明がある。
今回はレプリケーションを組みつつディスクに保存して永続性を持たせる`REPLICATE_PERSISTENT`を指定している。
```sh
create region --name=regionA --type=REPLICATE_PERSISTENT
```
作成後、`describe`を使ってregionAの詳細を確認する。
```sh
describe region --name regionA
```
ここでは以下のようにserver1がメンバーであることが分かる。
```sh
Name            : regionA
Data Policy     : persistent replicate
Hosting Members : server1

Non-Default Attributes Shared By Hosting Members

 Type  |    Name     | Value
------ | ----------- | --------------------
Region | data-policy | PERSISTENT_REPLICATE
       | size        | 0
       | scope       | distributed-ack
```
特定のServerのみ含めたい場合は、Region作成時に`--group`でGroup指定し、かつServer作成時にも`--group`を指定すると絞る事ができる。

### データの格納
ここでは以下のデータを格納する。
|key|value|
|---|---|
|1|one|
|2|two|

データの格納はputで行う。
```
put --region=regionA --key="1" --value="one"
put --region=regionA --key="2" --value="two"
```
格納出来たらクエリを発行してデータを取得してみる。
```
query --query="select * from /regionA"
```
実行すると以下の結果が得られる。
```sh
Result : true
Limit  : 100
Rows   : 2

Result
------
two
one
```
gfshを終了して、再度データにアクセスすると、`REPLICATE_PERSISTENT`でリージョンを作成していたため、データにアクセスすること出来る。
なお、Pulseの方からもクエリを発行してデータを取得することが出来る。
![1694997446548.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/e993281b-680a-69b1-db70-edd94c8fc849.png)

## 別ホストとのデータ共有
先程作ったデータはUbuntu上に作っていたが、ここに別ホスト（RHEL）から先程putしたデータを読み取ったり、逆にUbuntuからRHEL上のデータが読めるかを確認する。
構成としては以下のような感じ。
![1695017370706.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/f70fdf55-b12a-c22b-d5dd-ed9eba189ab8.png)

RHELでGemFireをインストールし、gfshを起動して`connect`コマンドで以下のようにLocatorへ接続する。
```sh
connect --locator=10.220.157.229[10334]
```
クエリを発行すると、データが取得できることが分かる。
```sh
gfsh>query --query="select * from /regionA"
Result : true
Limit  : 100
Rows   : 2

Result
------
two
one
```
メンバーも取得できる。
```sh
gfsh>list members
Member Count : 2

  Name   | Id
-------- | -----------------------------------------------------------------
server1  | wdc-10-220-157-229(server1:15127)<v1>:47702
locator1 | 10.220.157.229(locator1:9018:locator)<ec><v0>:58640 [Coordinator]
```
RHEL側でServerを立てる。
```sh
start server --name rhel1
```
すると、RHEL側、Ubuntu側双方で即座に認識される。
```sh
gfsh>list members
list members
Member Count : 3

  Name   | Id
-------- | -----------------------------------------------------------------
server1  | wdc-10-220-157-229(server1:15127)<v1>:47702
locator1 | 10.220.157.229(locator1:9018:locator)<ec><v0>:58640 [Coordinator]
rhel1    | wdc-10-215-65-35(rhel1:10868)<v2>:60444
```
RHEL側でデータを格納してみる。
```sh
put --region=regionA --key="foo" --value="bar"
put --region=regionA --key="hoge" --value="fuga"
```
Ubuntu側で確認する。
```sh
gfsh>query --query="select * from /regionA"
Result : true
Limit  : 100
Rows   : 4

Result
------
two
fuga
one
bar
```
無事RHEL側のデータが読み取れた。
なお、RHEL側で`shutdown`をするとUbuntu側のServerも含めて停止するため注意。