EC2でMemSQL クラスターを構成する （有償・無償 対応）

# 背景
MemSQLについての情報がすごく少ない。Qiitaではタグが付いている記事が3件。ググっても日本語・英語問わず公式サイトの情報以外あまり見かけないような気がする。
以前、リアルタイム分析の環境を作ったときに、MySQL互換のSQLが使えて、MySQLクライアントで接続できて、セットアップが簡単ということでMemSQLを採用し、そこそこのデータを扱ったので、どんな風に環境を作ったかメモを残す。

[MemSQL](https://www.memsql.com)の公式サイトはチュートリアルが親切。導入企業のロゴ見ると、データ量と要求される速度が厳しそうなブランドが並んでいる。

# MemSQLのライセンス
- 2年くらい前まで、「Enterprise」と「Community」みたいな区別になっていて、サポートを受けられるか、High Availability関連の機能が使えるか、バックアップ機能が使えるか、が違いだった。メモリーやストレージの容量はCommunity版でも制限は無かったので、EC2を使う費用を山盛りできれば爆速なインメモリーDBをサクッと立ち上げられた
- その後、「Enterprise」と「Developer」になり、Developer Editionは「本番環境での使用は許可しない」ような形になったが、引き続きメモリー等の制限はなく、あくまで開発環境では無料で使えた
- 2018年に、[MemSQL6.7が出て](https://www.memsql.com/blog/announcing-memsql-free-tier/)から、CommunityとかDeveloperというライセンスが廃止され、クラスター全体でのメモリー制限が128GBまでであれば全ての機能が「Free Tier」として無料で利用できるようになった。以前がフリーミアムっぽい設定だったのに対して、どちらかというと評価版という雰囲気か？
- ライセンスについての記述があまりなくて、実質こうですとか、公式ブログの過去記事にありますとか、そういう感じ
- MemSQL6.7未満では、ライセンスキーを入力しなければHA機能が使えないだけだったが、6.7からはMemSQLのサイトでユーザー登録をして無料版であってもライセンスキーの発行が必要になった。ライセンスキーは1ユーザー1つなので、複数クラスターを建てたいときにはなるべくライセンスを購入しましょう（けっこう良い値段した気がするけど忘れた）

# インメモリー vs メモリーテーブル
- MySQLにもメモリーテーブルというのがあるが、単にメモリーにデータがあるというだけ
    - メモリーをフル活用するために設計されていないのでは
    - ディスクに書き込まないので再起動すると消える
- インメモリーはメモリーを活かしきる
    - 内部でのデータ構造がメモリーを活用するために設計されている
    - ディスクに非同期で書き込むので適切に運用すればデータは消えない
    - DBの起動時にディスクからメモリーにデータを展開するので、起動までけっこう時間がかかる

# MemSQLのいいところ
- MySQL用ドライバーが使える（DataGripとかでもMySQLの設定する感じ）
- インメモリーがRow Storeだが、ディスクベースのColumn Storeが使える
- Row StoreとColumn Storeを跨ぐビューが作れる
- 早い
- 簡単
- 128GBまで無料

# クラスターをセットアップする

## 構成
- Master Aggregator として1台
    - 個々のLeafノードから戻ってくる集計結果を束ねる役割だが、メモリーやディスクはあまり要らない。
    - ネットワーク速度が重要なのでそこそこのサイズのインスタンスを選ぶ。小さすぎるとここがボトルネックになったり、EC2のネットワーククレジットが枯渇して急に遅くなったりする。
    - Free Tierの場合、8GBをMaster Aggregatorに割当てるので、その倍〜くらいのメモリーを持つインスタンスタイプが望ましい
- Leafノード（データを格納したり処理するノード）を任意台数
    - NVMe SSDが複数あるインスタンスタイプがオススメ。メモリーサイズも多めがいい。CPUコアあたりのメモリー容量が8GB以上になるのが推奨されているので、r5d.4xlargeが適当かもしれない
    - 物理的にCPUが複数ある場合、numactlを入れる
    - Free Tierの場合、Leafを3台とすると、128GBからMasterの8GBを引いて120÷3で40GBをMemSQLに割当てる。メモリーは余った部分をファイルキャッシュにするので、多くて問題無い。

## EC2 ホストのチューニング
- 基本的には、ストレージをなるべく高速にしましょうということで、別途公開した[Elasticsearchの構成](https://qiita.com/hsano/items/c2e3c387c34334587acb)と同じ。
- インスタンスタイプはNVMe SSD（エフェメラルストレージ）が付随するタイプで、なるべくNitro世代を使う。c5d, r5d, m5dあたり。ストレージ容量を稼ぎたい人は世代は古いけどi3でもいいかもしれない。ストレージのデバイス数が多い方がストライピングしたときの性能が出る気がする。
- ルートボリュームのEBSはあまり大きくなくて良い。
- OSはAmazon Linuxで、普通に管理コンソールで出てくるモノを使う。


### アプデート
```sh
yum -y update
```

### OSの設定を最適化

- `/etc/security/limits.conf` でファイルディスクリプタ、メモリーロック、プロセスが利用できるCPU数を緩和
- `/etc/sysctl.conf` でネットワークとスワップの調整

```sh
bash -c 'echo root soft nofile 1048576 >> /etc/security/limits.conf'
bash -c 'echo root hard nofile 1048576 >> /etc/security/limits.conf'
bash -c 'echo * soft nofile 1048576 >> /etc/security/limits.conf'
bash -c 'echo * hard nofile 1048576 >> /etc/security/limits.conf'
bash -c 'echo * soft memlock unlimited >> /etc/security/limits.conf'
bash -c 'echo * hard memlock unlimited >> /etc/security/limits.conf'
bash -c 'echo * soft nproc 128000 >> /etc/security/limits.conf'
bash -c 'echo * hard nproc 128000 >> /etc/security/limits.conf'
bash -c 'echo net.ipv4.tcp_tw_reuse = 1 >> /etc/sysctl.conf'
bash -c 'echo net.ipv4.tcp_fin_timeout = 30 >> /etc/sysctl.conf'
bash -c 'echo net.ipv4.ip_local_port_range = 16384 65535 >> /etc/sysctl.conf'
bash -c 'echo vm.max_map_count = 262144 >> /etc/sysctl.conf'
bash -c 'echo vm.swappiness = 1 >> /etc/sysctl.conf'
```

### NVMeの最適化

- この例ではNitro世代なので、エフェメラルストレージのデバイスは`nvme1`から始まる（0はEBSのルートになっている）
- `nvme1`部分をデバイスのかずだけ置き換えて設定を繰り返す

```sh
echo 0 > /sys/block/nvme1n1/queue/add_random
echo 1 > /sys/block/nvme1n1/queue/rq_affinity
echo none > /sys/block/nvme1n1/queue/scheduler
echo 1023 > /sys/block/nvme1n1/queue/nr_requests
```

### RAIDでストライピングする

- `/etc/rc.local`に追記する
- マウント先を `/var/lib/memsql` にsiteiruga,MemSQL6.7からはデータを格納するディレクトリの変更ができなくなったため。（以前は`memsql-ops` コマンドでディレクトリを移動することができた）

```sh
# エフェメラルストレージにディレクトリが残っていなかったら作成する
if [ ! -d /var/lib/memsql ]
then
    mkdir /var/lib/memsql
fi

# RAIDが構成されていなかったら構成する
if [ ! -d /dev/md0 ]
then

	# NVMeデバイスを束ねてRAIDを作る
	mdadm --create --verbose --level=0 /dev/md0 --name=DATA --raid-devices=2 /dev/nvme1n1 /dev/nvme2n1
	mkfs.ext4 /dev/md0
	mdadm --detail --scan | tee -a /etc/mdadm.conf

	# 念のためカーネルオプションを更新
	dracut -H -f /boot/initramfs-$(uname -r).img $(uname -r)

	# RAIDをマウントする
	mount -a

fi

chmod 755 /var/lib/memsql
```

### fstabに追記して再起動

```sh
bash -c 'echo /dev/md0 /var/lib/memsql ext4 defaults,nofail,noatime,discard 0 2 >> /etc/fstab'
reboot
```

## MemSQLのインストール

- インストール作業はMaster Aggregatorになるノードで行う
- SSHで個々のノードに繋いで自動的に処理してくれるので便利
    - 事前にMasterから各LeafにSSHできるようにして、接続確認しておく
- 有償ライセンスを持っている場合、クラスター全体でMemSQLに割当てるメモリー容量がライセンスで許可されている範囲内にすること
- 無償ライセンスの場合、クラスター全体でMemSQLに割当てるメモリーが128GB以下になる必要がある
    - クラスター全体の総メモリー容量ではない
    - MemSQLには `memsql_memory` と `memsql_table_memory` の2つの容量指定がある
    - `memsql_table_memory` はインメモリーのテーブルでデータを格納するのに割当てるメモリーで、`memsql_memory`がMemSQLが利用するメモリー。`memsql_memory`の80%くらいが`memsql_table_memory`になるのが普通だが、複雑な集計をする場合は7:3とか6:4くらいにした方がいい。

### MemSQLをダウンロードする
公式ドキュメント通りで、リポジトリを追加して、yumでインストールする。

```sh
sudo yum-config-manager --add-repo https://release.memsql.com/production/rpm/x86_64/repodata/memsql.repo
sudo yum install -y memsql-toolbox memsql-client memsql-studio
```

### Master Aggregatorを構成
MemSQLがこのノードをMaster Aggregatorとして使いますよ、という設定をする。

```sh
memsql-toolbox-config register-host --localhost --host 3.0.0.1
```

### Leafノードを構成
MemSQLがこれらのノードをLeafとして使いますよ、という設定をする。
`ssh` 引数で接続情報を渡している。

```sh
memsql-toolbox-config register-host --host 3.0.0.2 --ssh ec2-user@3.0.0.2:10022 -i .ssh/memsql.pem
memsql-toolbox-config register-host --host 3.0.0.3 --ssh ec2-user@3.0.0.3:10022 -i .ssh/memsql.pem
memsql-toolbox-config register-host --host 3.0.0.4 --ssh ec2-user@3.0.0.4:10022 -i .ssh/memsql.pem
```

### 全てのノードにMemSQLのバイナリーをインストールする
MemSQLは以前からSSH接続してクラスターをリモートで作成できるのが便利。

```sh
memsql-deploy install --all
```

### 全てのノード（EC2マシン）にMemSQLノード（MemSQLで使う領域）を作成する
ノードにノードを作成するという言い回しが変だけど…

```sh
memsql-admin create-node --password P4ssw0rd --host 3.0.0.1
memsql-admin create-node --password P4ssw0rd --host 3.0.0.2
memsql-admin create-node --password P4ssw0rd --host 3.0.0.3
memsql-admin create-node --password P4ssw0rd --host 3.0.0.4
```

### Master Aggregatorを立ち上げる

- 個々のノードの`MemSQL ID` は `memsql-admin list-nodes` で調べられる
- ライセンスはMemSQLのサイトでログインして取得しておく
- `maximum_memory` は Master Aggregatorの動くこのインスタンス/ノードで確保する容量
    - 今回はFree Tierでの構成なので、クラスター全体で128GBにしたい、Leafに多く割当てたい、のでMaster Aggregatorに8GBを割当てている

```sh
memsql-admin update-config --key maximum_memory --value 8192 --memsql-id [MemSQL ID]
memsql-admin restart-node --memsql-id [MemSQL ID]
memsql-admin bootstrap-aggregator --memsql-id [MemSQL ID] --license [License]
```

### Leafを立ち上げる（Leafノードの台数分やる）

- Leaf を3台にするので各40GBメモリーを確保する
- Masterと同じく、`memsql-admin list-nodes`でMemSQL IDを調べる

```sh
chown memsql:memsql /var/lib/memsql
memsql-admin update-config --key maximum_memory --value 40960 --memsql-id [MemSQL ID]
memsql-admin restart-node --memsql-id [MemSQL ID]
memsql-admin add-leaf --password P4ssw0rd --memsql-id [MemSQL ID]
```

### 調整
- `optimize` でパーティションの分割数やNUMA周りを推奨設定にしてくれる
- `maximum_memory`が勝手に大きくなってしまうが、改めてライセンスの範囲内になるように設定し直す（前段の設定と重複するが、前段で設定しないとインストールが出来ないので冗長だけど仕方ない）
- 設定し終わったらMemSQLを再起動する。コマンド一発で全ノードでMemSQLの再起動ができる。

```sh
memsql-admin optimize
memsql-admin update-config --key maximum_memory --value 8192 --memsql-id [MemSQL ID]
memsql-admin update-config --key maximum_memory --value 40960 --memsql-id [MemSQL ID]
memsql-admin update-config --key maximum_memory --value 40960 --memsql-id [MemSQL ID]
memsql-admin update-config --key maximum_memory --value 40960 --memsql-id [MemSQL ID]
memsql-admin restart-node --all
```

## 仕上げ
- MemSQL Studioを使いたい場合は以下のコマンドで起動する
- http://{}ホスト}:8080 でUIにアクセスできる。ELB等でhttpsで繋がるようにするといい

```sh
sudo memsql-studio &
```

- ユーザー作成やテーブル作成は Master Aggregatorで `memsql` コマンドを打てばMySQLクライアントそっくりのコンソールが使える（Masterからは認証無し）

# 終わりに
- ライセンスが安定しない印象があるので使い方は注意が必要かもしれない。ミッションクリティカルなことに使っていて、ある日突然ライセンス形態変わって焦るのは避けたい
- インメモリーDBで、MySQL用のドライバーがそのまま使えて、UTF-8がデフォルトで、使い勝手はかなりいい
- メモリー制限の範囲内で活用するにはエフェメラルストレージでRAID組んでColumn Storeを活用する必要がある。非同期でディスクに書き込んでもそのディスクが揮発性なのでデータの永続化には向かない。自分の場合はRedshiftで「遅いけど永続化」しつつMemSQLで「爆速だけどリスクは許容」というハイブリッド戦略にしていた
- 全て自己責任でお試しください