サーバー冗長化に対応できないアプリケーションの作り方
目次
はじめに
ウェブアプリケーションを運用する際、複数のサーバーを利用して動作させることが一般的です。これは、以下のような運用上の課題に対応するためです。
- サーバーダウンへの対処:サーバーは様々な理由で停止することがあります。複数台のサーバーを稼動させておくことで、一台がダウンしても他のサーバーが機能を維持し、サービスの提供を続けることができます。
- ワークロード最適化:サービスの利用量は、その性質や外部要因によって大きく変動します。例えば、確定申告のようなサービスは、毎年2月から3月の年度末に利用が集中します。また、メディア露出による突発的なアクセス増加にも対応するため、需要に応じてサーバーの台数を調整することが可能です。 サーバーの台数とスペックを適切に設定することで、運用上のコストの最適化と快適なサービス利用体験を両立させることができます。
- 効率的なデプロイ戦略:サーバーの冗長化は、アプリケーション更新時のダウンタイムを最小限に抑えるためにも利用できます。本番環境と同等の代替環境を準備し、新バージョンを先にそちらにデプロイします。テスト後、トラフィックを新しい環境へと切り替えることで、万が一の問題が生じた場合にも迅速に元の環境へ戻すことが可能です。このアプローチにより、更新作業のリスクを軽減しつつ、サービスの継続性を保てます。
昨今では、ウェブアプリケーションのクラウド化が進んでいますが、クラウド環境であっても即座にサーバー冗長化を行い、これらのメリットが享受可能というわけではありません。冗長化によって適切なメリットを得るためには、冗長化で動作させることを意識したソフトウェア開発が不可欠です。
冗長化ができないソフトウェアは、いくつかのNGパターンを利用して実装されています。 そのため、この記事では「どのようなことをすると、サーバー冗長化に対応できないのか」について、具体例を挙げて説明します。
NG例1: サーバー固有のステータスに依存したアプリケーションの実装
アプリケーションが以下のようにサーバー固有のデータを扱う場合、サーバー冗長化には対応できません。
- 1つのリクエストでローカルディスクにファイルを保存し、別のリクエストでそのファイルを利用する場合
- 1つのリクエストでメモリに計算結果を保存し、別のリクエストでその結果を参照する場合
この問題が発生するのは、サービスが複数のサーバーで運用されている場合です。ロードバランサーが次のリクエストを異なるサーバーに割り振る可能性があり、これによりサーバー間のデータの一貫性が失われることがあります。ロードバランサーはサーバー冗長化をサポートするために設計されており、リクエストを効率的に分配する役割を担っていますが、常に同一のサーバーにリクエストを割り振るわけではありません。
一部のロードバランサーでは「スティッキーセッション」と呼ばれる機能を提供しています。この機能を利用すると初回リクエストを受けたサーバーに後続のリクエストも送る設定が可能となります。スティッキーセッションを利用することで問題はある程度解決できますが、サーバーがダウンした場合のデータ損失やサーバー間の負荷分散の不均等は依然として問題となります。
この問題を根本的に解決するためには、ソフトウェアとサーバーが固有の状態を持たないステートレスな設計を目指すことが重要です。具体的な対策は以下の通りです。
- 一時ファイルの共有化: リクエストによって生成される一時ファイルは、すべてのサーバーからアクセス可能な共有ディスクに保存します。これにより、どのサーバーからでもファイルへのアクセスが可能になります。
- 計算結果の集中管理: 計算結果はメモリではなく、RedisやMemcachedなどのキャッシュデータベースや共用データベースに保存します。これにより、任意のサーバーから計算結果にアクセスでき、データの一貫性が保たれます。
- セッションストレージの共有化: ユーザーのログイン状態は通常、セッションとしてサーバー上のローカルファイルに保存されます。しかし、サーバー冗長化を考慮すると、この方法では他のサーバーからセッションにアクセスできません。そのため、セッション情報もキャッシュデータベースや共用データベースに保存する設定が必要です。これにより、どのサーバーからもセッション情報に一貫してアクセス可能となります。
NG例2: フロントサーバーに時間のかかる処理を多数実装する
時間のかかる処理をフロントサーバーに実装すると、その処理を実行している間はサーバーリソースが占有され、リクエストの効率的な割り振りが妨げられる可能性があります。時間のかかる処理が予想される場合、メッセージキューを利用して処理を分離することが望ましいです。このアプローチにより、処理を既存サーバー上で行うか、新たに設置する遅延処理専用のワーカーサーバーに委譲するかの選択が可能になります。これにより、インフラコストを最適化しつつ、システムの応答性を維持することができます。
具体的な実装方法については、以下の関連記事を参照してください。
NG例3: DBマイグレーション戦略を定めない
デプロイ時には、既存のアプリケーションと新バージョンが一時的に同時に動作することがあります。ウェブアプリケーションでは、一般的にMySQLなどのRDSを使用し、特定のテーブル構造に依存することが多いです。この状況で、データベースの構造を変更する際には特に注意が必要です。
例えば、新バージョンで特定のカラムをテーブルから削除すると、旧バージョンがそのカラムに依存している場合、データベースのカラムを削除する瞬間に旧バージョンが機能しなくなる可能性があります。このような場合、サーバーは冗長化されていてもデプロイ時にアプリケーションを停止する必要があります。
対策として、カラム削除を行う場合は複数のバージョンアップを通じて段階的に実行する方法が考えられます。例えば、最初のバージョンではカラムを使用し、次のバージョンでカラムを使用しないように変更し、その後のバージョンでカラムを完全に削除する、という手順です。 また、一般にカラム・テーブルを追加し続ける限りは概ね問題は起こらないため、そもそもカラムを削除しないという方法も採用されるかもしれません。 この問題に関しては不安に思った時に事前に検証できる環境を用意することも大切です。
サーバーの冗長化がデプロイの柔軟性を向上させるためには、適切なDBマイグレーション戦略が不可欠です。そのため、マイグレーションを意識したアプリケーション開発が求められます。
おわりに
本記事では、サーバー冗長化の際に避けるべきいくつかのNGパターンを紹介しました。サーバー冗長化と聞くと、インフラチームに依頼するだけで解決すると考えがちですが、実際には冗長化を効果的に運用するためには、適切なアプリケーション開発が不可欠です。この点を理解し、適切な準備を行うことが、冗長化の成功に繋がります。