TiDB 入門 (tiup playground)

MySQL 互換の NewDB として最近気になっている TiDB について調査すべく、クラスタを手元で簡単にセットアップ可能な `tiup playgroud` コマンドを使って触ってみます。

気になったきっかけは PingCAP さんの Webinar でした。過去分が [YouTube](https://www.youtube.com/channel/UCatxrGZANSnii2fe7FeEwvg) で公開されおり、TiDB について詳しく紹介されているため、興味のある方は見ていただくのが良いかと思います。

TiDB は MySQL 互換 (100% ではない) の NewSQL で PingCAP 社がオープンソース ([GitHub](https://github.com/pingcap/tidb)) として開発しており　TiDB Cloud　というサービスも提供されています。Aurora では Write の性能が足りないといった場合に選択肢となるのではないでしょうか。
(PingCAP さんといえば [Chaos Mesh](https://chaos-mesh.org/) もそうですね)

# アーキテクチャ

TiDB は次の図のような構成となっています。(画像は[公式ドキュメント](https://docs.pingcap.com/tidb/stable/tidb-storage)より)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8739/12372e1e-1b91-7fad-7798-6f9007b07857.png" width="800">

- SQL Layer にある TiDB がクライアントからの接続を受けて処理を行う
- TiDB はデータを持たないため、1台以上の任意の台数にスケール可能 (クエリ処理の CPU が足りないと思ったらここを増やす)
- データはトランザクション対応の分散 Key-value ストアである [TiKV](https://github.com/tikv/tikv) に保存される
- アクセスしたいデータがどこにあるかは [Placemant Driver](https://github.com/tikv/pd) が持っている
- TiKV 内のデータは [Region](https://docs.pingcap.com/tidb/stable/tidb-storage#region) (デフォルトでは96MB) という単位で分割されています
- Region ごとに3つもしくは5つのコピーを持つ (これを node の label で別の AZ に保存されるように調整可能)
- 各 Region は Raft でクラスタ化されており、Leader が IO のリクエストを受ける
- node の負荷が偏らないように Leader を自動で切り替えたりする
- Region は Primary Key などの Range で分けられているが、収まらなくなると分割される
- [TiFlash](https://docs.pingcap.com/tidb/stable/use-tiflash) はオプションだが、列指向のデータ構造で Table 単位でデータを同期させることができ、TiDB のオプティマイザが TiKV と TiFlash のどちらを使うかを判断して使い分ける (HTAP が謳われているのはこの TiFlash があるため)

TiKV の詳細については [Deep Dive というドキュメント](https://tikv.org/deep-dive/introduction/)で詳しく説明されているようです。

# playground で動かしてみる

TiDB のインストール用コマンドである tiup には playground という localhost でクラスタ一式を簡単に実行するための機能があるのでこれを試してみる

## tiup コマンドのインストール

[ドキュメント](https://docs.pingcap.com/ja/tidb/stable/tiup-overview)の通りだと次のようになります。(ちょっと指定が冗長ですかね)

```
curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh
```

install.sh の中では S3 から tarball を取得して tiup バイナリを `$HOME/.tiup/bin/tiup` にインストールして `tiup mirror set` コマンドを実行している。

```
tiup mirror set https://tiup-mirrors.pingcap.com
```

インストール先はデフォルトでは `$HOME/.tiup` となっており、 `TIUP_HOME` という環境変数で指定することも可能。ここではデフォルトのまま進める。

## tiup playground の実行

次のように実行するだけでクラスタができる。モニタリング関連もコミコミ。

```
tiup playground
```

必要なコンポーネントを全てダウンロードしてくれて、`$HOME/.tiup` ディレクトリ下にデータなども保存される。

デフォルトでは最新バージョンがインストールされますが、引数でバージョンを指定することも可能らしい。

```
If you'd like to use a TiDB version other than v6.2.0, cancel and retry with the following arguments:
	Specify version manually:   tiup playground <version>
	Specify version range:      tiup playground ^5
	The nightly version:        tiup playground nightly
```

起動すると次のように表示され、MySQL クライアントから接続できるようになる。

```
CLUSTER START SUCCESSFULLY, Enjoy it ^-^
To connect TiDB: mysql --comments --host 127.0.0.1 --port 4000 -u root -p (no password)
To view the dashboard: http://127.0.0.1:2379/dashboard
PD client endpoints: [127.0.0.1:2379]
To view the Prometheus: http://127.0.0.1:9090
To view the Grafana: http://127.0.0.1:3000
```

`tiup playground` は foreground で動いており、Ctrl-C で停止するとデータディレクトリごと削除されてしまいます。なのでこの後の操作は、background に移してから実行するか、別の terminal から行う。

## TiDB への接続

mysql クライアントコマンドがなくても `tiup client` コマンドを実行するとクライアントコマンドをダウンロードしてくれる。

```
$ tiup client
tiup is checking updates for component client ...
A new version of client is available:
   The latest version:         v1.10.3
   Local installed version:    
   Update current component:   tiup update client
   Update all components:      tiup update --all

The component `client` version  is not installed; downloading from repository.
download https://tiup-mirrors.pingcap.com/client-v1.10.3-linux-amd64.tar.gz 4.37 MiB / 4.37 MiB 100.00% 8.48 MiB/s                                                                                  
Starting component `client`: /home/teraoka/.tiup/components/client/v1.10.3/tiup-client
Connected with driver mysql (5.7.25-TiDB-v6.2.0)
Type "help" for help.

my:root@127.0.0.1:4000=>  show databases;
      Database      
--------------------
 INFORMATION_SCHEMA 
 METRICS_SCHEMA 
 PERFORMANCE_SCHEMA 
 mysql 
 test 
(5 rows)

my:root@127.0.0.1:4000=>
```

## コンポーネントの確認

`tiup playground display` コマンドで現在起動しているコンポーネントが確認できます。Prometheus や Grafana などのモニタリング系のプロセスは表示されない。

```shell
$ tiup playground display
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground display
Pid   Role     Uptime
---   ----     ------
884   pd       8m20.111776675s
895   tikv     8m8.637417703s
1034  tidb     8m4.338608284s
1102  tiflash  7m35.750738423s
```

ps コマンドなどで実行されているプロセスを確認してみると次のプロセスが起動されていました。

- tiup-playground
  - pd-server
  - tikv-server
  - tidb-server
  - tiflash
  - prometheus
  - ng-monitoring-server
  - grafana-server

Placement Driver (PD) サーバーは etcd を内包しており 2379 port で etcd としても機能しますが `/dashboard` ではクラスタ全体の状況を確認できる Dashboard をブラウザで確認することができますし、`/pd/` path 配下は PD としての独自の機能を持っていそうです。

```
$ curl -s http://127.0.0.1:2379/version | jq 
{
  "etcdserver": "3.4.19",
  "etcdcluster": "3.4.0"
}
```
次のような etcd の endpoint にもアクセス可能で、`/v2/keys` で key-value ストアとして PUT, GET もできるが

- http://localhost:2379/v2/stats/leader
- http://localhost:2379/v2/members

次のような endpoint もありました。

- http://localhost:2379/pd/api/v1/ping
- http://localhost:2379/pd/api/v1/health
- http://localhost:2379/pd/api/v1/members
- http://localhost:2379/pd/api/v1/status
- http://localhost:2379/pd/api/v1/stores
- http://localhost:2379/pd/api/v1/config

## Dashboard を確認

起動時に表示されたように http://127.0.0.1:2379/dashboard にブラウザでアクセスすることで Dashboard が確認できます。かなり便利そうです。

```
To view the dashboard: http://127.0.0.1:2379/dashboard
```

<details>
<summary>ログイン画面 (playground なのでパスワードなしでアクセスできます)</summary>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8739/cc519a8f-6e25-88e2-df16-5ed551cb20ee.png">
</details>

<details>
<summary>Overview</summary>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8739/135265d7-5fae-c054-bdaf-9312911306d6.png">
</details>

<details>
<summary>SQL Statements</summary>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8739/49fc3739-3c84-5adc-a28a-432c8c2a7e49.png" alt="SQL Statements">
</details>

<details>
<summary>Instances</summary>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8739/cda0b513-85a8-0809-41f5-7c3444fddcf6.png" alt="Instances">
</details>

<details>
<summary>Store Topology</summary>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8739/e2a21281-1e6e-51b6-283b-c296604e1b6f.png" alt="Store Topology">
</details>

<details>
<summary>Slow Queries</summary>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8739/41806956-13dd-74c4-b874-100422d87aeb.png" alt="Slow Queries">
</details>

<details>
<summary>Monitoring</summary>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8739/74f769e7-651f-97a7-f68e-aefdf282b9c8.png" alt="Monitoring">
</details>

多すぎるのでここには載せませんが、Grafana の Dashboard もかなり整備されています。

## スケールアウトを試す

`tiup playground scale-out` コマンドを使うことで各コンポーネントのスケールアウト（追加）も可能になっている。

### TiDB を増やしてみる

増やす前 tidb は1プロセスしかいない。

```
$ tiup playground display
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground display
Pid   Role     Uptime
---   ----     ------
884   pd       1h23m43.888388462s
895   tikv     1h23m32.414024691s
1034  tidb     1h23m28.115208718s
1102  tiflash  1h22m59.527339355s
```

`tiup playground scale-out --db 2` を実行。

```shell
$ tiup playground scale-out --db 2
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground scale-out --db 2
To connect new added TiDB: mysql --comments --host 127.0.0.1 --port 36087 -u root -p (no password)
To connect new added TiDB: mysql --comments --host 127.0.0.1 --port 41867 -u root -p (no password)
```

1 → 2 に増やすと思って実行したけど実際は +2 でした。

```
$ tiup playground display
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground display
Pid    Role     Uptime
---    ----     ------
884    pd       1h24m6.602635957s
895    tikv     1h23m55.128275408s
1034   tidb     1h23m50.829461771s
57302  tidb     13.803597012s
57315  tidb     12.78547016s
1102   tiflash  1h23m22.241609485s
```

当然ですが、新しいプロセスの 36087, 41867 port でも接続できますし、同じデータが確認できました。

### TiKV を増やしてみる

今回は +1 してみる。

```shell
$ tiup playground scale-out --kv 1
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground scale-out --kv 1
```

+1 されました。

```
$ tiup playground display
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground display
Pid    Role     Uptime
---    ----     ------
884    pd       1h30m6.988179523s
895    tikv     1h29m55.513827062s
58110  tikv     11.743457155s
1034   tidb     1h29m51.215037697s
57302  tidb     6m14.189175641s
57315  tidb     6m13.171052432s
1102   tiflash  1h29m22.627218731s
```

Dashboard でも確認することができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8739/acc65b7d-68ba-3b6a-7361-d1bc9bfc291d.png" width="400">

### PD を増やしてみる

PD は奇数にしておくべきだろうということで +2 します。

```
$ tiup playground scale-out --pd 2
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground scale-out --pd 2
```

増えま... あれ？ 1つ `exit status 1` でコケてる

```
$ tiup playground display
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground display
Pid    Role     Uptime
---    ----     ------
884    pd       1h33m37.156008357s
58343  pd       5.987212106s
58353  pd       exit status 1
895    tikv     1h33m25.681663993s
58110  tikv     3m41.911283186s
1034   tidb     1h33m21.382858975s
57302  tidb     9m44.356996604s
57315  tidb     9m43.338867983s
1102   tiflash  1h32m52.795006356s
```

```
receive command: scale-out
Start pd instance:v6.2.0
pd quit: exit status 1
[2022/09/04 11:41:13.321 +00:00] [INFO] [util.go:41] ["Welcome to Placement Driver (PD)"]
[2022/09/04 11:41:13.321 +00:00] [INFO] [util.go:42] [PD] [release-version=v6.2.0]
[2022/09/04 11:41:13.321 +00:00] [INFO] [util.go:43] [PD] [edition=Community]
[2022/09/04 11:41:13.321 +00:00] [INFO] [util.go:44] [PD] [git-hash=db465fee5548bf747b0a82b821d01c4c72b34b4f]
[2022/09/04 11:41:13.322 +00:00] [INFO] [util.go:45] [PD] [git-branch=heads/refs/tags/v6.2.0]
[2022/09/04 11:41:13.322 +00:00] [INFO] [util.go:46] [PD] [utc-build-time="2022-08-11 01:48:01"]
[2022/09/04 11:41:13.322 +00:00] [INFO] [metricutil.go:83] ["disable Prometheus push client"]
[2022/09/04 11:41:13.322 +00:00] [INFO] [join.go:217] ["failed to open directory, maybe start for the first time"] [error="open /home/teraoka/.tiup/data/TGSUtPb/pd-2/data/member: no such file or directory"]
[2022/09/04 11:41:13.322 +00:00] [FATAL] [main.go:91] ["join meet error"] [error="there is a member that has not joined successfully"] [stack="main.main\n\t/home/jenkins/agent/workspace/build-common/go/src/github.com/pingcap/pd/cmd/pd-server/main.go:91\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:250"]
...
check detail log from: /home/teraoka/.tiup/data/TGSUtPb/pd-2/pd.log
```

次の項で scale-in を試そうと思っていたけど、ここでコケた pd をいないものとするため scale-in コマンドを使います。

```
$ tiup playground scale-in --pid 58353
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground scale-in --pid 58353
no such process
```

消えました。

```
$ tiup playground display
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground display
Pid    Role     Uptime
---    ----     ------
884    pd       1h40m23.2050935s
58343  pd       6m52.036294413s
895    tikv     1h40m11.730736319s
58110  tikv     10m27.960352843s
1034   tidb     1h40m7.431923397s
57302  tidb     16m30.406056264s
57315  tidb     16m29.387926604s
1102   tiflash  1h39m38.844062689s
```

再度 scale-out で +1 を実行。

```
$ tiup playground scale-out --pd 1
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground scale-out --pd 1
```

増えました。

```
$ tiup playground display
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground display
Pid    Role     Uptime
---    ----     ------
884    pd       1h42m22.611039171s
58343  pd       8m51.442243337s
58592  pd       8.311906211s
895    tikv     1h42m11.136697258s
58110  tikv     12m27.366317289s
1034   tidb     1h42m6.837891486s
57302  tidb     18m29.812027642s
57315  tidb     18m28.793899547s
1102   tiflash  1h41m38.250037677s
```

etcd の endpoint で member を確認してみる。

```json
$ curl -s http://localhost:2379/v2/members | jq
{
  "members": [
    {
      "id": "3037d9ad26fb2231",
      "name": "pd-0",
      "peerURLs": [
        "http://127.0.0.1:2380"
      ],
      "clientURLs": [
        "http://127.0.0.1:2379"
      ]
    },
    {
      "id": "5cbd0ce685928950",
      "name": "pd-3",
      "peerURLs": [
        "http://127.0.0.1:37001"
      ],
      "clientURLs": [
        "http://127.0.0.1:35929"
      ]
    },
    {
      "id": "615810cfd763e2cf",
      "name": "pd-1",
      "peerURLs": [
        "http://127.0.0.1:38071"
      ],
      "clientURLs": [
        "http://127.0.0.1:39201"
      ]
    }
  ]
}
```

```json
$ curl -s http://localhost:2379/v2/stats/leader | jq
{
  "leader": "3037d9ad26fb2231",
  "followers": {
    "5cbd0ce685928950": {
      "latency": {
        "current": 0.001328,
        "average": 0.0074498699186991936,
        "standardDeviation": 0.06450564994425718,
        "minimum": 0.000163,
        "maximum": 0.719886
      },
      "counts": {
        "fail": 0,
        "success": 123
      }
    },
    "615810cfd763e2cf": {
      "latency": {
        "current": 0.000702,
        "average": 0.0025673810082063333,
        "standardDeviation": 0.025057330285287605,
        "minimum": 0.000144,
        "maximum": 0.733244
      },
      "counts": {
        "fail": 1,
        "success": 853
      }
    }
  }
}
```

## スケールインを試す

PD のスケールアウトでコケたプロセスを削除するためにすでに scale-in コマンドは登場しましたが、データをもつ TiKV をスケールインさせてみます。

```
$ tiup playground display
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground display
Pid    Role     Uptime
---    ----     ------
884    pd       2h32m32.099234107s
58343  pd       59m0.930435089s
58592  pd       50m17.800106636s
895    tikv     2h32m20.624894931s
58110  tikv     1h2m36.854510585s
1034   tidb     2h32m16.326088732s
57302  tidb     1h8m39.300221586s
57315  tidb     1h8m38.282091024s
1102   tiflash  2h31m47.738225701s
```

`scale-in` コマンドでは外したい（停止したい）コンポーネントの　PID を `--pid` で指定します。

```
teraoka@tidb:~$ tiup playground scale-in --pid 895
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground scale-in --pid 895
tikv will be stop when tombstone
```

**tikv** の場合はデータを持っているためか __tikv will be stop when tombstone__ と出力されます。 tombstone (ツームストーン) は見慣れない単語ですね、直訳では墓石という意味らしいが、Active Directory では廃棄という意味で登場するようだ。([廃棄 tombstone](https://www.google.com/search?q=%E5%BB%83%E6%A3%84+tombstone))
持っていた Region は移して停止するのか？ 停止後に他の tikv からコピーするのか？

TiDB の停止時はこんな感じ。

```
$ tiup playground scale-in --pid 57315
tiup is checking updates for component playground ...
Starting component `playground`: /home/teraoka/.tiup/components/playground/v1.10.3/tiup-playground scale-in --pid 57315
scale in tidb success
```

# まとめ

tiup コマンドの playground 機能でできることを確認してみた。
TiDB のクラスタは各コンポーネントが単一コマンドで完結しているなどシンプルで良いなという印象で、そのため、ローカルのクラスタでも実構成とほぼ差がない。
Dashboard も非常に充実していて運用時には重宝しそう。
TiKV でのデータの持ち方、[TiDB からのアクセスの仕方など](https://tikv.org/deep-dive/distributed-sql/dist-sql/)をもっと知りたくなりましたし、今回全然触れなかった MySQL 互換の部分についても今後触れていきたい。各 TiDB インスタンス間のキャッシュの同期はどうなっているのか？

tiup コマンドは同様に簡単にプロダクション用のクラスタも作れるらしいので便利。