## モジュール 1: ディスカッションと環境準備

+ このモジュールでは、SQLレイヤーでスケールインする際のアプリケーションのベストプラクティスについて議論します。内容をよく読んでください。
+ また、演習の準備として、TiDBサーバーのスケールアウト（後でスケールインできるように）やデータのインポートも行います。

### 手順 1. ディスカッション: SQLレイヤーでスケールインする際のアプリケーション開発のベストプラクティス

> [!TIP]
> このモジュールで議論する内容はSQLレイヤーのスケールインにのみ適用されます。TiKVやPDのスケールインには適用されません。TiKVやPDのスケールインはアプリケーションからは透過的です。理由は2つあります：(1) アプリケーションはTiKVやPDと直接アクセスしません。ネットワークバランサーやTiProxyも同様にTiKVやPDと直接通信しません。(2) TiDBとPD/TiKV間の接続はgRPCです。

+ 一般的に、アプリケーションがTiDBと通信する方法は3つあります
  + TiProxy経由
  + ネットワークロードバランサー経由
  + 1つのTiDBサーバーインスタンスに直接接続（本番環境では非推奨）

+ TiProxyはTiDBクラスタのオプションコンポーネントです。TiProxyはコネクションのマイグレーション、フェイルオーバー、サービスディスカバリー、迅速なデプロイを提供します。TiProxyを利用すれば、SQLレイヤーのスケールインもアプリケーションから透過的になります。以下で議論する複雑さを回避できます。

+ アプリケーションがネットワークロードバランサー経由や直接TiDBに接続している場合、TiDBのスケールインは完全に透過的ではありません。このモジュールで説明するアプリケーション開発のベストプラクティスに従う必要があります。これは、アプリケーション（またはロードバランサー）とTiDB間の接続がMySQLプロトコルを使用しているためです。MySQLプロトコルでは、クライアントは単一のサーバーのみが存在すると仮定しており、分散された複数のサーバーは想定していません。さらに、接続はTCPの状態に依存します。

+ 本番環境では、アプリケーションがTiDBサーバーインスタンスに直接接続することはほとんどありません。このモジュールでは、(a) アプリケーションがネットワークロードバランサー経由でTiDBに接続する場合、(b) TiProxy経由で接続する場合の2つのケースを比較・議論します。

