こたつ&&みかん&&でーたべーす

DB 関連の話を中心に技術っぽい記事を書きます。

CockroachDB をスケールアウトするお話

今回は CockroachDB クラスタをスケールアウトする方法についてのお話です。

環境

今回は以下の環境 (Docker を利用したローカルの Insecure クラスタ) で検証しています。

Ubuntu : 19.10
Docker : 19.03.5
CockroachDB : 19.2.4
Container Image : cockroachdb/cockroach

クラスタ構築 (スケールアウト前)

まずは 3匹構成のクラスタを構築します。詳細は割愛しますが、Docker を利用したローカルクラスタの構築方法についてはこちらの記事に記載していますので、興味がある方はご参照ください。

今回は "ノード名の数字 (cockroach-N)" と "CockroachDB 内部で各ノードに割り当てられる ID" を一致させるために、意図的に "1匹目起動 -> クラスタ初期化 (cockroach init) -> 2匹目起動 -> 3匹目起動" の順序でコマンドを実行し、クラスタを構築しています。

$ sudo docker network create cockroach-net
$ sudo docker run -d \
--name=cockroach-1 \
--hostname=cockroach-1 \
--net=cockroach-net \
-p 26257:26257 -p 8080:8080 \
-v "${PWD}/cockroach-data/cockroach-1:/cockroach/cockroach-data" \
cockroachdb/cockroach:v19.2.4 start \
--insecure \
--join=cockroach-1,cockroach-2,cockroach-3
$ sudo docker exec -it cockroach-1 ./cockroach init --host=cockroach-1 --insecure
$ sudo docker run -d \
--name=cockroach-2 \
--hostname=cockroach-2 \
--net=cockroach-net \
-v "${PWD}/cockroach-data/cockroach-2:/cockroach/cockroach-data" \
cockroachdb/cockroach:v19.2.4 start \
--insecure \
--join=cockroach-1,cockroach-2,cockroach-3
$ sudo docker run -d \
--name=cockroach-3 \
--hostname=cockroach-3 \
--net=cockroach-net \
-v "${PWD}/cockroach-data/cockroach-3:/cockroach/cockroach-data" \
cockroachdb/cockroach:v19.2.4 start \
--insecure \
--join=cockroach-1,cockroach-2,cockroach-3

構築したクラスタはこんな感じになります。

$ sudo docker ps
CONTAINER ID        IMAGE                           COMMAND                  CREATED              STATUS              PORTS                                              NAMES
78c86cb0e977        cockroachdb/cockroach:v19.2.4   "/cockroach/cockroac…"   35 seconds ago       Up 35 seconds       8080/tcp, 26257/tcp                                cockroach-3
b324a970c3cf        cockroachdb/cockroach:v19.2.4   "/cockroach/cockroac…"   47 seconds ago       Up 46 seconds       8080/tcp, 26257/tcp                                cockroach-2
b1cdf52b5de4        cockroachdb/cockroach:v19.2.4   "/cockroach/cockroac…"   About a minute ago   Up About a minute   0.0.0.0:8080->8080/tcp, 0.0.0.0:26257->26257/tcp   cockroach-1
$ sudo docker exec -it cockroach-1 ./cockroach node status --host=cockroach-1 --insecure
  id |      address      |    sql_address    |  build  |            started_at            |            updated_at            | locality | is_available | is_live  
+----+-------------------+-------------------+---------+----------------------------------+----------------------------------+----------+--------------+---------+
   1 | cockroach-1:26257 | cockroach-1:26257 | v19.2.4 | 2020-03-03 10:17:05.067085+00:00 | 2020-03-03 10:18:44.108782+00:00 |          | true         | true     
   2 | cockroach-2:26257 | cockroach-2:26257 | v19.2.4 | 2020-03-03 10:17:39.956784+00:00 | 2020-03-03 10:18:47.508341+00:00 |          | true         | true     
   3 | cockroach-3:26257 | cockroach-3:26257 | v19.2.4 | 2020-03-03 10:17:51.714024+00:00 | 2020-03-03 10:18:45.761979+00:00 |          | true         | true     
(3 rows)

スケールアウト (3匹 -> 5匹)

それではさっそく、スケールアウトしてみます。

