Spotify が連鎖的なエラーの診断から学んだこと
苦労話は必ずしも悲劇で終わるとは限りません。イノベーションには、賢くリスクを負うことも必要です。The New York Timesundefined の CTO、Nick Rockwell 氏は、「正しいリスクテイク」は未知の分野で起こり、失敗はポジティブな結果、つまり将来に活かせる学習体験になり得ると語っています。
このシリーズでは、HashiCorp の Seth Vargo 氏がいかに障害から復旧したか、Vogue の Kenton Jacobsen 氏が「フェイルファスト」 (早く失敗して、迅速に修正すること) を実行している方法など、テクノロジー業界のリーダー達のエピソードをご紹介してきました。これらのエピソードから私たちは、データベースの再起動やバックエンドの切り替えなどのごく日常的な変更であっても、時には予期せぬエラーにつながることを学びました。しかし、知識や経験が豊富なチームは、発生したエラーを解決するためのツールやプロセスをすでに備えているものです。このブログ記事では、Spotify で Principal Engineer を務める Niklas Gustavsson 氏が直面した、日常的であるはずのライブコンテンツ (本番環境でエンドユーザーがアクセス可能) の変更後にコンテンツが再生不能になった事例と、それから得た教訓、同氏お気に入りのデバッグツールについてご紹介します。
Spotify はレガシーシステム (古い Spotify クライアントのユーザーなど) 向けのオーディオコンテンツを「プロダクションストレージ」と呼ばれる、少しカスタマイズされた nginx を使って配信しています。クライアントはアクセスポイント (認証を管理しコンテンツをルーティングする境界サービス) と通信し、アクセスポイントはプロダクションストレージと、プロダクションストレージはプロキシと、プロキシはマスターストレージ (すべてのオーディオファイルのオリジン) と通信を行う仕組みになっています。
同社は今年、すべてのオーディオファイルを含むバックエンドストレージを Google Cloud Storage (GCS) に移行しました。数ペタバイトものデータを GCS に移行した後、プロキシをリダイレクトすることで GCS をオリジンに設定しました。Google は、それまで使用していたソリューションの S3 と同じ API を提供しているので、簡単な作業になるはずでした。
新しいシステムは事前にテストしていましたが、それにもかかわらず、切り替え後にオーストラリアとニュージーランドから、再生できない新しいコンテンツがあるという報告を受けました。新しいコンテンツが再生できないと聞いてアーティストやレーベルが「不安になる」のは当然のことです。問題を追跡するインシデントレポートを作成した後、Gustavsson 氏は「お気に入りのデバッグツール」である ngrep を使用しました。ngrep は、ローカルシステム上のファイルに対する通常の grep と同じように、サーバーが送受信する未加工のネットワークトラフィックを洗い出し、特定のパターンに一致するトラフィックをリアルタイムでキャプチャするツールです。
ngrep によって、GCS からのレスポンスに Accept-Ranges: bytes
レスポンスヘッダーが存在しないことがわかりました。GCS へのリクエストには range が使用されていなかったものの、これが GCS と S3 の唯一の違いであったため、さらに調査を進めました。
nginx は GCS に対して range リクエストを実行しませんが、Spotify のクライアントアプリケーションと、クライアントと nginx の間のアクセスポイントは、どちらも range リクエストを実行します。デフォルトでは、nginx はキャッシュされたオブジェクトに Accept-Ranges
レスポンスヘッダーが存在することを確認し、range リクエストに応答できることを認識します。今回の場合はそのヘッダーがないため、range リクエストに対して HTTP ステータスコード206およびリクエストされた指定の範囲のコンテンツを返すのではなく、HTTP ステータスコード200およびリクエストされたオブジェクト全体を返すことになります。これは規則に準拠した動作ではありますが、nginx のレスポンスが以前とは異なるため、ダウンストリームで問題が発生する原因となったのです。
音楽ファイル全体を返すと、ダウンストリームで2つのバグが発生することが明らかになりました。アクセスポイントは、部分的でないレスポンスに対応する設計ではないため、不正な Content-Length
のレスポンスをクライアントに送信していました。クライアントアプリケーションは、不正な Content-Length
に対処できないため、トラックを再生できなくなっていたのです。
幸いなことに、Accept-Ranges
ヘッダーなしでアップストリームからコンテンツを受け取った場合でも、range リクエストに従い、これまでと同様に指定された範囲の部分的なコンテンツを返すよう nginx を設定することで問題を解決できました。また、nginx にキャッシュされたファイルを確認することで、どのファイルにレスポンスヘッダーがないかを調べ、システムからパージすることができました。
チェーンを遡る
このインシデントの発生後、Gustavsson 氏はチームとともに、問題がないことを確認するため、チェーンの他の部分を調べました。nginx を再設定することで Accept-Ranges
ヘッダーへの依存は取り除かれたものの、GCS はデフォルトではヘッダーの設定をサポートしていないため、追加措置として nginx と GCS 間のプロキシによって、常にこのヘッダーが追加される設定に変更しました。
アクセスポイントのバグも修正され、range リクエストに対するフルレスポンスを適切に処理できるようになりました。これによってアクセスポイントはクライアントに不正な Content-Length のエラーを返さなくなりましたが、クライアントの修正も進行中であり、今後はこうした例外的なエラーを処理できるようになります (障害が発生した場合、「乱暴な方法ではなく、適切な方法で」処理されます)。
今後も引き続き、数か月にわたって業界のリーダー達の苦労話をご紹介していきます。ご期待ください。