ある昼下がり。保守担当者からもらったチャット。
「サーバーダウンしてるっぽいんだけど、心当たりとかあります?」
心当たりがあるのに秘匿してたら困るでしょう、と思いつつ。
「心当たりはないですが調べてみますね」と返す。
これは心の余裕をなくしたプログラマーたちが置いていった、負の遺産の物語―――。
前提の確認
お守りをしているシステムがよくダウンすると連絡を受けました。
システム構成
件のシステムは3台のサーバーから構成されています。
- フロント部分を担うWebサーバー
- バックエンド部分を担うAPIサーバー
- DBサーバー
間にあるサーバーを飛び越えて通信するようなことはしません。
クライアントからのリクエストはWebサーバーが処理します。
WebサーバーはDBサーバーと直接通信することはなく、必要なAPIを叩くのみです。
Webサーバーからリクエストを受け取ったAPIサーバーがDBサーバーからデータを取り出します。
システムの死活監視について
死活監視サービスを利用しており、システムのダウンを検知すると指定されたメールアドレスに通知を送信します。以下に死活監視の流れを記載します。
- 死活監視サービスからWebサーバーへ生存確認リクエストを送信
- WebサーバーがAPIサーバーへ生存確認のリクエストを送信
- APIサーバーがDBサーバーへSELECT文を発行
- DBサーバーが3の結果をAPIサーバーへ返却
- DBサーバーから応答を受けたAPIサーバーがWebサーバーへレスポンスを返却
- APIサーバーから応答を受けたWebサーバーが死活監視サービスへレスポンスを返却
1~6までが60秒以内に完結しない状態が続くと「ダウン検知」となりメールが飛びます。
このダウン検知メールがめちゃくちゃ大量に来る、というのが受け取った連絡の内容でした。
原因
WebサーバーがAPIサーバーへのリクエスト増幅器になってしまっていた
実際の画面は出せないので、X(旧Twitter)の画面でイメージしていただきたいのですが。
PCでXのホーム画面を開くと、いくつかの情報が目に入ると思います。
①未読通知の件数
②他アカウントでの未読通知の有無
③タブリスト(おすすめ/フォロー中以外にカスタムしているタブのリスト)
④フォロー中のアカウントが参加しているスペースの情報
⑤ログインユーザーの興味に基づいたニュースの一覧
⑥ログインユーザーの興味に基づいたトレンドの一覧
⑦ログインユーザーの興味に基づいたおすすめユーザー
⑧未読DM件数
⑨開かれているタブでのタイムライン
件のシステムでは、この9つの情報をそれぞれ別のAPIで取得するような実装になっていました。
つまり、Webサーバーへアクセスすると、Webサーバーが9本のAPIを叩くようなイメージです。
Webサーバーは10分間に2,000のリクエストを受けながら、APIサーバーに18,000のリクエストを送信していました。
DBサーバーへのアクセスはもっと多くなるでしょう……セルフでリフレクション攻撃をしている状態です……。
ユーザーのリトライ回数が増える
ユーザーはXが重い時、どのような行動に出るでしょうか。
保守担当としては、アクセスが集中している状態なので時間をおいてから再度アクセスしてもらうのが理想です。
保守担当としては。
実際のところは、リロードを連打するのではないかなと思います。あれー、読み込めないなー、となったらリロードするのはまあまあ普通の感覚だと思います。何秒くらい応答がなかったら「遅い」と感じるでしょうか。きっちり測定したことはありませんが、400ミリ秒以下で「いつも通り」くらい、1000ミリ秒は待てないユーザーが多いのではないかなと思います。
10分間で18,000リクエストを捌く羽目になったAPIサーバーは、だんだんレスポンス速度が悪化していきます。APIサーバーのレスポンスが悪くなれば、当然Webサーバーのレスポンスも悪くなります。そして期待した時間内にレスポンスを得られなかったユーザーたちは、リロードを連打します。Webサーバー視点では、正当なるユーザーたちによるDoS攻撃状態であり、APIサーバー視点ではWebサーバーを利用したDDos攻撃状態です……。Webサーバーには同一のユーザーから1秒間に15リクエスト受信しているログも残っていましたので、的外れな予想ではないかなと思います。APIサーバーでは何分かログが途切れている時間がありました。リクエストを捌ききれずダウンしたものと思われます。
対応
API一本化
以下が最大の原因ですので、真っ先に手を打ちました。
件のシステムでは、この9つの情報をそれぞれ別のAPIで取得するような実装になっていました。つまり、Webサーバーへアクセスすると、Webサーバーが9本のAPIを叩くようなイメージです。
すべてのAPIを1本にまとめることはできませんでしたが、それでも2本くらいにはまとめました。
2,000リクエスト-18,000APIコールの状態から、2,000リクエスト⇒A4,000APIコールになるよう実装を修正しました。これだけでもかなり改善しました。
DBレスポンスチューニング
そもそもDBレスポンスもそれほどよくはなかったので、インデックスを貼ったりSQLの見直しをしたりしました。一定の効果は得られましたが、API一本化ほど大きな効果は得られませんでした。
対応結果
ダウン検知のメールが届くことはほぼなくなりました。繁忙期にはたまに届くようですが、それでもほとんど無視できる程度です。保守担当者には喜んでもらえました。お給料には影響しませんでしたが
教訓
ベストプラクティスに逆らわなくてはいけないこともある
対応前は、9種類の情報を得るのに9種類のAPIを叩いていました。APIの設計としては理想的な状態だったかもしれません。ただ、設計としては美しいかもしれないですが、Webサーバーがリクエストを9倍に増幅する攻撃サーバーのようになってしまってはいけません。ユーザーから見えない設計の美しさより、ユーザーに大きく影響するレスポンス速度の方が重要になります。設計の美しさを捨ててでも、必要なAPI数を減らす必要がありました。私は保守担当なので、応答がなかったとして3秒4秒でも待ちますが、そうではない人がほとんどでしょうから。
どこかの何かしらのシステムの負荷軽減にこの記事が役に立てば幸いです。
お読みいただきありがとうございました。
