Parse.comからGCPへ移行した話

# この資料について
* [GCPUG Shonan vol.7](http://gcpug-shonan.connpass.com/event/41911/)での発表資料です

---
# 自己紹介
* 武市融紀
* 株式会社ノハナ
* サーバーサイドエンジニア
* GCP Next 2016で弊社事例について発表してきました

---
# [ノハナ](http://nohana.jp/)
* 株式会社ミクシィの社内公募制度による新規事業が子会社化
* 月一冊無料でフォトブックが作成できるアプリ
* 累計150万ユーザー
* 写真累計アップロード 1.5億枚

---
# [Parse.com](http://parse.com/)
* 老舗Mobile向けBaaS
* REST APIによるデータのCRUD
    - バックエンドのMongoDBに永続化
* ユーザー認証
* ファイルストレージ
* プッシュ通知送信機能

---
# 移行のきっかけ
* Parse.comの突然の終了宣告
    - 来年1月までに移行を終えないとサービスが＼(^o^)／ｵﾜﾙ
* 終了宣言以前から移行の話は出ていた
    - 月末に度々不安定になっていた

---
# GCPを選んだ理由
* コストパフォーマンス
    - GCE料金が利用期間に応じて勝手に割引される
    - Cloud StorageがS3より安い
* Googleの長年の技術/ノウハウが投入されている
* Googleのクラウドへの投資額
    - 成長が期待できる
* データ解析系サービスの拡充に期待
    - 画像データの資産を活かしたい

---
# 移行の全貌
* DB
    - MongoDB (Replica set)
* API Server
    - [parse-server](https://github.com/ParsePlatform/parse-server) (node.js + express)
        + iOS / Androidアプリ向けのエンドポイント
* CloudCode
    - parse-serverのwebhookとして機能するサーバー (node.js + express)
    - parse-serverでレコード作成/更新時にリクエストが飛んでくる
        + 保存前のvalidationなどを行う
    - Cloud Functionsのような機能
        + 名前もそのまま"Cloud Function"
* Push通知送信機能
    - 全ユーザに対する予約プッシュ機能
    - 開封通知の集計
* 写真ファイルの移行
    - 1.5億枚(平均1.5MB)の写真ファイルをParse.comのストレージから自社ストレージへ移行

---
# DBとAPI serverの構成の検討
* 今回の移行はMongoDB一択
    - データ移行ツールがMongoDBしかサポートされていない
    - DBはGCEに置くしか無い
* 検討したAPI serverの構成
    - GAE Standard Env
    - GAE Flexible Env
    - GKE
    - GCE instance group

---
# GAE Standard Envの場合
* GCEとはnetwork的に分断されている
    - mongoにprivate IPでアクセスできない
    - 出口IPが決まらない
        + mongoへのアクセスをIPアドレスで制限できない...
* connection poolを作れない
    - Socket APIだと毎回mongoとのconnectionを確立しにいくためオーバーヘッドが大きい
        + SEのStatelessな設計は爆速スケールとのトレードオフなので仕方ない...
    - connection poolingを行う別のserviceをFEで作ってgRPCで会話？
        + オーバーエンジニアリング感が...
* スケーラビリティの対価が大きすぎるため断念

---
# GAE Flexible Envの場合
* 裏側はGCE with Docker
    - Private IPでmongoと喋れる
    - serverがstateを持てる
* まだGAじゃないので(ry

---
# GKE
* Docker + コンテナオーケストレーション層
    - API serverもMongoDBも載せてしまえる
* 習得時間掛けれなく断念

---
# GCE instance group
* 1 instance group + 1 LBのシンプルな構成
* これになりました

---
![gcpug_shonan_figures.001.png](https://qiita-image-store.s3.amazonaws.com/0/2284/d971b462-4cac-ef79-70b6-7d392b0a0881.png)

---
# MongoDB

---
# MongoDBのinstance構成
* GCE + persistent SSD
    - IOPS 15,000 & 240MB/s 出るのでRAID組む必要なかった
* replicaを各zoneに配置

---
# 柔軟なinstance size
* Custom instance typeのお陰でscale up (≠scale out)を細かな粒度で行える
    - shardしなくて済んだ
* stopすればinstanceを作成し直すことなくサイズを変更できる
    - 最初はオーバープロビジョニングして後々下げていく戦略が取れる

---
# Live migration
* 本番DBで経験したが問題なし
    - Operationsに表示されなければ本当に気が付かない
* Local SSDを持つinstanceではCPU使用率が大きく上昇した
    - StackdriverのGCE MonitoringベースのCPU使用率が上昇しアラートが飛んだ
    - 物理host間でSSDの中身コピーが走ってる？
    - 実際に性能が低下したかどうかまでは確認できず

---
# Disk snapshotによるバックアップ
* 1時間に1回backup replicaを切り離してdisk snapshotを作成
    - 弊社の規模だと大体5分ぐらいで作成できる
    - 500以上の差分があってもリストアのスピードが遅くはならない
* コストが低く抑えれる
    - 単価はGCS相当 ($26/TB/mth) と安い
    - 差分バックアップの実データ量で計算される
        + 実際のデータ増分よりも差分バックアップのサイズは10倍程度大きい
        + ブロック単位でのdiffを取ってる？
* snapshotはglobalにreplicateされる
    - DBのregion移動の際に重宝

---
# MongoDBのネットワーク構成
* Parse.comからの接続を受けるためPublic IPを振っている
    - Public IPをfirewall ruleに登録
        + 残念ながらタグでの指定は出来ない
* Replica間の通信はPublic IPで行っている
    - ドメイン名を元にReplica間で通信を行うため
    - 通信はgoogleの域内で閉じておりワンホップでアクセスできる
        + 課金対象にはなってしまう
* hostnameの書き換え
    - DHCPでIPを取得するタイミングでhostnameを上書き
    - set-hostnameに細工
        + https://gist.github.com/yuki-takeichi/3080521322f0f1d159ea6a343e2323e6

---
# API Server

---
# API Serverの構成
* 1LB + 1instance group
    - parse-serverとwebhook serverが同一instanceに同居
    - 2LB + 2instance group構成だとlatency5倍ぐらいになり諦めた
        + public LBだとmicroserviceライクなアーキテクチャは厳しい
        + internal LBがGAになったら再度検討したい
* mongoへはprivate IPで接続
    - /etc/hostsに追記
        + mongoのreplica追加の際にはデプロイにより更新
        + もう少しスマートにしたい...
* firewall
    - LBのpublic IPからのport 80アクセスのみ許可
    - Cloud Shellを使うためにGoogleの出口IPからのport22へのアクセスを許可
        + Cloud Shellの出口IPレンジは限定されて欲しい...

---
# プロビジョニング
* packerでimageを作成
    - googlecompute builder
        + NVMe向けimage familyはサポートしてない (PR出したがちっともMergeされない...)
        + image familyには対応してない
    - ansible provisioner
        + GCE instacne上で実行するにはbuilderのoptionにuse_internal: trueを追加

---
# 起動時間
* `systemd-analyze` での分析
    - systemd起動後はnetworkのonline待ちが5secと最長
* 平均20secほどで起動し終わるのでinstance groupのinitial delayは30secに設定
* systemd unitファイルにAfter=network.targetを指定
    - onlineになったらserver processを起動するようにしている

---
![スクリーンショット 2016-10-19 10.59.05.png](https://qiita-image-store.s3.amazonaws.com/0/2284/5657badb-c373-5779-106c-e8b44fc4d0ac.png)

---
# デプロイ
* gcloud compute instance-groups recreate-instances
* rolling updateが早くGAになって欲しい...!

---
# モニタリング
* Stackdriver agent
    - CPU, mem, I/O など各種メトリクス
    - process死活監視
* newrelicと併用

---
# log周り
* Papertrail
* GCE上にElasticSearchを立てJSONを流している
* Stackdriver Loggingは最初にスキーマが決められてしまい柔軟性に欠ける...

---
# instance group
* スケールしきい値はほぼデフォルトで運用
    - CPU負荷をスケール基準
* Auto scalerの挙動が面白い
    - 急激な負荷が来ると一気に10台ぐらい増えたりする

---
# LB
* HTTPS load balancing
    - TLSはLB終端
    - セキュリティ要件次第ではinstance終端させた方が良い場合も
* health check
    - parse-serverに専用のendpointを作成
* LBの作成がものすごく早い
    - 本当に10secぐらいで作成が完了する
    - [ウォーミングアップ無しで1MQPS捌けちゃうらしい...](http://qiita.com/kazunori279/items/8d2417c8510021c697e7)

---
# Push通知

---
![gcpug_shonan_figures.002.png](https://qiita-image-store.s3.amazonaws.com/0/2284/0e3b2b43-3646-1f31-2d2f-825efbaddb17.png)

---
# [Gaurun](https://github.com/mercari/gaurun)をGCE上で動かしている
* GCE + instance group
    - workerをスケールできるようにしている
* 間にCloud PubSubを挟んでいる
    - 予約プッシュを送る際のpersistentなqueueとして利用
    - Subscriberから受け取ったdevicetokenをgaurunに流している

---
# GAE SEによる開封通知の受信
* 開封通知のような急激なトラフィックを捌くのにGAE SEは最適
    - 爆速スケールの恩恵
* 開封データはDatastoreに保存
    - BigQueryにexportして解析

---
![スクリーンショット 2016-10-11 14.23.13_12.png](https://qiita-image-store.s3.amazonaws.com/0/2284/740c103f-55ac-114f-e46d-d464006d1abf.png)

---
# 写真の移行

---
# 写真の移行
* GCSを写真ストレージとして活用
    - nearlineに移行することも検討
        + オリジナルサイズの画像はフォトブック作成時しか使用されないため
* Cloud PubSub
    - SubscriberがParseのストレージから写真をDLしGCSにUL

---
# 移行した感想

---
# GCEが洗練されている
* instanceの立ち上げが早い
    - 開発時にストレスを感じない
* 8core & 500GB ($290/mth)で最高帯域が確保できる安心感
    - Network: 16Gbps
    - Disk: 15,000 IOPS & 240MB/s
    - これだけ出れば大抵のケースでは十分
* Metadata serverの利便性
    - 追加したssh keyが各instanceに反映される
    - Regionを跨ぐKVSなのでconfig serverとして使える
        + `?wait_for_change=true` による更新通知
* instance名を決められる
    - 管理しやすい
    - 自動的に割り振られるドメイン名でアクセスできる
        + [instance-name].c.[project-id].internal
        + IPをいちいち調べに行く必要が無く便利

---
# 開発者フレンドリー
* web consoleがサクサク動いてくれる
    - loadingで待たされることなくストレスを感じない
    - instance以外のリソースの作成も早いように感じる
* APIがきれいに設計されていてわかりやすい
* ドキュメントが充実している
    - 知りたいことは大体公式ドキュメントに書いてあった
    - 必要に応じてwhite paperや論文なども

---
# 要望
* IAM周りやその他サービスの拡充に期待
    - AWSにあってGCPにない機能はまだまだ多い
* シンプルさや使い勝手の良さは維持して欲しい

---
# まとめ
* 基本機能が洗練されておりスムーズな移行が実現できた
* パフォーマンスに関しての苦労は(今のところ)ない
* 謎技術に驚くことしばしば
* 欲しい機能が用意されておらず自分たちで作らないといけない部分もある

---
# ありがとうございました