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]
また、表示されているグラフをマウスオーバーすると、各ノードが保持している Replica 数が表示されます。各タイミング (5匹構成, 6匹構成, 7匹構成) における各ノードが保持している Replica 数は以下のようになっています。
[5匹構成 (スケールアウト前)]
[6匹目追加後]
[7匹目追加後]
スケールアウト前 (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 には注意が必要な雰囲気 (一時的に負荷が上がる可能性がありそう) です。