builderscon 2017 Tokyo: Building high performance push notification server in Go
GoのOSSのGaurunを使ってアプリユーザーにプッシュ通知を送るための話。
※聞きながら書いたので、雑になってます
プッシュ通知の仕組み
Push通知のためのサービスを通して、ユーザーに通知が行く。
APNs GCM/FCMなどを使った話をします。
APNs
種類
GCM/FCM
- HTTPS
- サーバーキー
GoogleはFCM使えと言っている。
基本はJSONペイロードをcurlで投げるだけでpush通知を投げれる。
ネットワークを介す処理だから遅い
APNsとGCM/FCMとの通信には、ある程度時間がかかる。これはサーバーがアメリカにあったりするからです。特にAPNsは結構遅い。
構成例
サーバー -> nginx -> Gaurun -> HTTP2 -> APNs/GCMなど -> アプリ
通知するタイミングは、お気に入りつけたや購入したコメントしたなど。
出来れば短時間で対象ユーザーに送りたい。
短時間で大量にさばくには、ネットワークレイテンシが高い問題を解消する必要があった。(これが難しい)
旧メルカリの構成
通知が遅くなる問題を見たら、処理と通知が同期的に行われていたから、ネットワークレイテンシと比例して遅くなる。またバッチ処理もあまり効率が良くなかったので、全ユーザー通知も時間がかかっていた。
通知を非同期にした
APIサーバーが通知Queueに送って、それらを順次処理していく形にした。これで劇的なパフォーマンスの改良が可能になった。QueueはQ4Mを使った。
PHPがそもそも遅いので限界があった
PHPは並行処理が苦手なので、そこまで速くならなかった。
Goと相性が良かった
ネットワークレイテンシを下げる必要がある。スループットを改善する。このような需要とマッチしていた。
Gaurun
プッシュ通知サーバーに使うGoOSS
JSON baseのAPI(HTTP)
提供しているAPI
- プッシュ通知
- モニタリング(errorの数やgoroutineの数)
- コンフィグ
Goを採用した理由
- 高い並行性
- 高いパフォーマンス
- 標準パッケージが使いやすい
構成
- HTTPサーバー
- APNs GCM/FCMのプロキシ
- queueと並行処理が出来る
gorutineはwerkerはpusherを持っている。
werkerはpusherの数しか知らない。pusherはpushするだけ。
コネクションハンドリング
http.Clientを使っている(net/http) 設定は、http.Transportを使っている。昔は結構自力で書いてたけど、いまは標準パッケージにお任せしている。
Timeoutについて
サーバーのタイムアウトなのか?クライアントのタイムアウトなのか?接続する時のタイムアウトなのか?とにかく色んなタイムアウトがある。
net/httpパッケージ
- net.Dial
- http.Transport
- http.Client
- http.Server
上記それぞれにTimeoutが用意されている
Gaurunのパフォーマンスを上げる
TOMLで設定をチューニングする必要があります。なぜなら、初期設定のままだと、結構保守的な設定になっています。
- core.workers ワーカーの数
- core.queues channelのサイズ
などなど、色々な設定が出来る。
pusherの数
core.workers x core.pusher_max を調整して、メモリを無駄なく使うと良い。
Keep-Aliveの数
keep-aliveはあまりにも大きすぎる数値を指定すると、場合によってはAPNsなどに拒否されることがあったりするかも?
Bulk enqueue
JSONペイロードのnotification[]の中に複数指定して、リクエストすればおk。デフォルトでは、100件までとしている。
Device tokenのスクリーニング
無効化されたtokenを定期的に削除したい。無駄に送信するとパフォーマンスが落ちるので大事です。Gaurunでは成功、失敗が error_logで取れるので、これを使えばスクリーニングが出来ます。