[TiDB] Resource Control を使ってみた 

# 概要

今年の5月からリリースされた v7.1 に Resource Control といった機能が実装されました。Resource Group により使用可能なリソースを制御する仕組みとなります。

また、システムの特性を基づいて、2つレイヤのリソースを制御する機能を提供します。TiDB レイヤでは Token Bucket アルゴリズムを使用して**BURSTABLE** キーワードを使用して設定可能となります。TiKV レイヤでは **PRIORITY** とのキーワードにて TiKV 側の Write/Read タスクの実施優先度を設定することで実現しています。

本文ではtiup playground にて起動したテスト環境を使って、tpcc にて設定値を変えながら、パフォーマンス比較して、設定値の効果を確認します。


## 環境準備
今回は手元の Mac PC を使って、簡易なテスト環境を構築しました。tiup のインストール手順は割愛いたします。

```
tiup playground v7.1.1
```
別のターミナル、またはタブにて以下のコマンドを実施して、クラスターに接続できます。
```
% mysql --comments --host 127.0.0.1 --port 4000 -u root -p
Enter password: << 初期パスワードが空なので、Enter を押す
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 407
Server version: 5.7.25-TiDB-v7.1.1 TiDB Server (Apache License 2.0) Community Edition, MySQL 5.7 compatible

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
```

tpcc を使用しますので、tpcc prapare します。
```
tiup bench tpcc -H 127.0.0.1 -P 4000 -D tpcc --warehouses 5 --threads 20 prepare
```

:::note info
tiup のインストール手順については下記をご参照ください。
https://docs.pingcap.com/ja/tidb/stable/quick-start-with-tidb#deploy-a-local-test-cluster
:::

## リソース容量を見積もる
Resouce Unit(以下RUと略す)は TiDB にてシステムのリソース量を測りやすくするために、数字化にして作られて論理的なリソースの単位となります。一般的には、リソース制御に関する作業を行う前に、まずシステムのリソース容量を見積もって、容量を元に RU を割り当てます。

2つの見積もる方法があります。
<dl>
  <dt>1. 実際のワークロードに基づいて見積もる</dt>
  <dd> ワークロードの時間範囲を指定して見積もります。 </dd>
  <dd> コマンド例)</dd>
  <dd></strong> 2023年11月29日から10分間のワークロードを基づいてリソース容量を見積もります。</dd>
  <dd> <code>mysql> CALIBRATE RESOURCE START_TIME '2023-11-29 12:00:00' DURATION '10m';</code> </dd>
</dl>

<dl>
  <dt>2. 一般的なワークロードに基づいて見積もる</dt>
  <dd> 事前用意されたワークロード(TPCC、OLTP_WRITE_ONLY、OLTP_READ_WRITE、OLTP_READ_ONLY)の中から、実業務に近いワークロードタイプを指定して見積もります。 </dd>
  <dd> コマンド例)</dd>
  <dd> TPCC ワークロードにてリソース容量を見積もります。</dd>
  <dd> <code>mysql> CALIBRATE RESOURCE WORKLOAD TPCC;  </code> </dd>
  </code> </dd>
</dl>


今回は前述の 2.のTPCC のワークロードにてリソース容量を見積もります。29070 になります。
```mysql> CALIBRATE RESOURCE WORKLOAD TPCC;
+-------+
| QUOTA |
+-------+
| 29070 |
+-------+
1 row in set (0.00 sec)
```

# 色々なシナリオを試す
## 1. 使用可能な RU 値による変化
### 1-1. RU に差がある場合
1). リソースグループ rg1 及び rg2 を作ります。