以下のコマンドで既存のクラスタに 4匹目を追加します。

$ sudo docker run -d \
--name=cockroach-4 \
--hostname=cockroach-4 \
--net=cockroach-net \
-v "${PWD}/cockroach-data/cockroach-4:/cockroach/cockroach-data" \
cockroachdb/cockroach:v19.2.4 start \
--insecure \
--join=cockroach-1,cockroach-2,cockroach-3

以上!

これだけ。本当にこれだけです。CockroachDB はとても簡単にスケールアウトできます。

cockroach node status コマンドで確認すると、クラスタが 4匹構成になっていることが確認できます。

$ sudo docker exec -it cockroach-1 ./cockroach node status --host=cockroach-1 --insecure
  id |      address      |    sql_address    |  build  |            started_at            |            updated_at            | locality | is_available | is_live  
+----+-------------------+-------------------+---------+----------------------------------+----------------------------------+----------+--------------+---------+
   1 | cockroach-1:26257 | cockroach-1:26257 | v19.2.4 | 2020-03-03 10:17:05.067085+00:00 | 2020-03-03 10:19:33.607906+00:00 |          | true         | true     
   2 | cockroach-2:26257 | cockroach-2:26257 | v19.2.4 | 2020-03-03 10:17:39.956784+00:00 | 2020-03-03 10:19:32.507973+00:00 |          | true         | true     
   3 | cockroach-3:26257 | cockroach-3:26257 | v19.2.4 | 2020-03-03 10:17:51.714024+00:00 | 2020-03-03 10:19:30.771919+00:00 |          | true         | true     
   4 | cockroach-4:26257 | cockroach-4:26257 | v19.2.4 | 2020-03-03 10:19:29.534717+00:00 | 2020-03-03 10:19:34.091829+00:00 |          | true         | true     
(4 rows)

更にノードを追加したい場合は、同じように新しいノード (cockroach-5) を起動します。

$ sudo docker run -d \
--name=cockroach-5 \
--hostname=cockroach-5 \
--net=cockroach-net \
-v "${PWD}/cockroach-data/cockroach-5:/cockroach/cockroach-data" \
cockroachdb/cockroach:v19.2.4 start \
--insecure \
--join=cockroach-1,cockroach-2,cockroach-3
$ sudo docker exec -it cockroach-1 ./cockroach node status --host=cockroach-1 --insecure
  id |      address      |    sql_address    |  build  |            started_at            |            updated_at            | locality | is_available | is_live  
+----+-------------------+-------------------+---------+----------------------------------+----------------------------------+----------+--------------+---------+
   1 | cockroach-1:26257 | cockroach-1:26257 | v19.2.4 | 2020-03-03 10:17:05.067085+00:00 | 2020-03-03 10:20:09.607054+00:00 |          | true         | true     
   2 | cockroach-2:26257 | cockroach-2:26257 | v19.2.4 | 2020-03-03 10:17:39.956784+00:00 | 2020-03-03 10:20:08.508058+00:00 |          | true         | true     
   3 | cockroach-3:26257 | cockroach-3:26257 | v19.2.4 | 2020-03-03 10:17:51.714024+00:00 | 2020-03-03 10:20:06.773181+00:00 |          | true         | true     
   4 | cockroach-4:26257 | cockroach-4:26257 | v19.2.4 | 2020-03-03 10:19:29.534717+00:00 | 2020-03-03 10:20:10.085793+00:00 |          | true         | true     
   5 | cockroach-5:26257 | cockroach-5:26257 | v19.2.4 | 2020-03-03 10:20:07.715985+00:00 | 2020-03-03 10:20:07.78451+00:00  |          | true         | true     
(5 rows)

スケールアウト (既存クラスタへのノード追加) で重要なのが --join オプションです。この --join オプションに、既存のクラスタ内で動作しているノード (接続先のノード) を指定します。

最初に構築した 3匹構成のクラスタ内の各ノードは、<hostname:26275> で他のノードからの通信及びクライアントからの通信を LISTEN しています (port 番号 "26257" はデフォルト値)。

