超実践 cloud spanner 設計講座

26
超実践 Cloud Spanner 設計講座 知ってることを全て紹介します! Proprietary Samir Hammoudi aka サミール クラウドカスタマエンジニア JULY 21, 2017

Upload: samir-hammoudi

Post on 21-Jan-2018

6.174 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: 超実践 Cloud Spanner 設計講座

超実践 Cloud Spanner 設計講座知ってることを全て紹介します!

Proprietary

Samir Hammoudi aka サミールクラウドカスタマエンジニア

JULY 21, 2017

Page 2: 超実践 Cloud Spanner 設計講座

Cloud Spanner とは?

Google のマネージド・スケーラブル・リレーショナルデータベース・サービス

完全マネージドのグローバルスケールで DB サービス1

2

3

4

ゾーン間・リージョン間の自動 synchronous レプリケーション

スキーマ、ACID トランザクション、SQL

Google内部では、既に5年以上の運用経験(AdWords, Google Play…)

Page 3: 超実践 Cloud Spanner 設計講座

注意事項:Cloud Spanner ≠ MySQL

Cloud Spanner は MySQL の単なる置き換えではない

● スキーマは似てるが、分散データベースのため、MySQLと違う可能性がある

● SQL の SELECT系とDDLは対応済み、DML も対応予定

→ 現時点では Mutation オブジェクトを利用して INSERT/UPDATE/DELETE を行う

● Auto-increment がない(Spannerではアンチパターンだから)

● MySQL 互換ではない(Spannerのクライアントライブラリを提供)

● パフォーマンスのプロファイルが違う(Scale-up DB vs Scale-out DB)→ 高負荷をかけても Cloud Spanner のレイテンシは安定(Quizletの事例をご参考ください)

● Cloud Spanner では PK の選択がクリティカル ←これ何回言っても足りないので赤にしました!

● FK が存在しない(Spanner では Interleave が FK 相当)

MySQL とは似てるところも色々あるが、Cloud Spanner は分散データベースだということを忘れないでね!!

Page 4: 超実践 Cloud Spanner 設計講座

MySQL ではないが...

特に大規模アクセスのあるコンテンツやDB運用に苦労しているサービスに最適!

ノードを追加するだけでオートシャードされ、理論上無限にスケールしつつ、

レイテンシーがより安定 :)運用も楽 ^_^

Page 5: 超実践 Cloud Spanner 設計講座

今日のおさらいリスト

1. PK の選択が重要 ←これが1番重要!!

2. なぜテーブルをインターリーブするか?

3. インデックスとインターリーブについて

4. Channels と Sessions5. ノード追加・削除の際、リシャードがなぜ1〜2sでできるのか?

6. エラーについて

7. 容量に関する上限 8. テーブルが分割(Split)されるロジック

9. 負荷試験は十分長く実行する

10. 負荷試験間はデータベースをDropする

11. TPSの数え方

12. ノード数を縮小するについて

13. グラフの ”Total storage” の意味って?

14. Query Plan Cache ←これも重要!!

Page 6: 超実践 Cloud Spanner 設計講座

PK の選択が重要

● シーケンシャルな PK を使わないこと → hotspots が発生する

○ auto-increment PK を使わない (even though it doesn’t exist in Cloud Spanner!)○ 時系列の PK を使わない

● MySQL の auto-increment を使用しているテーブルを Spanner にインポートする場合は:

○ 新しい PK を作り、乱数をオススメします (e.g. UUID).○ 元の PK から 計算する shard_id を作る (e.g. shard_id = hash(auto-inc_id)).○ シーケンシャルな PK を使うが、DB に格納する前やクエリーする時はビットを逆にする

● UUID が一番おすすめとしている PK です。

これ重要!

Page 7: 超実践 Cloud Spanner 設計講座

PKを選択する時の注意点 - Hotspot (1/2)

● Hotspot とは、データが Spanner サーバー間にちゃんと分散されない現象

○ 1つの Spanner サーバーに書き込みが集中して、パフォーマンスが減少

○ 同じサーバーを利用するサービスにインパクトも可能

● 現象の理由

○ 新しく書き込まれるレコードはテーブルの最後に追加されて、同じ Spanner サーバーに格納

される(時系列のPK)

CREATE TABLE Users ( LastAccessTimestamp INT64 NOT NULL, UserId INT64 NOT NULL, ...) PRIMARY KEY (LastAccessTimestamp, UserId);

Page 8: 超実践 Cloud Spanner 設計講座

PKを選択する時の注意点 - Hotspot (2/2)

● Hotspot の対策

○ PK の順序を交換