```
mysql> create resource group if not exists rg1 RU_PER_SEC=1000;
Query OK, 0 rows affected (0.09 sec)

mysql> create resource group if not exists rg2 RU_PER_SEC=2000;
Query OK, 0 rows affected (0.09 sec)

mysql> select * from information_schema.resource_groups where name='rg1' or name='rg2';
+------+------------+----------+-----------+
| NAME | RU_PER_SEC | PRIORITY | BURSTABLE |
+------+------------+----------+-----------+
| rg1  | 1000       | MEDIUM   | NO        |
| rg2  | 2000       | MEDIUM   | NO        |
+------+------------+----------+-----------+
2 rows in set (0.00 sec)
```

2). リソースグループ rg1 及び rg2 を作ります。
```
mysql> create user rc_user1@`%` identified by '********' resource group rg1;
Query OK, 0 rows affected (0.03 sec)

mysql> create user rc_user2@`%` identified by '********' resource group rg2;
Query OK, 0 rows affected (0.02 sec)

mysql> select User, User_attributes from mysql.user where user='rc_user1' or user='rc_user2';
+----------+---------------------------+
| User     | User_attributes           |
+----------+---------------------------+
| rc_user1 | {"resource_group": "rg1"} |
| rc_user2 | {"resource_group": "rg2"} |
+----------+---------------------------+
2 rows in set (0.01 sec)
-- 権限を付与する
mysql> GRANT SELECT,INSERT,UPDATE,DELETE ON tpcc.* TO rc_user1@`%`;
Query OK, 0 rows affected (0.02 sec)

mysql> GRANT SELECT,INSERT,UPDATE,DELETE ON tpcc.* TO rc_user2@`%`;
Query OK, 0 rows affected (0.01 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)
```

3). rc_user1 と rc_user2 にて同時に同じパラメータを指定して、tpcc ワークロードを走らせます。
```
tiup bench tpcc -H 127.0.0.1 -P 4000 -D tpcc --user rc_user1 --password ******** --warehouses 5 --threads 10 --time 5m run
tiup bench tpcc -H 127.0.0.1 -P 4000 -D tpcc --user rc_user2 --password ******** --warehouses 5 --threads 10 --time 5m run
```

rc_user1実行結果)
```
...
[Summary] DELIVERY - Takes(s): 299.9, Count: 623, TPM: 124.6, Sum(ms): 514303.9, Avg(ms): 825.3, 50th(ms): 738.2, 90th(ms): 1208.0, 95th(ms): 1476.4, 99th(ms): 1946.2, 99.9th(ms): 2415.9, Max(ms): 2684.4
[Summary] DELIVERY_ERR - Takes(s): 299.9, Count: 1, TPM: 0.2, Sum(ms): 661.1, Avg(ms): 654.3, 50th(ms): 671.1, 90th(ms): 671.1, 95th(ms): 671.1, 99th(ms): 671.1, 99.9th(ms): 671.1, Max(ms): 671.1
[Summary] NEW_ORDER - Takes(s): 300.0, Count: 7147, TPM: 1429.6, Sum(ms): 1241185.9, Avg(ms): 173.7, 50th(ms): 167.8, 90th(ms): 243.3, 95th(ms): 285.2, 99th(ms): 369.1, 99.9th(ms): 486.5, Max(ms): 570.4
[Summary] NEW_ORDER_ERR - Takes(s): 300.0, Count: 5, TPM: 1.0, Sum(ms): 317.4, Avg(ms): 63.7, 50th(ms): 56.6, 90th(ms): 134.2, 95th(ms): 134.2, 99th(ms): 134.2, 99.9th(ms): 134.2, Max(ms): 134.2
[Summary] ORDER_STATUS - Takes(s): 299.8, Count: 683, TPM: 136.7, Sum(ms): 62979.9, Avg(ms): 92.2, 50th(ms): 92.3, 90th(ms): 130.0, 95th(ms): 142.6, 99th(ms): 167.8, 99.9th(ms): 184.5, Max(ms): 184.5
[Summary] PAYMENT - Takes(s): 300.0, Count: 6951, TPM: 1390.4, Sum(ms): 1140446.8, Avg(ms): 164.2, 50th(ms): 151.0, 90th(ms): 268.4, 95th(ms): 318.8, 99th(ms): 402.7, 99.9th(ms): 486.5, Max(ms): 536.9
[Summary] PAYMENT_ERR - Takes(s): 300.0, Count: 4, TPM: 0.8, Sum(ms): 322.5, Avg(ms): 80.2, 50th(ms): 88.1, 90th(ms): 121.6, 95th(ms): 121.6, 99th(ms): 121.6, 99.9th(ms): 121.6, Max(ms): 121.6
[Summary] STOCK_LEVEL - Takes(s): 299.6, Count: 650, TPM: 130.2, Sum(ms): 38030.9, Avg(ms): 58.5, 50th(ms): 56.6, 90th(ms): 88.1, 95th(ms): 96.5, 99th(ms): 109.1, 99.9th(ms): 121.6, Max(ms): 134.2
tpmC: 1429.6, tpmTotal: 3211.5, efficiency: 2223.3%
```