+ 次に、TiDBサーバーノードがスケールイン時に行う処理について説明します：
  + ステップ 1: TCPポート（デフォルトは4000）を閉じ、新たな接続を拒否する
  + ステップ 2: 既存の接続について、トランザクションが実行中かどうかを確認する
    + ケース 2.1: トランザクションが実行中でなければ、接続を閉じる
    + ケース 2.2: トランザクションが実行中の場合、一定時間トランザクションの完了を待ち、タイムアウトまたはトランザクションが完了した時点（早い方）で接続を切断します。（この待機時間はソースコード上 [gracefulCloseConnectionsTimeout](https://github.com/pingcap/tidb/blob/bed8f2573a7d6189f1611d913130cfb3150c9100/cmd/tidb-server/main.go#L924-L934C19) と呼ばれ、15秒で固定されており、設定変更はできません。）

+ 次に、ネットワークロードバランサー（NLB）のヘルスチェックプロセスについて説明します。AWSのNLBを例にします。ヘルスチェックに関連する設定やパラメータはいくつかあります。（詳細は[Health checks for Network Load Balancer target groups](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/target-group-health-checks.html)を参照）
  + `HealthCheckTimeoutSeconds`: ターゲットから応答がない場合にヘルスチェック失敗とみなす秒数。範囲は2～120秒。本演習では5秒。
  + `HealthCheckIntervalSeconds`: 個々のターゲットに対するヘルスチェックの間隔（秒）。範囲は5～300秒。本演習では5秒。
  + `UnhealthyThresholdCount`: ターゲットを不健康とみなすまでに必要な連続失敗回数。範囲は2～10。本演習では2回。
  + `Unhealthy draining interval`: ターゲットが不健康と判定された後、既存接続を切断するまでの猶予時間（秒）。本演習では0秒。

+ TiDBサーバーノードが不健康と判定されるまでの最短待機時間（スケールインがヘルスチェック直前に完了した場合）は、`(UnhealthyThresholdCount - 1) * HealthCheckIntervalSeconds + HealthCheckTimeoutSeconds` です。本演習では `(2 - 1) * 5 + 5 = 10` 秒です。

+ 上記計算の正しさを直感的に理解できるよう、いくつか例を示します。以下の図では、`HealthCheckIntervalSeconds`を`interval`、`UnhealthyThresholdCount`を`count`、`HealthCheckTimeoutSeconds`を`timeout`と呼びます。

  + ケース1: `interval = 5`, `count = 2`, `timeout = 2`。TiDBサーバーは最短7秒で`unhealthy`と判定されます。
  ![demo009-m7-interval-case1_aa_001_20250808.png 85%](https://lab-static.pingcap.com/images/2025/8/8/14ae237101b018cd0389e4808d8e33ebbc499cc3.png)

  + ケース2: `interval = 5`, `count = 2`, `timeout = 6`。TiDBサーバーは最短11秒で`unhealthy`と判定されます。
  ![demo009-m7-interval-case2_aa_001_20250808.png 85%](https://lab-static.pingcap.com/images/2025/8/8/6ac54809141c7d7c25a5ba2180490e96da018790.png)

  + ケース3: `interval = 5`, `count = 3`, `timeout = 2`。TiDBサーバーは最短12秒で`unhealthy`と判定されます。
  ![demo009-m7-interval-case3_aa_001_20250808.png 85%](https://lab-static.pingcap.com/images/2025/8/8/96e212e18bcebfdea83aefa0b817aeaf227ecab2.png)

+ 運が悪い場合、スケールインがヘルスチェック直後に完了し、直ちにヘルスチェックが成功で返ると、`UnhealthyThresholdCount * HealthCheckIntervalSeconds + HealthCheckTimeoutSeconds`だけ待つ必要があります。再びケース1を例にします。

  + ケース1: `interval = 5`, `count = 2`, `timeout = 2`。TiDBサーバーは最長12秒で`unhealthy`と判定されます。
  ![demo009-m7-interval-case1_worse_aa_001_20250808.png 85%](https://lab-static.pingcap.com/images/2025/8/8/9c3c48f009559e5b8c149d5716561ca3992acc00.png)

+ 上記の議論から、本演習の設定（`interval = 5`, `count = 2`, `timeout = 5`）では、TiDBノードがターゲットグループから`unhealthy`と判定されるまでの時間は10秒から15秒の間になります。

+ TiDBサーバーのスケールイン手順に基づき、アプリケーション側で追加対応が必要なケースがいくつかあります。
  + （状況A）手順1で、ネットワークロードバランサーがまだスケールイン対象のTiDBサーバーを不健康と判定していない場合、ロードバランサーは引き続き新規接続をこのTiDBサーバーに割り当てます。その結果、`dial tcp {host}:{port} connect: connection refused`のようなエラーが発生することがあります。
  + （状況B）手順2のケース2.1で、閉じられた接続がコネクションプールに戻され、後で再利用された場合、`begin transaction failed`のようなエラーが発生することがあります。
  + （状況C）手順2のケース2.1で、複数トランザクションを持つ接続（例：autocommitがONの場合）では、前半のトランザクションは正常に実行されても、後半で`invalid connection`のようなエラーが発生することがあります。
  + （状況D）手順2のケース2.2で、15秒を超えるトランザクションが実行中の場合、`Lost connection to MySQL server`のようなエラーが発生することがあります。

> [!TIP]
> コネクションの健全性を定期的にチェックできるよう、適切にコネクションバリデーションを利用することを推奨します。

+ 上記のケースについては、シンプルなリトライロジックで一部解決できる場合があります。
+ 次に、TiDBサーバーがスケールインされた際のアプリケーションの挙動を3つのケースで確認します：
  + コネクションリトライなしでTiDBに直接接続するアプリケーション
  + コネクションリトライありでTiDBに直接接続するアプリケーション
  + コネクションリトライなしでTiProxy経由で接続するアプリケーション

### 手順 2. TiUPコントロールマシンへの接続

+ こちらをクリック - [Terminal 1](ssh://monitor1-ssh#1) して、ハンズオン演習用の仮想マシンに接続された新しいターミナルを開いてください。

### 手順 3. クラスタトポロジの確認

+ 現在、TiDBサーバーが1台、TiKVサーバーが3台あるはずです。

   ```bash
   ~/.tiup/bin/tiup cluster display tidb-demo
   ```

   ```output
   Cluster type:       tidb
   Cluster name:       tidb-demo
   Cluster version:    v${LAB:TIDB_VERSION}
   Deploy user:        ec2-user
   SSH type:           builtin
   Dashboard URL:      http://35.91.126.63:2379/dashboard
   Grafana URL:        http://10.90.4.204:3000
   ID                 Role          Host         Ports        OS/Arch       Status  Data Dir                      Deploy Dir
   --                 ----          ----         -----        -------       ------  --------                      ----------
   10.90.4.204:9093   alertmanager  10.90.4.204  9093/9094    linux/x86_64  Up      /tidb-data/alertmanager-9093  /tidb-deploy/alertmanager-9093
   10.90.4.204:3000   grafana       10.90.4.204  3000         linux/x86_64  Up      -                             /tidb-deploy/grafana-3000
   10.90.3.89:2379    pd            10.90.3.89   2379/2380    linux/x86_64  Up|L    /tidb-data/pd-2379            /tidb-deploy/pd-2379
   10.90.4.204:9090   prometheus    10.90.4.204  9090/12020   linux/x86_64  Up      /tidb-data/prometheus-9090    /tidb-deploy/prometheus-9090
   10.90.1.74:4000    tidb          10.90.1.74   4000/10080   linux/x86_64  Up      -                             /tidb-deploy/tidb-4000
   10.90.1.103:20160  tikv          10.90.1.103  20160/20180  linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
   10.90.1.30:20160   tikv          10.90.1.30   20160/20180  linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
   10.90.1.34:20160   tikv          10.90.1.34   20160/20180  linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
   10.90.4.204:6000   tiproxy       10.90.4.204  6000/3080    linux/x86_64  Up      -                             /tidb-deploy/tiproxy-6000
   Total nodes: 9
   ```

### 手順 4. TiDBサーバーのスケールアウト

+ ここでTiDBサーバーをスケールアウトし、後でスケールインできるようにします。

  ```bash
  ~/.tiup/bin/tiup cluster scale-out tidb-demo scale-out-tidb-2.yaml \
  --user ec2-user \
  -i ~/.ssh/pe-class-key-${LAB:REGION_NAME}.pem \
  -y
  ```

  ```output
  ...
  Scaled cluster `tidb-demo` in successfully
  ```

### 手順 5. クラスタトポロジの確認

+ 2台の`TiDB`と1台の`TiProxy`があることを確認してください。

  ```bash
  ~/.tiup/bin/tiup cluster display tidb-demo
  ```

  ```output
  Cluster type:       tidb
  Cluster name:       tidb-demo
  Cluster version:    v8.5.1
  Deploy user:        ec2-user
  SSH type:           builtin
  Dashboard URL:      http://3.112.81.1:2379/dashboard
  Grafana URL:        http://10.90.4.40:3000
  ID                 Role          Host         Ports        OS/Arch       Status  Data Dir                      Deploy Dir
  --                 ----          ----         -----        -------       ------  --------                      ----------
  10.90.4.40:9093    alertmanager  10.90.4.40   9093/9094    linux/x86_64  Up      /tidb-data/alertmanager-9093  /tidb-deploy/alertmanager-9093
  10.90.4.40:3000    grafana       10.90.4.40   3000         linux/x86_64  Up      -                             /tidb-deploy/grafana-3000
  10.90.2.141:2379   pd            10.90.2.141  2379/2380    linux/x86_64  Up|L    /tidb-data/pd-2379            /tidb-deploy/pd-2379
  10.90.4.40:9090    prometheus    10.90.4.40   9090/12020   linux/x86_64  Up      /tidb-data/prometheus-9090    /tidb-deploy/prometheus-9090
  10.90.1.165:4000   tidb          10.90.1.165  4000/10080   linux/x86_64  Up      -                             /tidb-deploy/tidb-4000
  10.90.1.66:4000    tidb          10.90.1.66   4000/10080   linux/x86_64  Up      -                             /tidb-deploy/tidb-4000
  10.90.3.160:20160  tikv          10.90.3.160  20160/20180  linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.3.193:20160  tikv          10.90.3.193  20160/20180  linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.3.243:20160  tikv          10.90.3.243  20160/20180  linux/x86_64  Up      /tidb-data/tikv-20160         /tidb-deploy/tikv-20160
  10.90.4.40:6000    tiproxy       10.90.4.40   6000/3080    linux/x86_64  Up      -                             /tidb-deploy/tiproxy-6000
  ```

### 手順 6. データのインポート

  ```bash
  mysql -h ${HOST_DB1_PRIVATE_IP} -P 4000 -u root
  ```

  ```sql
  USE test
  CREATE TABLE `users` (
    `id` int,
    `name` varchar(100),
    `balance` int
  );
  INSERT INTO users VALUES (1, 'Alice', 10);
  INSERT INTO users VALUES (2, 'Bob', 10);
  INSERT INTO users VALUES (3, 'Charlie', 10);
  INSERT INTO users VALUES (4, 'Doug', 10);
  INSERT INTO users VALUES (5, 'Edison', 10);
  INSERT INTO users VALUES (6, 'Frank', 10);
  INSERT INTO users VALUES (7, 'Grace', 10);
  INSERT INTO users VALUES (8, 'Hannah', 10);
  INSERT INTO users VALUES (9, 'Iris', 10);
  INSERT INTO users VALUES (10, 'Jack', 10);
  ```

  ```sql
  SET GLOBAL autocommit = 0;
  exit
  ```
