Gaurun でかんたん構築! ユーザーごとに個別にプッシュ通知出来るシステムの作り方
大島 雅人
こんにちは、開発支援Gでインフラの運用をしている大島です。
本記事では、スタディサプリENGLISHでユーザーごとに個別にプッシュ通知を送る際の仕組みについて検討したことを含めて説明していきます。
( 前置き ) スタディサプリ ENGLISHで個別にプッシュ通知を送りたい事情が出てきた
スタディサプリENGLISHは、ユーザーのTOEICの点数UPにより貢献するために、『パーソナルコーチプラン』というサービスを新しく立ち上げました。このサービスでは、コーチがチャットや電話を利用してユーザーの学習に寄り添うというものです。その際にユーザーに対し、「個別に返信がきました」や「電話がかかってきました」といったプッシュ通知を出す機能が要件として挙がりました。
SaaSのAPIを利用したりサーバー側で送信処理を実装してもいいけど…
これまでスタディサプリENGLISHでは、外部のSaaSを利用してきました。主な用途は、「今日も勉強がんばりましょう!」といった文面を全会員へ送ったり、有料会員のみにセグメントを分けて送るといった程度だったので、外部SaaSでも充分でした。
しかしこのSaaSでは、個別にプッシュ通知を送る処理には向いておらず、これを実現するにはそれ用にデータの持ち方などを変更する必要がありました。そもそもサービスの重要な機能を外部のSaaSの事情に依存させるというのはリスクがあります。
一方で、プッシュ通知は様々なライブラリが各言語で用意されているため、そこまで大変ではないのではという話になりました。弊社のサーバーサイドエンジニア不足1)Scalaエンジニア募集しております・・!と開発リソースに負荷をかけないようにしたいという意図があったのでこのたび通知基盤を自前で構築することにしました。
メルカリ製のGaurunを採用しました
いろいろ検討した結果、メルカリが開発しているGaurunを採用することにしました。
上記のブログに詳細な説明がありますが、Gaurunは即座にレスポンスを返し、Goの並行処理の仕組みであるgoroutineを使って非同期でAppleやGoogleのサーバーへプッシュ通知を依頼するリクエストを送信する処理を実行してくれるというアーキテクチャです。
今回は、スケジュールが短かったというのと以下のようなことを考えて採用しました。
- スタディサプリENGLISHはマイクロサービス化されているので、GaurunをDockerコンテナとして起動するだけでよい
- iOS、Android両対応
- goroutineで非同期に処理を実行してくれる
- スタディサプリENGLISHはECSで運用しているので、Gaurunのリソースが足りなくてもコンテナ数を増やせばスケールできる
検討した内容はブログの後半に記載していますので、もしご興味がある方は是非最後までご覧になっていってください。
GaurunをDockerコンテナとして動かす
Gaurunはバイナリとしてビルドされたものが提供されているので、それを動かすだけで済みます。Dockerはとても便利ですね。
Dockerfileでgaurunのバイナリを持ってきてgaurun -c gaurun.tomlするだけです。
gaurun.tpl.toml
の中身は環境変数化しておき、start.sh
の中でenvsubst
を使って環境変数をもとにtomlを生成して設定を切り替えられるようにしておくだけです。
FROM alpine:3.6
ENV GAURUN_VERSION=0.9.1
RUN apk add --no-cache curl gettext
RUN curl -LO https://github.com/recruit-mp/gaurun/releases/download/v${GAURUN_VERSION}/gaurun-linux-amd64-${GAURUN_VERSION}.tar.gz
RUN tar xvzf gaurun-linux-amd64-${GAURUN_VERSION}.tar.gz -C $GOPATH/bin --strip-components 1
COPY start.sh start.sh
RUN chmod +x start.sh
COPY gaurun.tpl.toml gaurun.tpl.toml
CMD ./start.sh
ちょっとしたハマりポイント:iOSの通常のプッシュ通知とSilent Remote Push Notificationは証明書が別に必要
iOSではチャットや電話やりとりをするためにSilent Remote Push Notificationを利用しています。Silent Remote Push Notificationとは、簡単に言うなら、ユーザーの端末にプッシュ通知が届いた時にメッセージやバナーを表示せずにアプリケーションをバックグラウンドで起動するためのものです。
ちょっと想定外だったのが、このiOSのSilent Remote Push Notificationは通常のプッシュ通知の証明書と別の証明書を使う必要はあるということでした。
GaurunはGaurun一つに対してAPNs、FCMそれぞれ最大一つずつ送る前提になっていました。2)大抵このユースケースで事足りるので問題ないです。
iOS用に証明書を2つ設定することはできないので、今回は複数のGaurunのサービスを立ち上げて、ALBのlister ruleで振り分けることで回避しました。Dockerコンテナで運用してるからこそ気軽にこういうことができるのでとても便利ですね。
以上がGaurunによるシステム構築の説明です。
おまけ: Gaurun採用までの道のり
ここからは、どのような検討過程でGaurunの採用に至ったかを説明していきます。ご興味がある方はご覧になっていってください。
そもそもプッシュ通知基盤ってどうやって作るの?
普通にぱっと最初に思いつくものだと、APIサーバーからAPNsやFCMを直接叩いたり、Amazon SNSを使って送信する方法です。
この方式には、同期的に外部のAPI呼び出しをすることになるのでレスポンスの返しが遅くなるという課題があります。SNSからのレスポンスが遅くなればそれだけ処理が遅延するのと、スタディサプリENGLISHで採用しているPlayFrameworkは非同期前提のアーキテクチャになっているので、synchronousな処理があるとスレッドを使い切って他のリクエストが受け付けられなくなってしまうという問題が発生してしまいます。
案1: Akkaを使ってジョブキューに入れる
そこで外部APIの呼び出しする部分をジョブキューに入れて、workerで外部API呼び出しをやってもらうということを考えます。RailsだったらsidekiqをたててActiveJobを書いてというパターンが一般的ですね。
スタディサプリENGLISHのサーバーサイドはPlay Frameworkで作られているので、こういうことをやる場合はAkkaのactorを使えば多分出来るはずということは分かってはいたのですが、今まで開発経験がなく、actorの冗長化や管理の知見もまだない、更にカットオーバーまでのスケジュールが短かったということもあり断念しました。
案2: AWS Kinesis を使う
先ほど述べたSNSを使う方法では、間にキューシステムを入れることで問題を回避できます。例えば、SQSとRedisとなんらかのWorkerを組み合わせるというのも一般的ですが、Workerの運用もあるのでKinesisを使う案を検討しました。
この仕組みの面白いところは、矢印が逆になっており、consumerがデータを取得しに行くことで非同期な処理が出来るというところです。これなら後からconsumerを増やして別の処理をさせることもできるし、プラガブルで自由度がとても高いですね。
ただし、Akka同様にKinesisも本番導入実績がなかったのと、スケジュール的に厳しそうということであきらめました。プッシュ通知以外にも活用できるのでいつかは入れたいと思っているアーキテクチャです。
まとめ
ECS化を進めていたおかげで短いスケジュールでも簡単にシステムを構築出来ました。私もこういった基盤的なサービスを担うソフトウェア開発をできるようになりたいというのが目標ですが、まずは目の前の改善を地道にコツコツとやっていきたいと思います!
弊社のプロダクトはまだまだ改善ポイントがたくさんあるので、ECSやKubernetesを使ったインフラの運用にご興味のある方がいらっしゃいましたら是非お声がけください!