SlideShare a Scribd company logo
超実践 Cloud Spanner 設計講座
知ってることを全て紹介します!
Proprietary
Samir Hammoudi aka サミール
クラウドカスタマエンジニア
JULY 21, 2017
Cloud Spanner とは?
Google のマネージド・スケーラブル・リレーショナルデータベース・サービス
完全マネージドのグローバルスケールで DB サービス1
2
3
4
ゾーン間・リージョン間の自動 synchronous レプリケーション
スキーマ、ACID トランザクション、SQL
Google内部では、既に5年以上の運用経験
(AdWords, Google Play…)
注意事項: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 は分散データベースだということを忘れないでね!!
MySQL ではないが...
特に大規模アクセスのあるコンテンツや
DB運用に苦労しているサービスに最適!
ノードを追加するだけでオートシャードされ、
理論上無限にスケールしつつ、
レイテンシーがより安定 :)
運用も楽 ^_^
今日のおさらいリスト
1. PK の選択が重要 ←これが1番重要!!
2. なぜテーブルをインターリーブするか?
3. インデックスとインターリーブについて
4. Channels と Sessions
5. ノード追加・削除の際、リシャードがなぜ1〜2sでできるのか?
6. エラーについて
7. 容量に関する上限
8. テーブルが分割(Split)されるロジック
9. 負荷試験は十分長く実行する
10. 負荷試験間はデータベースをDropする
11. TPSの数え方
12. ノード数を縮小するについて
13. グラフの ”Total storage” の意味って?
14. Query Plan Cache ←これも重要!!
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 です。
これ重要!
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);
PKを選択する時の注意点 - Hotspot (2/2)
● Hotspot の対策
○ PK の順序を交換
○ 新しく書き込まれるレコードは、テーブルの最後に追加されず、 Spanner
サーバー間にちゃんと分散される
CREATE TABLE Users (
 UserId INT64 NOT NULL,
 LastAccessTimestamp INT64 NOT NULL,
...
) PRIMARY KEY (UserId, LastAccessTimestamp);
なぜテーブルを
インターリーブするか
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 B
1111 john
1111 001
1111 002
1122 paul
1234 bob
1234 001
Spanserver A
Logical View Physical View
No interleave
Physical View
With interleave
インタリーブをすると、 JOIN   クエリ
が2つのサーバにアクセスすることも
なく、1サーバで済む
インデックスとインターリーブについて
テーブルがインターリーブされている場合、不要なインデックスを作っているかもしれません。
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_id
ON 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_item
ON item (
user_id
)
この例ではセカンダリイン
デックスは不要です
PK は自動的に  イ
ンデックスされる
user_id は既に親テーブルで
インデックスされている
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
エラー (auto-retry or SpannerException)
Cloud Console 上のグラフにエラーが発生する場合
があります。
● エラーの原因は通常トランザクションが abort
されたからです
● エラーが発生したと��ラフで見えるが、コード
の中で SpannerException をキャッチできな
い
● それはクライアントライブラリが自動的に
abort されたトランザクションをリトライするか
らです
● 自動リトライが失敗すると、
SpannerException をキャッチできます
● 基本エラーは無視しても問題ありません
ノード追加・削除の際、リシャードが
なぜ1〜2sでできるのか? (1/5)
S S S S ノード = Spanner Servers
2TB まで管理できるサーバ
Colossus (分散ストレージ)
実データはここに格納される
C クライアント
Split
ノード数:4
各ノードは
複数の Split の
オーナー
ノード追加・削除の際、リシャードが
なぜ1〜2sでできるのか? (2/5)
S S S S ノード = Spanner Servers
Colossus (分散ストレージ)
C クライアント
Split
ノード数:4>3
1ノードを削除
ノード追加・削除の際、リシャードが
なぜ1〜2sでできるのか? (3/5)
S S S ノード = Spanner Servers
Colossus (分散ストレージ)
C クライアント
Split
ノード数:3
1〜2秒後
Split のオーナー
が変わるだけ
ノード追加・削除の際、リシャードが
なぜ1〜2sでできるのか? (4/5)
S S S S ノード = Spanner Servers
Colossus (分散ストレージ)
C クライアント
Split
ノード数:3>4
1ノードを追加
ノード追加・削除の際、リシャードが
なぜ1〜2sでできるのか? (5/5)
S S S S ノード = Spanner Servers
Colossus (分散ストレージ)
C クライアント
Split
ノード数:4
1〜2秒後
Split のオーナー
がまた変わる
だけ
容量に関する上限(2GB and 2TB)
Cloud Spanner には 2TB size limit per node という容量上限があります。
インターリーブテーブルを使うと、裏上限がもう1つあります: 2GB per parent record and related child records
Max 2GB
テーブルが分割(Split)されるロジック
Cloud Spanner は容量と負荷状況次第、テーブルを split します。ただし、実際にどのタイミングでテーブルが分割されるか
は保証できません。
Cloud Spanner では以下の2つの条件でテーブルが分割されます:
● Size-based splits
○ テーブルの容量が数GB程度になったら、テーブルを split します
● Load-based splits
○ Cloud Spanner は分散できる負荷だと、テーブルを split します。分散できない負荷は、      例えばシー
ケンシャルな INSERT(だから Hotspot が発生する)。
上記は、どれだけ PK の選択肢が重要かを示しています。
負荷試験は十分長く実行する
Cloud Spanner の負荷試験を行う場合、5分ではなく、20〜30分の負荷試験をオススメします。その理由は、データの容量
が多くなることにより、Split がより多く発生して、データが正常に全ノード間に分散されるからです。
テーブルが十分 split されると、全ノードが
活用され、Cloud Spanner の最大の
パフォーマンスを出せるようになります。
Split
happens
負荷試験間はデータベースをDropする
負荷試験を毎回行う際は、Insert したデータを全て削除するのではなく、データベースを Drop するようオススメします。
データの削除+再Insert は、テーブルスキャンのパフォーマンスを劣化します。それは、削除されたデータは Garbage
Collector が物理的に削除するまではテーブルに append されるからです。テーブルのクリーンアップまで1週間までかかり
ます。
Spanner のストレージは log-structured merge trees (LSM Tree)を使用している。
→ Delete のオペレーションは "append a delete mutation" とストレージレベルで見られる。古いデータはデータベースがク
リーンアップされるまで削除されない。
→ 削除された PK がまだ格納されているので、テーブルスキャンがインパクトされる。
TPSの数え方
パフォーマンスグラフは受信した API リクエスト数を表示する
● "reads/s" は read と query を含む
● "writes/s" はトランザクションのコミット数を示す
その結果、TPS は "writes/s" ということになります
3 read API calls と 9 buffered mutations を含むトランザクションは 3R, 1W とカウントされる
ノード数を縮小
するについて
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
グラフの ”Total storage” の意味って?
Cloud Console のグラフとして表示される “Total storage” には最新のデータ+
削除・更新されたデータを含まれています。削除・更新されたデータは1週間以内
にクリーンアップされます(平均 3.5 日)。
古いデータを一時的に確保するメリットは:
● 高スループットの Read/Write を提供するため
● 整合性のあるバックアップを取るため
お客様に課金されるデータ:
● ライブデータ
● 削除・更新されたデータ(1週間以内にクリーンアップされるまで)
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();
これ重要!
Additional readings
● SQL Best Practices
● Best Practices for Schema Design
● Efficient Bulk Loading
● Quizlet Cloud Spanner tests

