前回に引き続き、Vitessの各機能について検証を行いたいと思います。
前回はminikube上で検証を行いましたが、今回はEKSを使用してVitessを構築してみました。
EKSの構築手順は一般的なものですのでここで詳しくは触れません。
もし試される場合は、以下のようなガイドを参照の上ご準備ください。
https://aws.amazon.com/jp/getting-started/projects/deploy-kubernetes-app-amazon-eks/
検証項目
前回の記事の最後にいくつかの機能をご紹介しました。
今回は、その中から以下の項目について動作を確認してみます。
- Horizontal sharding(水平シャーディング)
Vitessコンポーネントについてのおさらい
ここでVitessがどのようなパーツから構成されているか、簡単な説明を記載します。
正確にはConceptsを確認してください。
コンポーネント名 | 説明 |
---|---|
Tablet (vttablet) |
mysqldを含むいくつかのPod。1:2でレプリケーションされたmysqld、バックアップ、リストア、予約用Pod等を含む |
VTGate | 接続をTabletへルーティングするためのプロキシ。シャーディング、HA、パフォーマンス等を考慮してルーティングを行う。 |
vtworker | 長時間実行されるプロセス(リシャーディング操作等)をホストするPod。 |
vtctld | Cliツールの処理を受け付けるHTTPサーバ |
Topology | 1つのVitessを構成するすべてのコンポーネントをまとめる論理的な単位 |
Cell | 1つの完全に動作するVitess環境をまとめる論理的な単位。データセンターごとにCellを配置する事ができます。 |
VSchema | 複数のKeyspaceを含む論理的なスキーマ |
Keyspace | シャーディングされたデータを格納するデータベース。Tabletは異なるKeyspaceを持つ事ができます。 |
引用: https://jp.techcrunch.com/2019/05/24/2019-05-23-planetscale-vitess/
Vitessのデータ分散方式について
Vitessは、データ分散方式に2つの戦略を使用する事ができます。
一つはVirtical Split(垂直分割)、もう一つはHorizontal sharding(水平シャーディング)です。
Virtical Split(垂直分割)
https://vitess.io/docs/user-guides/vertical-split/
テーブルを異なるTabletが保持するKeyspaceに分散させることで、書き込みや読み込みの負荷を平均化する方法です。
テーブル単位で担当を分けるというシンプルな考え方で、R/Wを分散させることができます。
一方で、単一のテーブルに対する負荷が高いようなケースでは、1つのTabletに負荷が集中してしまうため、有効な方法では無い場合があります。
前回の記事で検証をしていますので、よろしければご確認ください。
Horizontal sharding(水平シャーディング)
https://vitess.io/docs/user-guides/horizontal-sharding/
テーブルのキー値を基準にして、同じKeyspace内で異なるTabletにレコードを分散させる方式です。
水平シャーディングを行うためには、以下の操作が必要になります。
- VSchema内のKeyspace定義にsharding設定を追加
- Vitess Sequenceの追加
- Vindexの追加
Vitess Sequenceとは
Sequence(シーケンス)とは、ある数値から開始し、一定数増分する数値を連続して生成するためのオブジェクトです。
例えば nextval
のような操作を用いて次の数値を取得することができ、その値を使ってあるカラムのデフォルト値に使う事があります。
MySQLにはシーケンスはありませんが、ユニークキーが定義されている列にauto_increment属性を付与することで、その列にシーケンスを使った場合と同じようなデータを自動的に更新する事が可能です。
しかし、Vitessで水平シャーディングを行った場合、論理的な1つのテーブル内のレコードは、物理的には異なるテーブル(シャード)に格納されています。
ですので、auto_incrementを使用する事ができません。
そのため、VitessではKeyspaceごとにグローバルな独自のシーケンスを使用することで同様の処理をサポートしています。
このシーケンスの実態は、シャードされていない単一のテーブルです。
VSchema定義のauto_incrementに、このシーケンスと関連付ける列を指定することで、MySQLのauto_incrementと同様の動作を実現することができます。
水平シャーディングにこのSequenceが必須というわけではありませんが、平均的にレコードの分散を行うためのキーとしてauto_increment列を指定することは一般的に効率が良いため、合わせて良く使用されます。
VIndexとは
前項の説明の通り、シャーディングされたテーブルは物理的には個別のテーブルです。
当然ですが、MySQLのIndexは個別のテーブルに存在します。
しかし、水平シャーディングを行うにあたっては、KeyspaceをまたがるIndexが必要となります。
このグローバルなIndexをVIndexと呼びます。
ここでは簡単にVIndexについてご説明します。
詳しくは、Vindexesをご確認ください。
プライマリVIndex
入力値(列値)に対して、一意の Keyspace ID を生成するためのIndexです。
一般的にシャードキーと呼ばれ、Vitessの水平シャーティングに必要なオブジェクトです。
Keyspace IDの生成方法には、事前に用意された方式(hash等)を使用することも、ユーザ定義の関数やルックアップテーブルを使用して実装することも可能です。
セカンダリVIndex
プライマリVIndexを使用しないクエリの実行時にVTgateがシャードを判断するためのVIndexです。
VIndexを使用できないクエリはすべてのシャードに実行されることになります。
Vitessの初期化
では前提の説明も終わりましたので早速始めましょう。
ドキュメントに従い、vitessのリポジトリクローン, kubectl, Helm2, etcd-operatorのインストールを完了します。
水平シャーディングのチュートリアルに沿って検証を行います。
水平シャーディングについてのマニフェストは、301_customer_sharded.yaml
以降の項番ですが、前提として以下の記載がありますので、駆け足で設定を進めます。
This guide follows on from Vertical Split and Get Started with a Local deployment. It assumes that several scripts have been executed, and you have a running Vitess cluster.
まずはVittesを基本的な構成で起動します。
1 2 |
$ cd vitess/example/helm $ helm install ../../helm/vitess -f 101_initial_cluster.yaml |
EKS上のポートをlocalhostに転送するために接続用に3306ポートのport-forwardを有効化しました。
1 2 |
$ VTGATE=$(kubectl get pods | grep -io "vtgate[a-zA-Z0-9-]*") $ kubectl port-forward $VTGATE 3306:3306 |
以下のコマンドを実行すると、準備は完了です。
なお、各upgradeは操作をkubernatesに伝えた時点で制御が返りますが、実際に行われる更新についてはkubenates上である程度時間を要します。
前の処理完了前に次の処理を実行してしまわないようにご注意ください。
1 2 3 4 5 6 7 8 |
$ release=$(helm ls -q) $ mysql -uroot -h127.0.0.1 < ../common/insert_commerce_data.sql $ helm upgrade $release ../../helm/vitess/ -f 201_customer_keyspace.yaml $ helm upgrade $release ../../helm/vitess/ -f 202_customer_tablets.yaml $ helm upgrade $release ../../helm/vitess/ -f 203_vertical_split.yaml $ helm upgrade $release ../../helm/vitess/ -f 204_vertical_migrate_replicas.yaml $ helm upgrade $release ../../helm/vitess/ -f 205_vertical_migrate_master.yaml $ helm upgrade $release ../../helm/vitess/ -f 206_clean_commerce.yaml |
準備が正常に完了した場合、以下の状態となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
$ mysql -uroot -h127.0.0.1 mysql> show vitess_shards; +------------+ | Shards | +------------+ | commerce/0 | | customer/0 | +------------+ mysql> show vitess_tablets; +-------+----------+-------+------------+---------+------------------+-------------------------------------+ | Cell | Keyspace | Shard | TabletType | State | Alias | Hostname | +-------+----------+-------+------------+---------+------------------+-------------------------------------+ | zone1 | commerce | 0 | MASTER | SERVING | zone1-1564760600 | zone1-commerce-0-replica-0.vttablet | | zone1 | commerce | 0 | REPLICA | SERVING | zone1-1564760601 | zone1-commerce-0-replica-1.vttablet | | zone1 | commerce | 0 | RDONLY | SERVING | zone1-0794219800 | zone1-commerce-0-rdonly-0.vttablet | | zone1 | customer | 0 | MASTER | SERVING | zone1-0528462200 | zone1-customer-0-replica-0.vttablet | | zone1 | customer | 0 | REPLICA | SERVING | zone1-0528462201 | zone1-customer-0-replica-1.vttablet | | zone1 | customer | 0 | RDONLY | SERVING | zone1-1240617300 | zone1-customer-0-rdonly-0.vttablet | +-------+----------+-------+------------+---------+------------------+-------------------------------------+ mysql> use commerce/0; mysql> show tables; +--------------------+ | Tables_in_commerce | +--------------------+ | product | +--------------------+ mysql> use customer/0; mysql> show tables; +--------------------+ | Tables_in_customer | +--------------------+ | corder | | customer | +--------------------+ |
シーケンス、VIndexの作成とシャーディングの有効化
シーケンス、VIndexの作成とキースペース(customer)のシャーディングの有効化のために example/helm/301_customer_sharded.yaml
を新たに適用します。
1 |
$ helm upgrade $release ../../helm/vitess/ -f 301_customer_sharded.yaml |
schema > seqの箇所に指定されているDDLは実際にcommerceスキーマにシーケンスを作成するために使用されます。
vschema > seqの箇所に指定されているJSONは、vtgateにシーケンスの存在を知らせるために定義されています。
シーケンスを作成するキースペースはシャーディングされていない必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
13 keyspaces: 14 - name: "commerce" : 24 schema: 25 seq: |- 26 create table customer_seq(id int, next_id bigint, cache bigint, primary key(id)) comment 'vitess_sequence'; 27 insert into customer_seq(id, next_id, cache) values(0, 1000, 100); 28 create table order_seq(id int, next_id bigint, cache bigint, primary key(id)) comment 'vitess_sequence'; 29 insert into order_seq(id, next_id, cache) values(0, 1000, 100); 30 vschema: 31 seq: |- 32 { 33 "tables": { 34 "customer_seq": { 35 "type": "sequence" 36 }, 37 "order_seq": { 38 "type": "sequence" 39 }, 40 "product": {} 41 } 42 } |
customerスキーマでは実際に前述のシーケンスを使用するための関連付けが行われています。
customerスキーマに"sharded": true
をつけることで水平シャーディングを有効化しています。
また、同時にcustomer/0.customer.costomer_id、customer/0.corder.customer_idに"type": "hash"
のVIndexを付与しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
43 - name: "customer" 44 shards: 45 - name: "0" : 57 vschema: 58 sharded: |- 59 { 60 "sharded": true, 61 "vindexes": { 62 "hash": { 63 "type": "hash" 64 } 65 }, 66 "tables": { 67 "customer": { 68 "column_vindexes": [ 69 { 70 "column": "customer_id", 71 "name": "hash" 72 } 73 ], 74 "auto_increment": { 75 "column": "customer_id", 76 "sequence": "customer_seq" 77 } 78 }, 79 "corder": { 80 "column_vindexes": [ 81 { 82 "column": "customer_id", 83 "name": "hash" 84 } 85 ], 86 "auto_increment": { 87 "column": "order_id", 88 "sequence": "order_seq" 89 } 90 } 91 } 92 } |
VSchemaによりシーケンス、及びtype: hashのVIndexを使用したシャーディングの準備ができました。
シャードの追加
実際にcustomerキースペースをシャード分割します。
1 |
$ helm upgrade $release ../../helm/vitess/ -f 302_new_shards.yaml |
以下では、
1. shard 0, -80, 80- を追加
2. 各シャードに対応するtabletを追加
という事をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
24 - name: "customer" 25 shards: 26 - name: "0" 27 tablets: 28 - type: "replica" 29 vttablet: 30 replicas: 2 31 - type: "rdonly" 32 vttablet: 33 replicas: 1 34 - name: "-80" 35 tablets: 36 - type: "replica" 37 vttablet: 38 replicas: 2 39 - type: "rdonly" 40 vttablet: 41 replicas: 1 42 copySchema: 43 source: "customer/0" 44 - name: "80-" 45 tablets: 46 - type: "replica" 47 vttablet: 48 replicas: 2 49 - type: "rdonly" 50 vttablet: 51 replicas: 1 52 copySchema: 53 source: "customer/0" |
各シャードのnameは値の範囲を意味します。
前述のtype: hashのVIndexは、入力値に対して16進数で表される8byteの数値を返します。
値の範囲は0x0000000000000000-0xFFFFFFFFFFFFFFFFです。
nameには、0xをのぞいて-で区切った範囲を指定します。
0x8000000000000000(-80, 80-)は中央値となりますので、2つ目のシャードと3つ目のシャードに半々にデータが格納されます。
copySchemaは、シャーディングするデータをコピーする際の定義です。
sourceにcustomer/0を指定しています。
適用後にtabletが作成されているはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
mysql> show vitess_tablets;; +-------+----------+-------+------------+---------+------------------+----------------------------------------+ | Cell | Keyspace | Shard | TabletType | State | Alias | Hostname | +-------+----------+-------+------------+---------+------------------+----------------------------------------+ | zone1 | commerce | 0 | MASTER | SERVING | zone1-1564760600 | zone1-commerce-0-replica-0.vttablet | | zone1 | commerce | 0 | REPLICA | SERVING | zone1-1564760601 | zone1-commerce-0-replica-1.vttablet | | zone1 | commerce | 0 | RDONLY | SERVING | zone1-0794219800 | zone1-commerce-0-rdonly-0.vttablet | | zone1 | customer | -80 | MASTER | SERVING | zone1-1581380800 | zone1-customer-x-80-replica-0.vttablet | | zone1 | customer | -80 | REPLICA | SERVING | zone1-1581380801 | zone1-customer-x-80-replica-1.vttablet | | zone1 | customer | -80 | RDONLY | SERVING | zone1-1223715000 | zone1-customer-x-80-rdonly-0.vttablet | | zone1 | customer | 0 | MASTER | SERVING | zone1-0528462200 | zone1-customer-0-replica-0.vttablet | | zone1 | customer | 0 | REPLICA | SERVING | zone1-0528462201 | zone1-customer-0-replica-1.vttablet | | zone1 | customer | 0 | RDONLY | SERVING | zone1-1240617300 | zone1-customer-0-rdonly-0.vttablet | | zone1 | customer | 80- | MASTER | SERVING | zone1-0822947600 | zone1-customer-80-x-replica-0.vttablet | | zone1 | customer | 80- | REPLICA | SERVING | zone1-0822947601 | zone1-customer-80-x-replica-1.vttablet | | zone1 | customer | 80- | RDONLY | SERVING | zone1-1026037800 | zone1-customer-80-x-rdonly-0.vttablet | +-------+----------+-------+------------+---------+------------------+----------------------------------------+ |
まだ定義をしたのみですので、データはシャーディングされたキースペースに格納されていません。
データのクローン
データを各シャードにコピーするために以下のマニフェストを適用します。
1 |
$ helm upgrade $release ../../helm/vitess/ -f 303_horizontal_split.yaml |
適用すると、SplitCloneが実行され、シャード内データが生成されます。
1 2 3 4 5 |
51 jobs: 52 - name: "horizontal-split" 53 kind: "vtworker" 54 cell: "zone1" 55 command: "SplitClone -min_healthy_rdonly_tablets=1 customer/0" |
具体的には、
1. commerce/0のrdonly podからシャードされたデータをコピー
2. rdonly podのレプリケーションを停止し、最終同期
3. commerce/0の更新トラフィックを各シャードにレプリケートするプロセスを起動し、同期開始
という手順を踏みます。
ここまででシャーディング自体は行う事ができましたが、まだvtgateはシャード用tabletにトラフィックをルーティングしませんので、データを確認することはできません。
トラフィックのカットオーバ
vtgateからシャードを担当するTabletへのトラフィックルーティングを有効化するために、カットオーバを行います。
カットオーバは、以下のJOBで行われます。
1 2 3 4 |
51 jobs: 52 - name: "mst1" 53 kind: "vtctlclient" 54 command: "MigrateServedTypes customer/0 rdonly" |
rdonlyの部分は、replica, masterなど対象のタブレット名を指定します。
早速実行します。
1 |
$ helm upgrade $release ../../helm/vitess/ -f 304_migrate_replicas.yaml |
カットオーバが完了すると、rdonly, replicaタブレットへの接続が可能となります。
1 2 3 4 5 6 7 8 |
mysql> use customer/-80@replica mysql> show tables; +--------------------+ | Tables_in_customer | +--------------------+ | corder | | customer | +--------------------+ |
特にデータに問題がなく、継続して更新されているようであればmasterをカットオーバします。
1 |
$ helm upgrade $release ../../helm/vitess/ -f 305_migrate_master.yaml |
無事シャーディングされたデータが確認できました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
mysql> use customer/-80@master mysql> show tables; +--------------------+ | Tables_in_customer | +--------------------+ | corder | | customer | +--------------------+ mysql> select * from corder; +----------+-------------+----------+-------+ | order_id | customer_id | sku | price | +----------+-------------+----------+-------+ | 1 | 1 | SKU-1001 | 100 | | 2 | 2 | SKU-1002 | 30 | | 3 | 3 | SKU-1002 | 30 | | 5 | 5 | SKU-1002 | 30 | +----------+-------------+----------+-------+ mysql> select * from customer; +-------------+--------------------+ | customer_id | email | +-------------+--------------------+ | 1 | alice@domain.com | | 2 | bob@domain.com | | 3 | charlie@domain.com | | 5 | eve@domain.com | +-------------+--------------------+ mysql> use customer/80-@master mysql> select * from corder; +----------+-------------+----------+-------+ | order_id | customer_id | sku | price | +----------+-------------+----------+-------+ | 4 | 4 | SKU-1002 | 30 | +----------+-------------+----------+-------+ mysql> select * from customer; +-------------+----------------+ | customer_id | email | +-------------+----------------+ | 4 | dan@domain.com | +-------------+----------------+ |
クリーンアップ
最後にソーススキーマとなったcustomer/0を削除します。
以下のマニフェストは、今までのKeyspace定義からcustomer/0を削除した内容です。
これを実行すると、customer/0に関連したTabletは削除されます。
show vitess_tablets
コマンド等で確認してみてください。
1 |
$ helm upgrade $release ../../helm/vitess/ -f 306_down_shard_0.yaml |
Tabletは削除されましたが、関連メタデータが残っているため以下で削除します。
1 |
$ helm upgrade $release ../../helm/vitess/ -f 307_delete_shard_0.yaml |
具体的なJOBは以下です。
1 2 3 4 5 |
43 jobs: 44 - name: "delete-shard0" 45 kind: "vtctlclient" 46 command: "DeleteShard -recursive customer/0" 47 |
まとめ
いくつものPodが連携しあってシャーディングを実現しているため、パーティショニングのような機能よりも手間はかかるという印象ですが、
KubernatesとHelmを使って準備された手順で宣言的に変更を適用していく方式は作業者にとっては安心材料になり得るかもしれませんね。
Kubernates力の低い私でも最後までやりきる事ができましたので、意外と敷居は低いのではないかと感じました。
RDBMSはWrite Scaleしないという状況に一石を投じるようなソリューションで、今後に大いに期待できます!
オンプレミスのKubernatesとなると若干敷居が高いですが、最近ではパブリッククラウドでもマネージドなKubernatesが簡単に使用できる時代になりましたので、お気軽にお試し頂ければ嬉しいです。
Enjoy the Vitess!