こんにちは、WEARバックエンドブロックの天春( @AmagA001 )です。バックエンドの運用・開発に携わっています。WEARはサービス開始から10年ほどの古いVBScriptを使った環境からRuby on Rails環境にシステムリプレイスを行なっています。本記事では、リプレイスの中でも既存環境が複雑で問題や課題が多くあったPUSH通知システムのリプレイスについてご紹介します。
PUSH通知システムとは WEARアプリにPUSH通知を配信するために構築しているシステムのことを呼んでいます。PUSH通知は次の2種類が存在します。
1:1通知:一人のユーザーに対して1回だけ送る通知 1:N通知:同じ通知を同時に複数のユーザーに対して送る通知 リプレイス前のPUSH通知システム リプレイス前のPUSH通知システムはオンプレミスのMicrosoft SQL Server、.NET FrameworkとAWSのサービスで構成されています。AWSのサービスはEC2、DynamoDB、API Gateway、Lambda、SNS、SQSが使われています。開発言語はVBScript、C#、Golang、Pythonです。
1:1用通知処理バッチ(Golang)と1:N用通知処理バッチ(C#)がWindowsバッチサーバーのタスクスケジューラに登録されて定期的に通知配信API(Python)経由で通知を配信していました。通知サービスはAWSのSNS経由でAPNsとFCMを使っていました。
リプレイス前のPUSH通知システムの問題点 通知送信バッチのスケールアウトが出来ない 通知送信バッチはスケールアウトが考慮されてなく決まった時間に通知を送る仕組みになっていたので1:Nの通知の場合、通知が多い時は1日以上の遅延が発生している状態でした。
障害対応・運用が難しい状況 障害・エラーが発生した場合、開発当時の資料と開発メンバーの不在、必要なログデータの不足により原因特定・障害対応・運用に時間がかかりました。原因が判明して修正できたとしても影響範囲が特定できないこととテスト環境がないことも問題でした。
複数の開発言語による運用・改修コストが高い APIとバッチの改修のためにはVBScript、C#、Golang、Python、シェルスクリプトの修正が必要になるため、関連言語の学習コスト発生や経験者が必要になりました。
ステージング環境で通知確認ができない ステージング環境でバッチが動いてない状態だったので通知の改修や追加時にQAテストが出来ず、本番環境で動作確認するしかない状況でした。
リプレイスの背景 WEARサービスにコーディネート動画 1 やフリマ機能 2 の追加により新規通知を追加する必要がありました。既存システムを改修する方法もありましたが、既存システムが複雑すぎて障害・エラー発生時に原因調査・対応が難しい状況だったのでリプレイスを選びました。
リプレイス後のPUSH通知システム
非同期システム・EKS導入 Ruby on Rails環境では ActiveJob を使うことでキューイングライブラリを気にせずジョブの作成、キュー登録、実行が可能です。キューイングライブラリはジョブをキューに登録して非同期でジョブを実行できるライブラリのことです。Railsガイド 3 にも書かれているSidekiq、Resque、Delayed Jobを対象に検討しました。結果、Sidekiqがマルチスレッド対応で大量のジョブ処理に向いていることとメモリあたりのパフォーマンスがいいことでした。WEARには同時に200万回以上の大量通知が発生することもあるのでSidekiqを選定しました。チームメンバーにSidekiqの経験者がいたことも1つの理由でした。
既存配信バッチはMicrosoft SQL Serverのテーブルをキューとして利用してテーブルから通知対象を取得して通知配信後、通知一覧に必要なデータをDynamoDBに登録していました。リプレイス後はバッチをなくしてMicrosoft SQL Serverを使わずに非同期ジョブを利用して直接DynamoDBに通知一覧データを登録しています。 EKS の導入により拡張可能なシステムになったのも大きな変化です。通知配信サービスは Firebase Cloud Messaging(FCM) だけを使うようにしました。
既存システムの問題解決 バッチのスケールアウトが出来ない EKS導入により負荷が多い時の非同期ジョブ処理(Sidekiq)のスケールアウトが可能になりました。オートスケールは KubernetesのHPA(Horizontal Pod Autoscaler) を使っています。
障害対応・運用が難しい状況 エラー検知と障害検知についてはエラーログをSlackに通知することで解消しました。また、Sidekiqのダッシュボード機能によりエラー確認・ジョブ再実行が簡単にできるようになったので運用も楽になりました。
複数の開発言語による改修コストが高い C#・Golangのバッチ処理をやめてRuby on Railsに非同期システムを導入したことで開発言語はRubyだけになりました。
ステージング環境で通知確認ができない 非同期システムのステージング環境を構築したのでQA時に通知確認ができるようになりました。
その他 その他考慮したのは緊急度が高い通知は「critical」キューから配信、既存通知は「default」キューから配信しています。遅延が発生しても問題ない1:Nの通知は「multi」キューに分けることで緊急度・優先度が高い通知に遅延が起きないように考慮しています。これらのキューは処理の性質や負荷が異なるので、キュー単位でReplica数やリソース割り当てができるようにKubernetesのDeploymentを用意しました。Deploymentの定義には以下のようにSidekiq起動時キューを指定しています。
spec:
serviceAccountName: sidekiq
shareProcessNamespace: true
containers:
- name: sidekiq-critical
imagePullPolicy: Always
command: ["bundle", "exec"]
args:
- |
sidekiq \
--verbose \
--queue critical \
--pidfile ./tmp/pids/sidekiq.pid
lifecycle:
preStop:
exec:
command:
[
"/bin/bash",
"-c",
"SIDEKIQ_PID=$(ps aux | grep sidekiq | grep busy | awk '{ print $2 }'); kill -SIGTSTP $SIDEKIQ_PID",
]
EKSについての詳細は以前WEAR部SREチームから公開した記事を参考にしてください。
ローカル開発環境 docker-compose を利用してローカル開発環境から Redis , Sidekiq , dynamodb-local , dynamodb-admin を使っています。
続きは こちら