なので、後から追加するノード (cockroach-4, cockroach-5) を起動する際の --join オプションに <hostname:[port]> を指定して起動する (明示的に port を指定しなかった場合はデフォルト値の "26257" が指定される) と、--join に指定したノードに対して接続を試行し、既存のクラスタに起動したノード (新しいノード) が追加されます。

また、--join オプションにはカンマ区切りで複数の値を指定することができます。ドキュメントを見る限り、既存クラスタ内の 3 ~ 5 台のノードを指定すると良さそうです。

この --join オプションを指定してクラスタに追加するノードを起動すれば、クラスタ接続後のデータのリバランシングやその他の処理を自動でいい感じにやってくれます。

スケールアウト時の Automatic Rebalancing

CockroachDB は、ユーザが作成したテーブルのデータを "Range" と呼ばれる単位のデータに分割し、各 Range の Replica (コピー) を 3つ保持しています (デフォルト設定の場合)。また、クラスタメタデータ等が含まれているテーブル (System Range と呼ばれている) は、デフォルトで 5つの Replica を保持しています (ただし、3匹構成のクラスタの場合は System Range の Replica も 3つ)。CockroachDB は、この Replica を各ノードにいい感じに分散配置することで、耐障害性や性能の向上を図っています。

CockroachDB をスケールアウトすると、このいい感じ分散配置されている Range (Replica) をリバランシング (新しいノードにも分配) してくれるので、その動作を検証してみます。

テストデータ作成

まずは、root ユーザで DB に接続し、テスト用のテーブル (t0, t1, t2) の作成と適当なデータの INSERT を実施します。

$ sudo docker exec -it cockroach-1 ./cockroach sql --host=cockroach-1 --insecure
#
# Welcome to the CockroachDB SQL shell.
# All statements must be terminated by a semicolon.
# To exit, type: \q.
#
# Server version: CockroachDB CCL v19.2.4 (x86_64-unknown-linux-gnu, built 2020/02/06 21:55:19, go1.12.12) (same version as client)
# Cluster ID: a75fe40c-37a4-4e13-bde1-800b30e5995a
#
# Enter \? for a brief introduction.
#
root@cockroach-1:26257/defaultdb>
root@cockroach-1:26257/defaultdb> CREATE TABLE t0 (id INT PRIMARY KEY, c1 INT, c2 INT, c3 INT);
CREATE TABLE

Time: 75.726718ms

root@cockroach-1:26257/defaultdb>
root@cockroach-1:26257/defaultdb> CREATE TABLE t1 (id INT PRIMARY KEY, c1 INT, c2 INT, c3 INT);
CREATE TABLE

Time: 59.199631ms

root@cockroach-1:26257/defaultdb>
root@cockroach-1:26257/defaultdb> CREATE TABLE t2 (id INT PRIMARY KEY, c1 INT, c2 INT, c3 INT);
CREATE TABLE

Time: 72.846164ms

root@cockroach-1:26257/defaultdb>
root@cockroach-1:26257/defaultdb> INSERT INTO t0 SELECT num, (random() * 100)::int, (random() * 100)::int, (random() * 100)::int FROM generate_series(1,1500000) AS num;
INSERT 1500000

Time: 21.588598891s

root@cockroach-1:26257/defaultdb>
root@cockroach-1:26257/defaultdb> INSERT INTO t1 SELECT num, (random() * 100)::int, (random() * 100)::int, (random() * 100)::int FROM generate_series(1,1500000) AS num;
INSERT 1500000

Time: 30.683205437s

root@cockroach-1:26257/defaultdb>
root@cockroach-1:26257/defaultdb> INSERT INTO t2 SELECT num, (random() * 100)::int, (random() * 100)::int, (random() * 100)::int FROM generate_series(1,1500000) AS num;
INSERT 1500000

Time: 43.993299853s

root@cockroach-1:26257/defaultdb>

テーブル作成後、各テーブル (t0, t1, t2) の Range (Replica) がどのノードに配置されているかを確認します。

root@cockroach-1:26257/defaultdb> SELECT table_name, range_id, replicas FROM crdb_internal.ranges WHERE database_name = 'defaultdb';
  table_name | range_id | replicas  