○ 新しく書き込まれるレコードは、テーブルの最後に追加されず、 Spanner サーバー間にちゃんと分散される

CREATE TABLE Users ( UserId INT64 NOT NULL,  LastAccessTimestamp INT64 NOT NULL, ...) PRIMARY KEY (UserId, LastAccessTimestamp);

Page 9: 超実践 Cloud Spanner 設計講座

なぜテーブルをインターリーブするか

user_id

SELECT user_name, item_id FROM users INNER JOIN items ON users.user_id = items.user_id WHERE user_id = 1234;

user_id user_name

1111 john

1122 paul

1234 bob

user_id item_id

1111 001

1111 002

1234 001

user_name

user_id

item_id

1111 john

1122 paul

1234 bob

1111 001

1111 002

1234 001

Spanserver A Spanserver B1111 john

1111 001

1111 002

1122 paul

1234 bob

1234 001

Spanserver A

Logical View Physical ViewNo interleave

Physical ViewWith interleave

インタリーブをすると、 JOIN   クエリが2つのサーバにアクセスすることもなく、1サーバで済む

Page 10: 超実践 Cloud Spanner 設計講座

インデックスとインターリーブについて

テーブルがインターリーブされている場合、不要なインデックスを作っているかもしれません。

See the example below:

Table DDL index

user

CREATE TABLE test_user (user_id INT64 NOT NULL,create_date TIMESTAMP NOT NULL) PRIMARY KEY (user_id)

CREATE INDEX idx_user_idON user (user_id)

item

CREATE TABLE test_item (user_id INT64 NOT NULL,item_id INT64 NOT NULL) PRIMARY KEY (user_id, item_id),INTERLEAVE IN PARENT user ON DELETE CASCADE

CREATE INDEX idx_user_id_in_itemON item (user_id)

この例ではセカンダリインデックスは不要です

PK は自動的に  インデックスされる

user_id は既に親テーブルでインデックスされている

Page 11: 超実践 Cloud Spanner 設計講座

Channels と sessions

● Channels○ Cloud Spanner へ接続する gRPC コネクション数

○ Default = 4, max = 256.○ コードの中で定義する必要がある

■ SpannerOptions options = SpannerOptions.newBuilder().setNumChannels(8).build();

● Sessions○ Session はトランザクションを実行できるコンテキストです。並列で行われるトランザクションは各自 session

を利用する必要がある。例えば、あるアプリが並列に100トランザクションを行うとしたら、Session pool を最

低100に設定する必要があります。

○ Default value = Default number of channels * 100 = 400○ Recommendations: Number of sessions = number of expected threads○ データベースごとにセッション数の上限が:10000 per node.○ 詳細はこちら:https://cloud.google.com/spanner/docs/sessions?hl=ja

Page 12: 超実践 Cloud Spanner 設計講座

エラー (auto-retry or SpannerException)

Cloud Console 上のグラフにエラーが発生する場合

があります。

● エラーの原因は通常トランザクションが abort されたからです

● エラーが発生したとグラフで見えるが、コード

の中で SpannerException をキャッチできな

● それはクライアントライブラリが自動的に abort されたトランザクションをリトライするか

らです

● 自動リトライが失敗すると、 SpannerException をキャッチできます

● 基本エラーは無視しても問題ありません

Page 13: 超実践 Cloud Spanner 設計講座

ノード追加・削除の際、リシャードがなぜ1〜2sでできるのか? (1/5)

S S S S ノード = Spanner Servers2TB まで管理できるサーバ

Colossus (分散ストレージ )実データはここに格納される

C クライアント

Split

ノード数:4

各ノードは 複数の Split のオーナー

Page 14: 超実践 Cloud Spanner 設計講座

ノード追加・削除の際、リシャードがなぜ1〜2sでできるのか? (2/5)

S S S S ノード = Spanner Servers

Colossus (分散ストレージ )

C クライアント

Split

ノード数:4>3

1ノードを削除

Page 15: 超実践 Cloud Spanner 設計講座

ノード追加・削除の際、リシャードがなぜ1〜2sでできるのか? (3/5)

S S S ノード = Spanner Servers

Colossus (分散ストレージ )

C クライアント

Split

ノード数:31〜2秒後

Split のオーナーが変わるだけ

Page 16: 超実践 Cloud Spanner 設計講座

ノード追加・削除の際、リシャードがなぜ1〜2sでできるのか? (4/5)

S S S S ノード = Spanner Servers

Colossus (分散ストレージ )

C クライアント

Split

ノード数:3>4

1ノードを追加

Page 17: 超実践 Cloud Spanner 設計講座

ノード追加・削除の際、リシャードがなぜ1〜2sでできるのか? (5/5)

