狠狠撸

狠狠撸Share a Scribd company logo
国内Cloud Spanner初事例!
迎車料金無し!新感覚タクシーアプリ
2017-12-14 ExistMikan
酒とゲームとインフラとGCP 第7回
自己紹介
@ExistMikan
経歴
?会津大学卒(ヅ大)
?ヅ大発某ベンチャー企業に就職
→ モバイルアプリメイン
?現職場にJOIN
→ GCP中心の開発へ
伊藤勇斗
(Hayato Ito) 吉積情報株式会社所属
?フルクルのサーバ/モバイル担当
今日する話
フルクル紹介
システム構成
Cloud Spanner利用の経緯
RESTでCloud Spannerの細かい話
実際の利用状況等
フルクルとは
www.fulcul.com
提供:国際自動車株式会社
2017/11/12 リリース!
フルクルとは
フルクルとは
システム構成
API系
App Engine Batch系
App Engine
wordpress
App Engine
管理系
App Engine
BigQueryLogging
Cloud
SQL
Cloud
Spanner
Cloud
DatastoreiOS
Android
Clients manager
Task
Queues
Memcache
Monitoring
想定リクエスト数
Android/iOS
約3300台 : 各車約10秒に1回継続的にリクエスト
?人 : 起動中に約5秒に1回リクエスト
ほぼタクシーのリクエストが占める形
毎秒300リクエスト来る前提で構える
モニタリング
?SpannerのCPU使用率
→ ノード数の基準になるので超大事
?GAEインスタンス数
→ おかしなスパイクが発生していないかとか
?APIのレイテンシ
→ 健康なシステムかどうか
?APIのエラー
→予想しない出来事が起きているか
他にも色々なグラフをダッシュボードで管理
何かがおかしいとき、同じ時間軸の他の指標を
見れることで色々推測できてGOOD
Cloud Spannerとは
水平スケーリング可能でグローバルな整合性を備えた、
初のリレーショナル?データベース
大まかな構造
インスタンス
データベース
テーブル
インデックス
インスタンスの下に、各種構成要素がぶら下がる形。
インスタンスはノード数を設定でき、この数が性能と料金に直結する。
Spanner利用の経緯
メイン機能
?タクシーの近くにいるユーザ、およびユーザの近くにいるタクシーを割り出す
当初想定 Cloud
Datastore
不等号フィルタが複数の
プロパティに適用できない制限
できない!
35.0000 < lat < 36.000 && 138.0000 < lon < 139.000
&&
2017/12/13 00:00:00 < modificationDate < 2017/12/13 00:01:00
35.0000 < lat < 36.000 && 138.0000 < lon < 139.0000
時間でも絞りたいけど、もっとできない!
Spanner利用の経緯
Cloud
Datastore
DataStore、実はこんなのがいたりする
ローカルサーバでは動くが、GeoPtあるとデプロイ不可 → アルファ版!!
Spanner利用の経緯
このままだとどうなるか?
データストアから大量に取ってきてJava側でどうにかする
→ どうにかはなる
→ データ量多い
→ 共有memcacheぶっ壊れる (でも使わないとDataStore Read料金がスゴい)
→ 処理量多い=APIのレイテンシ増加
→ GAEインスタンス数の増加 → 料金が(ry
読み込みはいいとしても書き込みの料金も結構行く
Spanner利用の経緯
2017/02/14 ベータ発表
2017/05/16 GA
2017/06/16 東京リージョン選択可 !
僥倖???!いざ検証へ
CloudSQLについてはコネクション速度の懸念など
あったためspanner優先とした。。
Spanner検証
App
Engine 今回のAppEngineは Standard Environment Java 7 (!)
(goでは動作した様子)
java 8, Flex, GCEにする? → 時間的余裕無し → RESTで検証
Spanner検証:REST
Cloud Spanner API Client Library
https://developers.google.com/api-client-library/java/apis/spanner/v1?hl=ja
RESTをラッパしたいつものGoogleライブラリは提供されている
Spanner検証:REST
認証周り
インスタンスの情報
インスタンスの管理
データベースの管理
データベースオペレーションの管理
セッションが絡む処理
(セッションの作成/削除、データの作成 /更新/削除、クエリ)
インスタンスオペレーションの管理
Spanner検証:REST
?認証
?インスタンス/データベース/テーブル/イン
デックスのAPIを実行
?時間がかかるものはオペレーション API
をポーリングして状況把握
管理系 データ操作系
?認証
?操作するデータが入っているデータベー
スのセッションを取得
?セッションAPIにて、
データの操作を行う
?取得したセッションを削除する
(必要であれば)
GAEの1リクエストの中で、下記のような流れで 础笔滨を叩いていく
認証
SpannerのAPIにアクセスするためのオブジェクトを下記の様に生成
認証に関係するHttpRequestInitializerの生成コードは下記
デプロイ環境でのアクセスはAppIdentityCredentialにスコープを設定するだけでOK
ローカルとかで触りたい場合は認証用のキーを入手しておき、それを指定して作成
インスタンスを作成する:REST APIリファレンス
REST APIのリファレンス
GCP ProjectのID
が入る
インスタンスを作成する:APIライブラリ
APIのパラメータを作成する部分
APIを実行する部分
長時間実行オペレーションの管理
オペレーション名をパスに含め、
オペレーションのステータスを
問い合わせるAPI。
instance用とdatabase用でクラスが
違うので注意(パスが異なるため。
スーパークラスは同じ )
ポーリングしてオペレーションの状態を確認できる API
セッションの生成
セッションを作成したいデータベースまでのパス +”/sessions”となるパスに対してPOST (パラメータは空)
SessionオブジェクトのgetName()で取得できる文字列がセッション。
データの書き込み
commitというAPIがあり、それに更新内容を詰めてPOSTする形。パスの殆どはセッションで、下記のような文字列になる。
projects/project-amaterasu/instances/main-instance/databases/main-db/sessions/AO6KeAX4B5A0hUz3_d1bjBQ0T8wrsxNqS-lbplIDWcM8_X727kVAlK9Fm40
変更内容のオブジェクト
トランザクションの指定
データの書き込み
ライブラリ側は対応するオブジェクトを下記のように作り込む形となる。
下記は単一の行を単純に書き込む例。
writeXXXX(Object, xxx)は適切に
キャストするための即席関数
データの書き込み:即席関数
Spannerが認識する型のオブジェクトに適宜変換。
INT64にはIntegerでは入れられないなどがあった。
(実質Longだから? しかしLongも文字列..)
データの書き込み?プチハマりポイント
タイムスタンプ型は日付フォーマットの文字列指定する
→ ドキュメントにはタイムゾーン設定などができそうな形だったが、
実際はオフセットなし、 UTC固定のRFC3339文字列しか入らなかった
何故
データの読み込み
データ操作系なので書き込みなどと同様、セッションが必要。
SQL文字列を指定する。
‘@’パラメータによるプレースホルダー指定も可能(大事)
データの読み込み
実行結果からgetRows()でオブジェクトのリスト、のリストを取得。入れた順に入っているので都度
キャストしながら取得。※以下サンプルはplaceholder無し版
データの読み込み:即席関数
書き込みのときと同じノリで変換。
データの読み込み:プチハマリポイント
概ね”yyyy-MM-dd’T’HH:mm:ss.SSS’Z’でくるのだが、ミリ秒以下がちょうど 0だと、
ピリオド以下がばっかり切られる
→上記フォーマットだとパース失敗 (??ω?`)
→暫定対処で対応した。。
データの削除
データ書き込みのときの commit APIを利用するが、Mutationの中身をinsertOrUpdate
等ではなく、deleteを使用する。(サンプルコード省略)
セッション管理
読み書きするために必ず必要になる、Cloud Spanner データベース サービスとの通信チャネル。
Client Libraryには、Channnelという概念があり、そこでセッションプールのような管理がされているようだが
RESTでは該当する概念無し。
生成のためのレイテンシがそれなりにかかる(100ms~1000msくらい?)
→ リクエストするたびに生成/削除をやるのはコストが高い
毎回やりたくな
い!
そうだ、
キャッシュしよう
セッション管理
1個のセッションを全てのリクエストで使いまわして負荷を掛けてみた
秒間100リクエストくらいの書き込みまでは 200ms程度で捌けたっぽい
秒間300リクエストくらいになってくると、 10秒以上のレイテンシが発生するように。
完全にアウト
検証用のフロントエンドインスタンスも爆速で増加(??ω?`)
良い子はまねしてはいけない
セッション管理:キャッシュ場所
?簡単そうだが、インスタンス間で共有できない
→ インスタンス数の増加によっては、
セッション数の上限に達してしまうのでは?
→ コワイ
インスタンス
セッション
セッション
セッション
インスタンス
セッション
セッション
セッション
?インスタンス毎にメモリに持つ
?Memcache/DataStoreに入れる
インスタンス
セッション
セッション
インスタンス
セッション
セッション
?全インスタンス間で共有できる
?状態をもたせるのはつらそう(atomicに更新が厳しい)
?max値を決めておけば、上限を超えることはない
→ 負荷が高まった時は、1つのセッションを同時に使うリクエストが増える
→ 読み込みは快速だが、書き込みが1つのセッションに集中するとロックがかかるの
でその分遅くなる
?併用しているのは毎回DataStoreにreadしにいくとread entitiy分の料金が発生する
ため。
管理しやすそう
セッション管理:フローチャート
ランダムなキーでセッションを取って、なければ作り、あればそのまま利用する形で Try
(簡単化のため毎回セッション有効判定を実施 )
多量のリクエストを素早く捌けた
ベストプラクティスとホットスポット
Spannerには、パフォーマンスを最大化するために、スキーマの設計などに関してベストプラクティスがある。
ベストプラクティスとは?
主キーの選択
→ 値が単調に増加する列を最初のキー部分に選択すると、キー空間の最後にすべての挿入が実行されるため、
誤ってホットスポットが作成される可能性がある
Cloud Spanner は分散データベースなので、データベースが
大きくなると、Cloud Spanner は「スプリット」と呼ばれる塊に
データを分割します。各スプリットは、相互に独立して移動で
き、異なるサーバーに割り当てることができます。サーバーは異
なる物理的なロケーションに存在することもあります。
連続しているデータは別のスプリットに分割されにくい
挿入が同じスプリット =同じDBになるので、負荷が集中する
ホットスポットの影響(イメージ)
ノードA
ノードB
ノードC
スプリットA
スプリットB
スプリットC
スプリットD
ID:111112
ID:111111
ID:111110
ID:111109
:
ID:2222
ID:2221
ID:2220
ID:333
ID:332
ID:331
ID:9
ID:8
ID:7
?単調増加だと、赤字の行が追加される時は一番上のスプリットにアクセスが発生する。
?近いKeyの範囲でスプリットは作られるため、単調増加の際は必ずスプリット Aを管理するノードAにアクセスが
かかる
?ノードB、ノードCをフル活用できていないので、ノード数を追加しても性能向上の恩恵が受けられない
ホットスポットの検証
?タイムスタンプをキーにしたテーブルと、UUIDをキーにしたテーブルを用意し、負荷を掛けて比較
?ノード数1の場合と、ノード数3の場合で検証する
?GCEから秒間リクエスト500になるように、GAEにデプロイしたAPIを叩く → 30分継続実行
?APIでは、リクエストを受けたら新しい行をテーブルに追加するREST APIを実行する
?30分の実施後、DBは一回DB毎削除する→世代管理されているので中身をカラにするだけではダメ
ちゃんと分散されるほう ベストプラクティスに沿わないヤツ
ノード数1
SimpleTable
TimestampTable
25.11%
26.45%
ノード数1では対してCPU使用率に変化なし。
全スプリットを1ノードで管理するためと思われる。
ノード数3
SimpleTable
TimestampTable
?ノード数3にしたことで、CPU使用率がきっちり下がっている
?タイムスタンプをキーにしているテーブルは、
UUIDをキーにしているテーブルより CPUを喰っている!
8.728%
12.49%
検証まとめ
?高い処理効率を維持するには、ベストプラクティス準拠 & ノード数を3以上にするのが良い
→ キー選択以外にも多くのベストプラクティスがある
→ 想定リクエスト数で、 CPU使用率が75%を下回っているなら、ノード数は 1でも問題にはならない
Quotaについて
REST Libraryを使用した場合(UrlFetch版)
1日のリミット : 864,657,084
Client Libraryを使用した場合(gRPC版)
1日のリミット : 3,456,000
フルクルの1日のcall数
120,960,000
実際のリクエスト数とCPU使用率(1ヶ月)
容量/リクエスト数
変わらずで上昇。
これは無駄な不要
過去データを削除し
たら落ち着いた
基本的にデータは消去しないが、 Storageは上げ下げする
実際のリクエスト数とCPU使用率(1日)
大体がリクエスト数に準
じて変動している。
レイテンシはほぼ一定
の範囲で安定
エラーの話
?IOException
?SocketTimeoutException
?InternalTransientException
全て一時的なもので、
ぽつぽつと発生しているが、
全体の呼び出し回数から見ると
非常に少ない値
※1dayのグラフ
GAEのレイテンシに影響しないよう、
Spanner呼び出し時のタイムアウトは極力短めに設定。
スケーラブル:GAE
※1日分のグラフ
Active
Idle
javaでspin-upが遅いので、Idle多めでいい感じに対応
リクエスト数
スケーラブル:Cloud Spanner
node数を2→3に変更したとき。CPU使用率に反映されていることがわかる
現構成だとこのほか保持するセッション数の調整も必要だが。。
負荷試験について
?何よりも重要
Compute
Engine
App
Engine
Cloud
Spanner
?今回はGCEからgoスクリプトで想定リクエスト数のアクセスをGAEにかけた
?30分~1時間くらいのスパンで確認していたが、実際稼働後の様子を見るとさらに実際
に近いアクセスパターン/データで試すべきと感じた
?DBに期待する要件に合わせた検証方法を検討する必要がある
→ フルクルの利用方法からという視点だと、
一貫性やDB容量についての観点の負荷試験はあまり重要
ではなかったが、他はそうもいかないはず
負荷試験について:苦しいところ
はじめに要件があって、それを満たせるかどうかという観点では検証できたが、
最大のパフォーマンスを引き出せているかの確認が難しい???!
コンソールか何かで確認したいこと
?各ノードのCPU使用率
→ 高いやつがあればHotSpotが発生しているとわかる
?Storageの内訳
→ どれくらい過去のものまで保持しているのかとか
料金
→ お高い。。負荷試験はしょうがないとしてもtry&errorなこ
とやってるとき苦しかった
運用
?基本的には何もしなくてもOK
→ リリース直後はちょくちょくモニタリングを見ていたが、
総じて大きな問題は発生していない
→ SpannerCPU使用率やGAEのインスタンス数、
APIのレイテンシなどについてアラートだけ設定してあとは放置
このあたりの安心感はさすがGCPといったところ!
wordpressのアップデートは手動deploy..
そういえばシステムの料金は?
?Cloud Spanner 3台分なのでベースとしてはそれなりにかかる
→ ざっくり30万近く
?DataStoreと比べると、多少DataStoreの方が安くなるがCloud Spannerの
レイテンシが無さ過ぎたので、その分のGAEインスタンス代が浮いている形!
※1リクエストで3回くらいspannerをコールしてこれ。
パフォーマンスも向上しているので
フルクルには合っていると感じる
今後について
?Java1.8 (もしくは別言語)へのリプレース!
→ そろそろか?と思っていたところに昨日、java1.7 deprecate扱い+2019年1月で終了
のお達しメールが届く!
?gRPC版にして再度同検証を行い、同じようなCPU使用率になるのか
とかを見てみたい
→ 今は都合でREST APIで扱っているが、REST APIに優位性があるのなら使い続けるのもあり ?
?効率の良いテーブル構造/Indexになっているか等細かいチューニング
超実践 Cloud Spanner 設計講座
/HammoudiSamir/cloud-spanner-78081604
Spannerユーザ必読。ノードやスプリットの関係についても言及されています。
普通のエンジニアが【Cloud Spanner】使ってみた
/ssuserc49633/20170822-cloud-spanner
こちらも参考情報盛りだくさんです。
Cloud Spanner参考資料
ご清聴ありがとうございました!
ご帰宅は是非フルクルで!

More Related Content

国内Cloud spanner初事例!「迎車料金無し!新感覚タクシーアフ?リ「フルクル」」