ステートレス vs ステートフル:デプロイ戦略を左右するアプリケーションの特性

同じアプリケーションのインスタンスが2つ稼働しています。ユーザーがリクエストを送信しました。どのインスタンスが処理するでしょうか?「どちらでもよい、どちらが処理しても結果は変わらない」という答えなら、それはステートレスなアプリケーションです。「以前のリクエストを処理した同じインスタンスでなければならない」という答えなら、ステートフルなアプリケーションです。

この区別は学術的なものではありません。新しいバージョンをどれだけ簡単にデプロイできるか、トラフィック急増時にどれだけ容易にスケールできるか、問題発生時にどれだけ迅速にロールバックできるかを決定づけます。多くのチームが痛い目を見てこのことを学びます。あるサービスで完璧に動作したデプロイパイプラインを、別のサービスに適用したら予期せぬ障害が発生するのです。

アプリケーションがステートレスであるとはどういうことか

ステートレスなアプリケーションは、リクエスト間で何も記憶しません。各リクエストは独立しています。アプリケーションは入力を受け取り、処理し、結果を返し、そのやり取りについてすべてを忘れます。

商品IDを受け取ってデータベースから商品詳細を返すAPIエンドポイントを考えてみてください。すべての呼び出しは自己完結しています。アプリケーションは誰が呼び出したか、以前に何を呼び出したか、その後何が起こるかを気にしません。このAPIの3つのインスタンスをロードバランサーの背後で実行している場合、どのインスタンスでも任意のリクエストを処理できます。それらは互換性があります。

ステートレスなアプリケーションの一般的な例は以下の通りです:

次のフローチャートは、ステートレスアプリケーションとステートフルアプリケーションのデプロイパスを対比しています。

flowchart TD A[デプロイ開始] --> B{アプリケーションの種類?} B -->|ステートレス| C[新しいバージョンで新インスタンスを起動] C --> D[徐々にトラフィックを新インスタンスに移行] D --> E[旧インスタンスをドレインして削除] E --> F[ロールバック?トラフィックを戻すだけ] B -->|ステートフル| G[データ移行とセッション処理を計画] G --> H[ステートフルセットで新インスタンスを起動] H --> I[データを慎重に移行、またはスティッキーセッションを使用] I --> J[データ破損やセッション損失を監視] J --> K[ロールバックにはデータ形式の互換性が必要]
  • 共有データベースに対して読み書きを行うREST API
  • ファイルを変換して結果を返す画像処理サービス
  • セッションデータを保存せずにトークンを検証する認証サービス
  • すべての状態をCookieやURLパラメータに保存するサーバーサイドレンダリングのWebページ

ステートレスなアプリケーションは、デプロイ、スケーリング、復旧が最も容易です。複数のバージョンを並行して実行し、徐々にトラフィックを移行し、問題が発生した場合は即座に切り戻すことができます。

アプリケーションがステートフルであるとはどういうことか

ステートフルなアプリケーションは、リクエスト間で何かを記憶する必要があります。その「何か」を状態(ステート)と呼びます。ショッピングカート、アクティブなチャットセッション、進行中のファイルアップロード、サーバーメモリに保存されたユーザーの認証セッションなどが該当します。

Eコマースアプリケーションを考えてみましょう。ユーザーがカートに商品を追加します。カートのデータは、そのリクエストを処理したサーバー上に存在します。次のリクエストが別のサーバーに送られると、そのサーバーはカートについて知りません。ユーザーには空のカートが表示されます。これがステートフルな問題です。

ステートフルなアプリケーションは、以下のような場所に状態を保存します:

  • アプリケーションサーバーのメモリ内(セッションデータ)
  • サーバー上のローカルファイル(アップロードされたファイル、一時データ)
  • アプリケーションと一緒に実行される組み込みデータベース
  • インスタンス間で共有されないアプリケーションレベルのキャッシュ

課題は状態が存在すること自体ではありません。課題は状態がどこにあるかです。状態が特定のインスタンスに結びついている場合、データを失うことなく自由にトラフィックをルーティングしたり、インスタンスを交換したり、スケールダウンしたりすることはできません。

ステートレスアプリケーションがデプロイをいかに簡素化するか

ステートレスなアプリケーションのデプロイは簡単です。新しいバージョンで新しいインスタンスを古いインスタンスと並行して起動します。ロードバランサーが徐々にトラフィックを新しいインスタンスに振り向けます。すべてのトラフィックが新しいバージョンに移行したら、古いインスタンスをシャットダウンします。

新しいバージョンにバグがあった場合は、プロセスを逆にします。トラフィックを古いインスタンスに戻します。データ移行も、スキーマ変更も、セッション復旧も必要ありません。ロールバックは単なるトラフィックの再ルーティングです。