rc_user2実行結果)
```
...
[Summary] DELIVERY - Takes(s): 299.9, Count: 1345, TPM: 269.0, Sum(ms): 478045.4, Avg(ms): 355.5, 50th(ms): 260.0, 90th(ms): 805.3, 95th(ms): 939.5, 99th(ms): 1342.2, 99.9th(ms): 1744.8, Max(ms): 1879.0
[Summary] DELIVERY_ERR - Takes(s): 299.9, Count: 2, TPM: 0.4, Sum(ms): 334.6, Avg(ms): 167.2, 50th(ms): 58.7, 90th(ms): 285.2, 95th(ms): 285.2, 99th(ms): 285.2, 99.9th(ms): 285.2, Max(ms): 285.2
[Summary] NEW_ORDER - Takes(s): 300.0, Count: 14098, TPM: 2819.8, Sum(ms): 1065456.5, Avg(ms): 75.6, 50th(ms): 65.0, 90th(ms): 142.6, 95th(ms): 192.9, 99th(ms): 285.2, 99.9th(ms): 419.4, Max(ms): 520.1
[Summary] NEW_ORDER_ERR - Takes(s): 300.0, Count: 5, TPM: 1.0, Sum(ms): 186.3, Avg(ms): 36.8, 50th(ms): 30.4, 90th(ms): 71.3, 95th(ms): 71.3, 99th(ms): 71.3, 99.9th(ms): 71.3, Max(ms): 71.3
[Summary] ORDER_STATUS - Takes(s): 300.0, Count: 1143, TPM: 228.6, Sum(ms): 34412.1, Avg(ms): 30.1, 50th(ms): 29.4, 90th(ms): 52.4, 95th(ms): 58.7, 99th(ms): 75.5, 99.9th(ms): 88.1, Max(ms): 92.3
[Summary] PAYMENT - Takes(s): 300.0, Count: 13674, TPM: 2734.9, Sum(ms): 1390895.4, Avg(ms): 101.8, 50th(ms): 75.5, 90th(ms): 218.1, 95th(ms): 268.4, 99th(ms): 352.3, 99.9th(ms): 469.8, Max(ms): 570.4
[Summary] PAYMENT_ERR - Takes(s): 300.0, Count: 2, TPM: 0.4, Sum(ms): 90.2, Avg(ms): 45.1, 50th(ms): 41.9, 90th(ms): 50.3, 95th(ms): 50.3, 99th(ms): 50.3, 99.9th(ms): 50.3, Max(ms): 50.3
[Summary] STOCK_LEVEL - Takes(s): 300.0, Count: 1240, TPM: 248.0, Sum(ms): 27729.9, Avg(ms): 22.4, 50th(ms): 22.0, 90th(ms): 37.7, 95th(ms): 44.0, 99th(ms): 56.6, 99.9th(ms): 79.7, Max(ms): 92.3
tpmC: 2819.8, tpmTotal: 6300.4, efficiency: 4385.4%

```