S S S S ノード = Spanner Servers

Colossus (分散ストレージ )

C クライアント

Split

ノード数:41〜2秒後

Split のオーナーがまた変わるだけ

Page 18: 超実践 Cloud Spanner 設計講座

容量に関する上限(2GB and 2TB)

Cloud Spanner には 2TB size limit per node という容量上限があります。

インターリーブテーブルを使うと、裏上限がもう1つあります: 2GB per parent record and related child records

Max 2GB

Page 19: 超実践 Cloud Spanner 設計講座

テーブルが分割(Split)されるロジック

Cloud Spanner は容量と負荷状況次第、テーブルを split します。ただし、実際にどのタイミングでテーブルが分割されるか

は保証できません。

Cloud Spanner では以下の2つの条件でテーブルが分割されます:

● Size-based splits○ テーブルの容量が数GB程度になったら、テーブルを split します

● Load-based splits○ Cloud Spanner は分散できる負荷だと、テーブルを split します。分散できない負荷は、      例えばシー

ケンシャルな INSERT(だから Hotspot が発生する)。

上記は、どれだけ PK の選択肢が重要かを示しています。

Page 20: 超実践 Cloud Spanner 設計講座

負荷試験は十分長く実行する

Cloud Spanner の負荷試験を行う場合、5分ではなく、20〜30分の負荷試験をオススメします。その理由は、データの容量

が多くなることにより、Split がより多く発生して、データが正常に全ノード間に分散されるからです。

テーブルが十分 split されると、全ノードが

活用され、Cloud Spanner の最大の

パフォーマンスを出せるようになります。

Split happens

Page 21: 超実践 Cloud Spanner 設計講座

負荷試験間はデータベースをDropする

負荷試験を毎回行う際は、Insert したデータを全て削除するのではなく、データベースを Drop するようオススメします。

データの削除+再Insert は、テーブルスキャンのパフォーマンスを劣化します。それは、削除されたデータは Garbage Collector が物理的に削除するまではテーブルに append されるからです。テーブルのクリーンアップまで1週間までかかり

ます。

Spanner のストレージは log-structured merge trees (LSM Tree)を使用している。 → Delete のオペレーションは "append a delete mutation" とストレージレベルで見られる。古いデータはデータベースがク

リーンアップされるまで削除されない。

→ 削除された PK がまだ格納されているので、テーブルスキャンがインパクトされる。

Page 22: 超実践 Cloud Spanner 設計講座

TPSの数え方

パフォーマンスグラフは受信した API リクエスト数を表示する

● "reads/s" は read と query を含む

● "writes/s" はトランザクションのコミット数を示す その結果、TPS は "writes/s" ということになります 3 read API calls と 9 buffered mutations を含むトランザクションは 3R, 1W とカウントされる

Page 23: 超実践 Cloud Spanner 設計講座

ノード数を縮小するについて

Cloud Spanner にはオートスケール機能はまだありません。

ノード数を縮小しようとすると、1ノードまでに縮小できない場合があります。

Spanner の仕組み上、Delete や Update されたデータは Garbage Collector が起動するまでに一時的に格納されます。そ

の理由は2つ:整合性のあるバックアップを行うためとより高いパフォーマンスを提供するためです(Cloud Spanner はデー

タ容量が多いほど、パフォーマンスが出るので)。

1

2

3

1. Monitor Spanner CPU usage

2. Trigger function when threshold hit

3. Function add a node

Autoscale workaround

Page 24: 超実践 Cloud Spanner 設計講座

グラフの ”Total storage” の意味って?

Cloud Console のグラフとして表示される “Total storage” には最新のデータ+

削除・更新されたデータを含まれています。削除・更新されたデータは1週間以内

にクリーンアップされます(平均 3.5 日)。

古いデータを一時的に確保するメリットは:● 高スループットの Read/Write を提供するため

● 整合性のあるバックアップを取るため

お客様に課金されるデータ:

● ライブデータ

● 削除・更新されたデータ(1週間以内にクリーンアップされるまで)

Page 25: 超実践 Cloud Spanner 設計講座

Query Plan Cache

SQL クエリを Spanner で使用する際に、bound parameters を使用す

るのが重要です。

Cloud Spanner のノードは query plan cache の数が限られています。

クエリの中に静的パラメータを使用すると、各クエリが違うものと見られ

て、query plan cache を効果的に活用することができません。この問題

を避けるには、以下の例のようにクエリは parameter binding を利用す

る必要があります。

Statement statement = Statement .newBuilder("SELECT name WHERE id > @msg_id AND id < @msg_id + 100") .bind("msg_id").to(500) .build();

これ重要!