+------------+----------+----------+
  t0         |       25 | {1,2,5}   
  t0         |       38 | {1,2,3}   
  t0         |       39 | {1,2,3}   
  t1         |       35 | {1,2,3}   
  t1         |       41 | {1,2,3}   
  t1         |       55 | {3,4,5}   
  t2         |       36 | {1,4,5}   
  t2         |       66 | {1,4,5}   
  t2         |       68 | {1,4,5}   
(9 rows)

Time: 5.774765ms

crdb_internal.ranges.replicas の数字は各ノードの ID です。ノード ID は cockroach node status コマンドで確認できます。今回は "ノード名の数字 (cockroach-N)" と "CockroachDB 内部で各ノードに割り当てられる ID" が一致する (cockroach-1 -> ID 1, cockroach-2 -> ID 2, cockroach-3 -> ID 3, ...) ようにクラスタを構築しているので、上記結果の場合、各テーブル (t0, t1, t2) のデータは 3つの Range に分割されており、各 Range の Replica は以下のように分散されて配置されています。

[t0 のデータ]

Range ID: 25 -> cockroach-1, cockroach-2, cockroach-5
Range ID: 38 -> cockroach-1, cockroach-2, cockroach-3
Range ID: 39 -> cockroach-1, cockroach-2, cockroach-3

[t1 のデータ]

Range ID: 35 -> cockroach-1, cockroach-2, cockroach-3
Range ID: 41 -> cockroach-1, cockroach-2, cockroach-3
Range ID: 55 -> cockroach-3, cockroach-4, cockroach-5

[t2 のデータ]

Range ID: 36 -> cockroach-1, cockroach-4, cockroach-5
Range ID: 66 -> cockroach-1, cockroach-4, cockroach-5
Range ID: 68 -> cockroach-1, cockroach-4, cockroach-5

スケールアウト (5匹 -> 7匹)

それでは、再度 CockroachDB クラスタをスケールアウトしてみます。

先程と同じように以下のコマンドでノード (6匹目, 7匹目) を追加します。

$ sudo docker run -d \
--name=cockroach-6 \
--hostname=cockroach-6 \
--net=cockroach-net \
-v "${PWD}/cockroach-data/cockroach-6:/cockroach/cockroach-data" \
cockroachdb/cockroach:v19.2.4 start \
--insecure \
--join=cockroach-1,cockroach-2,cockroach-3
$ sudo docker run -d \
--name=cockroach-7 \
--hostname=cockroach-7 \
--net=cockroach-net \
-v "${PWD}/cockroach-data/cockroach-7:/cockroach/cockroach-data" \
cockroachdb/cockroach:v19.2.4 start \
--insecure \
--join=cockroach-1,cockroach-2,cockroach-3

ノード追加後、cockroach node status コマンドでクラスタが 7匹構成になっていることを確認します。

$ sudo docker exec -it cockroach-1 ./cockroach node status --host=cockroach-1 --insecure
  id |      address      |    sql_address    |  build  |            started_at            |            updated_at            | locality | is_available | is_live  
+----+-------------------+-------------------+---------+----------------------------------+----------------------------------+----------+--------------+---------+
   1 | cockroach-1:26257 | cockroach-1:26257 | v19.2.4 | 2020-03-03 10:17:05.067085+00:00 | 2020-03-03 10:36:30.607204+00:00 |          | true         | true     
   2 | cockroach-2:26257 | cockroach-2:26257 | v19.2.4 | 2020-03-03 10:17:39.956784+00:00 | 2020-03-03 10:36:29.534801+00:00 |          | true         | true     
   3 | cockroach-3:26257 | cockroach-3:26257 | v19.2.4 | 2020-03-03 10:17:51.714024+00:00 | 2020-03-03 10:36:32.258867+00:00 |          | true         | true     
   4 | cockroach-4:26257 | cockroach-4:26257 | v19.2.4 | 2020-03-03 10:19:29.534717+00:00 | 2020-03-03 10:36:31.079381+00:00 |          | true         | true     
   5 | cockroach-5:26257 | cockroach-5:26257 | v19.2.4 | 2020-03-03 10:20:07.715985+00:00 | 2020-03-03 10:36:28.781256+00:00 |          | true         | true     
   6 | cockroach-6:26257 | cockroach-6:26257 | v19.2.4 | 2020-03-03 10:34:07.000889+00:00 | 2020-03-03 10:36:31.059838+00:00 |          | true         | true     
   7 | cockroach-7:26257 | cockroach-7:26257 | v19.2.4 | 2020-03-03 10:36:30.337069+00:00 | 2020-03-03 10:36:30.388762+00:00 |          | true         | true     