More Related Content

超実践 Cloud Spanner 設計講座

  • 1. 超実践 Cloud Spanner 設計講座 知ってることを全て紹介します! Proprietary Samir Hammoudi aka サミール クラウドカスタマエンジニア JULY 21, 2017
  • 2. Cloud Spanner とは? Google のマネージド・スケーラブル・リレーショナルデータベース・サービス 完全マネージドのグローバルスケールで DB サービス1 2 3 4 ゾーン間・リージョン間の自動 synchronous レプリケーション スキーマ、ACID トランザクション、SQL Google内部では、既に5年以上の運用経験 (AdWords, Google Play…)
  • 3. 注意事項: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 は分散データベースだということを忘れないでね!!
  • 5. 今日のおさらいリスト 1. PK の選択が重要 ←これが1番重要!! 2. なぜテーブルをインターリーブするか? 3. インデックスとインターリーブについて 4. Channels と Sessions 5. ノード追加・削除の際、リシャードがなぜ1〜2sでできるのか? 6. エラーについて 7. 容量に関する上限 8. テーブルが分割(Split)されるロジック 9. 負荷試験は十分長く実行する 10. 負荷試験間はデータベースをDropする 11. TPSの数え方 12. ノード数を縮小するについて 13. グラフの ”Total storage” の意味って? 14. Query Plan Cache ←これも重要!!
  • 6. 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 です。 これ重要!
  • 7. 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);
  • 8. PKを選択する時の注意点 - Hotspot (2/2) ● Hotspot の対策 ○ PK の順序を交換 ○ 新しく書き込まれるレコードは、テーブルの最後に追加されず、 Spanner サーバー間にちゃんと分散される CREATE TABLE Users (  UserId INT64 NOT NULL,  LastAccessTimestamp INT64 NOT NULL, ... ) PRIMARY KEY (UserId, LastAccessTimestamp);
  • 9. なぜテーブルを インターリーブするか 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 B 1111 john 1111 001 1111 002 1122 paul 1234 bob 1234 001 Spanserver A Logical View Physical View No interleave Physical View With interleave インタリーブをすると、 JOIN   クエリ が2つのサーバにアクセスすることも なく、1サーバで済む
  • 10. インデックスとインターリーブについて テーブルがインターリーブされている場合、不要なインデックスを作っているかもしれません。 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_id ON 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_item ON item ( user_id ) この例ではセカンダリイン デックスは不要です PK は自動的に  イ ンデックスされる user_id は既に親テーブルで インデックスされている
  • 11. 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
  • 12. エラー (auto-retry or SpannerException) Cloud Console 上のグラフにエラーが発生する場合 があります。 ● エラーの原因は通常トランザクションが abort されたからです ● エラーが発生したとグラフで見えるが、コード の中で SpannerException をキャッチできな い ● それはクライアントライブラリが自動的に abort されたトランザクションをリトライするか らです ● 自動リトライが失敗すると、 SpannerException をキャッチできます ● 基本エラーは無視しても問題ありません
  • 13. ノード追加・削除の際、リシャードが なぜ1〜2sでできるのか? (1/5) S S S S ノード = Spanner Servers 2TB まで管理できるサーバ Colossus (分散ストレージ) 実データはここに格納される C クライアント Split ノード数:4 各ノードは 複数の Split の オーナー
  • 14. ノード追加・削除の際、リシャードが なぜ1〜2sでできるのか? (2/5) S S S S ノード = Spanner Servers Colossus (分散ストレージ) C クライアント Split ノード数:4>3 1ノードを削除
  • 15. ノード追加・削除の際、リシャードが なぜ1〜2sでできるのか? (3/5) S S S ノード = Spanner Servers Colossus (分散ストレージ) C クライアント Split ノード数:3 1〜2秒後 Split のオーナー が変わるだけ
  • 16. ノード追加・削除の際、リシャードが なぜ1〜2sでできるのか? (4/5) S S S S ノード = Spanner Servers Colossus (分散ストレージ) C クライアント Split ノード数:3>4 1ノードを追加
  • 17. ノード追加・削除の際、リシャードが なぜ1〜2sでできるのか? (5/5) S S S S ノード = Spanner Servers Colossus (分散ストレージ) C クライアント Split ノード数:4 1〜2秒後 Split のオーナー がまた変わる だけ
  • 18. 容量に関する上限(2GB and 2TB) Cloud Spanner には 2TB size limit per node という容量上限があります。 インターリーブテーブルを使うと、裏上限がもう1つあります: 2GB per parent record and related child records Max 2GB
  • 19. テーブルが分割(Split)されるロジック Cloud Spanner は容量と負荷状況次第、テーブルを split します。ただし、実際にどのタイミングでテーブルが分割されるか は保証できません。 Cloud Spanner では以下の2つの条件でテーブルが分割されます: ● Size-based splits ○ テーブルの容量が数GB程度になったら、テーブルを split します ● Load-based splits ○ Cloud Spanner は分散できる負荷だと、テーブルを split します。分散できない負荷は、      例えばシー ケンシャルな INSERT(だから Hotspot が発生する)。 上記は、どれだけ PK の選択肢が重要かを示しています。
  • 20. 負荷試験は十分長く実行する Cloud Spanner の負荷試験を行う場合、5分ではなく、20〜30分の負荷試験をオススメします。その理由は、データの容量 が多くなることにより、Split がより多く発生して、データが正常に全ノード間に分散されるからです。 テーブルが十分 split されると、全ノードが 活用され、Cloud Spanner の最大の パフォーマンスを出せるようになります。 Split happens
  • 21. 負荷試験間はデータベースをDropする 負荷試験を毎回行う際は、Insert したデータを全て削除するのではなく、データベースを Drop するようオススメします。 データの削除+再Insert は、テーブルスキャンのパフォーマンスを劣化します。それは、削除されたデータは Garbage Collector が物理的に削除するまではテーブルに append されるからです。テーブルのクリーンアップまで1週間までかかり ます。 Spanner のストレージは log-structured merge trees (LSM Tree)を使用している。 → Delete のオペレーションは "append a delete mutation" とストレージレベルで見られる。古いデータはデータベースがク リーンアップされるまで削除されない。 → 削除された PK がまだ格納されているので、テーブルスキャンがインパクトされる。
  • 22. TPSの数え方 パフォーマンスグラフは受信した API リクエスト数を表示する ● "reads/s" は read と query を含む ● "writes/s" はトランザクションのコミット数を示す その結果、TPS は "writes/s" ということになります 3 read API calls と 9 buffered mutations を含むトランザクションは 3R, 1W とカウントされる
  • 23. ノード数を縮小 するについて 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
  • 24. グラフの ”Total storage” の意味って? Cloud Console のグラフとして表示される “Total storage” には最新のデータ+ 削除・更新されたデータを含まれています。削除・更新されたデータは1週間以内 にクリーンアップされます(平均 3.5 日)。 古いデータを一時的に確保するメリットは: ● 高スループットの Read/Write を提供するため ● 整合性のあるバックアップを取るため お客様に課金されるデータ: ● ライブデータ ● 削除・更新されたデータ(1週間以内にクリーンアップされるまで)
  • 25. 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(); これ重要!
  • 26. Additional readings ● SQL Best Practices ● Best Practices for Schema Design ● Efficient Bulk Loading ● Quizlet Cloud Spanner tests