### [結果1-1]
RU が少ないユーザに割り当てられたリソースが少ないので、性能が低くなります。
<table>
  <thead>
    <tr>
      <th> Resource Group </th><th>RU_PER_SEC</th><th>BURSTABLE</th><th>Priority</th> <th>tpmC</th>
    </tr>
  </thead>
  <tr>
    <td> rg1 </td><td> 1000 </td><td>NO</td><td>MEDIUM</td><td>1429.6</td>
  </tr>
  <tr>
    <td> rg2 </td><td> 2000 </td><td>NO</td><td>MEDIUM</td><td>2819.8</td>
  </tr>
</table>

:::note info
tpmC
1分間あたりに処理可能なトランザクション数となります。
:::


### 1-2. RU が同じの場合)
rg1 のRU 割り当てをrg2 と同じ値 2000 に変更します。
```
mysql> alter resource group rg1 RU_PER_SEC=2000;
Query OK, 0 rows affected (0.10 sec)
```

### [結果1-2]
ほぼ同じ性能が出ています。
<table>
  <thead>
    <tr>
     <th> Resource Group </th> <th>RU_PER_SEC</th> <th>BURSTABLE</th><th>Priority</th><th>tpmC</th>
    </tr>
  </thead>
  <tr>
    <td> rg1 </td> <td> 2000 </td><td>NO</td><td>MEDIUM</td> <td>2817.4</td>
  </tr>
  <tr>
    <td> rg2 </td> <td> 2000 </td><td>NO</td><td>MEDIUM</td> <td>2807.2</td>
  </tr>
</table>

## 2. BURSTABLE　の設定有無による変化
BURSTABLE　モードを設定すると、TiDB は現在サーバーに使用可能なすべてのリソースを使用することが可能になります。言い返すと、RU_PER_SEC の設定による制限がなくなります。


### 2-1. BURSTABLE モードでもう一度 1-1 と同じRU割合でもう一回テスト

```
mysql> alter resource group rg1 RU_PER_SEC=1000 BURSTABLE;
Query OK, 0 rows affected (0.10 sec)
mysql> select * from information_schema.resource_groups where name='rg1' or name='rg2';
+------+------------+----------+-----------+
| NAME | RU_PER_SEC | PRIORITY | BURSTABLE |
+------+------------+----------+-----------+
| rg1  | 1000       | MEDIUM   | YES       |
| rg2  | 2000       | MEDIUM   | NO        |
+------+------------+----------+-----------+
2 rows in set (0.00 sec)
```

### [結果2-1]
RU_PER_SEC の設定値に関係なく、BURSTABLE モードでサーバーの使用可能なリソースを使い切れるので、性能が遥かに良いです。
<table>
  <thead>
    <tr>
     <th> Resource Group </th> <th>RU_PER_SEC</th> <th>BURSTABLE</th><th>Priority</th><th>tpmC</th>
    </tr>
  </thead>
  <tr>
    <td> rg1 </td> <td> 1000 </td> <td>YES</td><td>MEDIUM</td><td>10754.5</td>
  </tr>
  <tr>
    <td> rg2 </td> <td> 2000 </td><td>NO</td><td>MEDIUM</td> <td>2731.7</td>
  </tr>
</table>

### 2-2. 2つとも BURSTABLE モードにする

```
mysql> alter resource group rg2 RU_PER_SEC=2000 BURSTABLE;
Query OK, 0 rows affected (0.09 sec)

mysql> select * from information_schema.resource_groups where name='rg1' or name='rg2';
+------+------------+----------+-----------+
| NAME | RU_PER_SEC | PRIORITY | BURSTABLE |
+------+------------+----------+-----------+
| rg1  | 1000       | MEDIUM   | YES       |
| rg2  | 2000       | MEDIUM   | YES       |
+------+------------+----------+-----------+
```