(7 rows)

スケールアウト完了 (7匹構成になっていること) を確認後、改めて先程作成した各テーブルの Rnage (Replica) がどのノードに配置されているかを確認します。(スケールアウト後のリバランシング処理には少し時間がかかるので、数分待ってから確認するとよいかもしれません)

root@cockroach-1:26257/defaultdb> SELECT table_name, range_id, replicas FROM crdb_internal.ranges WHERE database_name = 'defaultdb';
  table_name | range_id | replicas  
+------------+----------+----------+
  t0         |       25 | {1,2,5}   
  t0         |       38 | {1,3,7}   
  t0         |       39 | {1,2,7}   
  t1         |       35 | {1,2,6}   
  t1         |       41 | {2,3,6}   
  t1         |       55 | {5,6,7}   
  t2         |       36 | {1,4,7}   
  t2         |       66 | {1,4,6}   
  t2         |       68 | {4,5,6}   
(9 rows)

Time: 5.979204ms

すると、先程とは Range (Replica) の配置が変わっていることが確認できます。上記結果の場合、各 Range の Replica は以下のように配置されています。スケールアウト前と比べて、追加した cockroach-6 や cockroach-7 にも Replica が分散されていることが確認できます。

[t0 のデータ]

Range ID: 25 -> cockroach-1, cockroach-2, cockroach-5
Range ID: 38 -> cockroach-1, cockroach-3, cockroach-7
Range ID: 39 -> cockroach-1, cockroach-2, cockroach-7

[t1 のデータ]

Range ID: 35 -> cockroach-1, cockroach-2, cockroach-6
Range ID: 41 -> cockroach-2, cockroach-3, cockroach-6
Range ID: 55 -> cockroach-5, cockroach-6, cockroach-7

[t2 のデータ]

Range ID: 36 -> cockroach-1, cockroach-4, cockroach-7
Range ID: 66 -> cockroach-1, cockroach-4, cockroach-6
Range ID: 68 -> cockroach-4, cockroach-5, cockroach-6

また、Admin UI (Web UI) の Metrics タブ (http://localhost:8080/#/metrics/overview/cluster) を開くと、"Replicas per Node" の項目で各ノードが保持している Replica 数の推移をグラフで見ることができます。

今回のようにスケールアウトすると、以下のように各ノードが保持している Replica 数が変化していること (自動でリバランシングしていること) が確認できます。

[Replicas per Node] 00_admin_ui_replicas_per_node

また、表示されているグラフをマウスオーバーすると、各ノードが保持している Replica 数が表示されます。各タイミング (5匹構成, 6匹構成, 7匹構成) における各ノードが保持している Replica 数は以下のようになっています。

[5匹構成 (スケールアウト前)] 01_before_scale_out

[6匹目追加後] 02_add_6th_node

[7匹目追加後] 03_add_7th_node

スケールアウト前 (5匹構成) は各ノードが 28 ~ 31 の Replica を保持していますが、6匹構成時 (スケールアウト後) は各ノードが 23 ~ 25 の Replica を、7匹構成時は各ノードが 19 ~ 22 の Replica を保持しており、1ノードあたりの Replica 数が減少していること (クラスタ全体で Replica をある程度均等にリバランシングしていること) も確認できます。

ローカルクラスタの削除

検証後、不要な場合は各リソースを削除しておきます。

$ sudo docker rm $(sudo docker kill cockroach-1 cockroach-2 cockroach-3 cockroach-4 cockroach-5 cockroach-6 cockroach-7)
$ sudo docker network rm cockroach-net
$ sudo rm -rf ${PWD}/cockroach-data/

まとめ

CockroachDB はとてもシンプルな手順でスケールアウトすることができます。

ただし、自動で (裏で勝手に) データのリバランシング処理が走るので、スケールアウト実施時におけるクラスタ内のネットワーク帯域や各ノードのディスク I/O には注意が必要な雰囲気 (一時的に負荷が上がる可能性がありそう) です。