スケーリングも同じロジックに従います。より多くのキャパシティが必要ですか?インスタンスを追加で起動します。トラフィックが減りましたか?インスタンスを削除します。再配布するデータはありません。すべてのインスタンスは同一で、使い捨て可能です。

このシンプルさこそが、マイクロサービスアーキテクチャがステートレスな設計を好む理由です。各サービスは、他のインスタンスが何を記憶しているかを気にすることなく、独立してデプロイできます。

ステートフルアプリケーションがより注意を要する理由

ステートフルなアプリケーションをデプロイするということは、失ったり破損したりできないデータを扱うことを意味します。アプリケーションがセッションをメモリに保存している場合、すべてのインスタンスを一度に置き換えると、アクティブなすべてのユーザーがログアウトされます。アプリケーションがローカルファイルに書き込む場合、それらのファイルを移行するか、新しいバージョンが古いデータ形式と互換性がある必要があります。

ステートフルなアプリケーションをデプロイするための一般的な戦略は以下の通りです:

状態をアプリケーションの外部に移動する。 セッションを共有のRedisクラスターやデータベースに保存します。アップロードされたファイルをS3のようなオブジェクトストレージに保存します。これにより、デプロイの観点からアプリケーションはステートレスになります。状態が他の場所にあるため、アプリケーションは自由に交換できます。

スティッキーセッションを使用する。 ロードバランサーが同じユーザーを同じインスタンスに送信するように設定します。これは機能しますが、デプロイ中に問題を引き起こします。アクティブなユーザーを中断せずにインスタンスからトラフィックをドレインすることはできません。セッションの期限切れを待つ必要があるため、ローリングアップデートが遅くなります。

ステートフルセットまたはオペレーターを使用する。 KubernetesのStatefulSetやデータベースオペレーターは、ステートフルなアプリケーションのデプロイの複雑さを処理します。これらは、インスタンスが順序正しく起動および停止され、データが保持され、ネットワークIDが安定することを保証します。ただし、特定のステートフルアプリケーションの動作を理解する必要があります。

データ移行を計画する。 新しいバージョンがデータの保存方法を変更する場合、デプロイには移行ステップを含める必要があります。古いバージョンが新しいデータ形式を理解できない可能性があるため、ロールバックはリスクを伴います。これはデータベーススキーマの変更でよく見られます。

チームへの実際の影響

ステートレスとステートフルの区別は、技術的な決定だけでなく、チームの運用方法にも影響を与えます。

ステートレスなアプリケーションは、高速で頻繁なデプロイを可能にします。チームメンバーは誰でも、データ損失を心配することなく新しいバージョンをデプロイできます。ロールバックは安全かつ迅速です。これにより、デプロイへの恐れが軽減され、より小さく、より頻繁なリリースが促進されます。

ステートフルなアプリケーションは、慎重な調整を必要とします。デプロイは、データ移行、セッション処理、ロールバックの互換性を考慮して計画する必要があります。チームはしばしば、ステートフルなサービスのために特別な手順を開発します。デプロイウィンドウ、承認ゲート、手動検証手順などです。これにより、デリバリーが遅くなります。

組織に両方のタイプのアプリケーションがある場合、単一のデプロイプロセスがすべてに機能するとは期待しないでください。ステートレスなAPIのためのパイプラインは、データベースの移行やユーザーセッションを管理するステートフルなサービスには適合しません。

次のデプロイのためのクイックチェックリスト

デプロイを計画する前に、以下の質問を自問してください:

  • アプリケーションは、再起動後も保持する必要のあるデータをローカルに保存していますか?
  • ユーザーセッションはメモリ内に保存されていますか、それとも共有の外部ストアに保存されていますか?
  • どのインスタンスでも任意のリクエストを処理できますか、それともルーティングはどのインスタンスがデータを持っているかに依存しますか?
  • 以前のバージョンにロールバックした場合、データ形式はまだ読み取り可能ですか?
  • データの競合なしに2つのバージョンを並行して実行できますか?

ローカルストレージとセッション依存性に関するすべての質問に「いいえ」と答えた場合、それはステートレスなアプリケーションです。自由にデプロイしてください。いずれかの質問に「はい」と答えた場合、デプロイ戦略を設計する前に状態管理を計画する必要があります。

まとめ

ステートレスなアプリケーションは自由をもたらします。ステートフルなアプリケーションは制約をもたらします。誤りは、これらを同じように扱うことです。デプロイパイプラインを設計する前に、データがどこにあるかを理解してください。データがアプリケーションインスタンスの内部にある場合、デプロイ戦略はそのデータを考慮する必要があります。データが外部にある場合、アプリケーションを使い捨て可能なものとして扱い、自信を持ってデプロイできます。