### [結果2-2]
両方が BURSTABLE モードの場合は、どちらを優先することはなく、ほぼ同じ程度の性能が出ています。
<table>
  <thead>
    <tr>
     <th> Resource Group </th> <th>RU_PER_SEC</th> <th>BURSTABLE</th><th>Priority</th><th>tpmC</th>
    </tr>
  </thead>
  <tr>
    <td> rg1 </td> <td> 1000 </td> <td>YES</td><td>MEDIUM</td><td>10072.1</td>
  </tr>
  <tr>
    <td> rg2 </td> <td> 2000 </td><td>YES</td><td>MEDIUM</td> <td>11456.0</td>
  </tr>
</table>

## 3. PRIORITY の設定値による変化
PRIORITY　は TiKV 側の Write/Read タスクの実施優先度を設定することで実現しています。つまり、この設定によりTiKV にプッシュダウンされた coprocessor task や TiKV での書き込み処理タスクは優先処理されることができます。

### 3-1. rg1 を PRIORITY: HIGH で、その他は 2-2 と同じようにして、実施します。
```
mysql> alter resource group rg1 RU_PER_SEC=1000 PRIORITY=HIGH BURSTABLE;
Query OK, 0 rows affected (0.10 sec)

mysql> select * from information_schema.resource_groups where name='rg1' or name='rg2';
+------+------------+----------+-----------+
| NAME | RU_PER_SEC | PRIORITY | BURSTABLE |
+------+------------+----------+-----------+
| rg1  | 1000       | HIGH     | YES       |
| rg2  | 2000       | MEDIUM   | YES       |
+------+------------+----------+-----------+
2 rows in set (0.01 sec)
```
### [結果3-1]
TiKV 側にて rg1 の処理を優先で行われたため、rg1 に属した rc_user1 に行われたワークロードには性能が優れています。
<table>
  <thead>
    <tr>
     <th> Resource Group </th> <th>RU_PER_SEC</th> <th>BURSTABLE</th><th>Priority</th><th>tpmC</th>
    </tr>
  </thead>
  <tr>
    <td> rg1 </td> <td> 1000 </td> <td>YES</td><td>HIGH</td><td>10326.7</td>
  </tr>
  <tr>
    <td> rg2 </td> <td> 2000 </td><td>YES</td><td>MEDIUM</td> <td>9220.2</td>
  </tr>
</table>

# まとめ
前述のシナリオから以下のことがわかります。
1. BURSTABLE モードではない場合は、RU_PER_SEC の割り当てを基づいてリソースが使用されます。
2. BURSTABLE モードの場合は、RU_PER_SEC に関係なく、サーバー上に使用可能なリソースすべて使用されます。
3. PRIORITY の設定により tikv 側の子タスクの実施優先度を上げて処理することが可能となります。

実業務に合わせて使用することをご検討いただければと存じます。お役に立てれば幸いです。


# 補足
本文では、ユーザレベルでの Resource Control を使用しました。その他に、セッションレベル、SQL レベルの使用方法があります。
<dl>
  <dt>- セッションレベル</dt>
  <dd> SET RESOURCE GROUP にて当該セッションにて特定のResource Group を使用することが可能となります。</dd>
  <dd> <strong>コマンド例)</dd>
  <dd> <code>SET RESOURCE GROUP `rg2`;</code> </dd>
  <dd> 以下のコマンドで現在の Resource Group　を確認可能です。</dd>
  <dd> <code>SELECT CURRENT_RESOURCE_GROUP();</code> </dd>
<dt>- SQL レベル</dt>
  <dd> /*+ RESOURCE_GROUP(rg1) */ といったヒントを追加することで特定のResource Group を使用して、SQL を実行することが可能となります。</dd>
  <dd> <strong>コマンド例)</dd>
  <dd> <code>SELECT /*+ RESOURCE_GROUP(rg1) */ * FROM t;</code> </dd>
</dl>


参考）
https://docs.pingcap.com/ja/tidb/stable/tidb-resource-control#what-is-request-unit-ru


最後まで読んでいただいて、ありがとうございました！