Cookie、セッション、パーシステンス
はじめに
HTTP(HyperText Transfer Protocol)は、サーバーからクライアントへデータを転送するステートレスなリクエスト/レスポンスモデルをサポートするように設計されました。最初のバージョンである1.0では、リクエストと接続の比率は完全に1:1でした(つまり、1つの接続につき1組のリクエストとレスポンスをサポートしていました)。
バージョン1.1では、この比率がN:1、つまり1つの接続で複数のリクエストを処理できるように拡張されました。これは、サーバーからクライアントに転送する必要がある多数のオブジェクトや要素など、Webページの複雑化に対応するためです。
2.0の採用後も、HTTPは1つの接続で複数のリクエストを処理するモデルを引き続きサポートしました。最も抜本的な変更点は、ヘッダーのやり取りと、テキストベースからバイナリ形式への転送方式の移行です。
いつしかHTTPは、サーバーからクライアントへテキストや画像を転送するだけの単純な仕組みではなく、アプリケーションのプラットフォームへと発展しました。ブラウザが広く普及していること、クロスプラットフォームであること、複数のオペレーティングシステムや環境をサポートするための多大なコストをかけずにアプリケーションを容易に展開できることは、確かに魅力的でした。しかし、HTTPはアプリケーションの転送プロトコルとして設計されたものではなく、文書を転送するために設計されたものです。APIなど、現代のHTTPの用途でも、文書に似たペイロードが前提となっています。その好例が、キーと値のペアで構成され、テキストとして転送されるデータ形式のJSONです。文書とアプリケーションプロトコルは一般にどちらもテキストベースですが、共通点はそこまでです。
従来のアプリケーションでは何らかの方法で状態を維持する必要がありますが、ドキュメントにはその必要がありません。アプリケーションは論理的なフローとプロセスに基づいて構築されており、いずれもその時点でユーザーがどの段階にいるかをアプリケーションが把握する必要があります。そのために状態が必要です。HTTPは本質的にステートレスであるにもかかわらず、Webにおける事実上のアプリケーション転送プロトコルとなっています。技術史上、最も広く受け入れられた有用な工夫の1つとして、アプリケーションの利用中に状態を追跡できる仕組みがHTTPに追加されました。この「工夫」を実現するのが、セッションとCookieです。
セッション
ステートレスからステートフルへの変換
セッションは、Webサーバーやアプリケーションサーバーが状態を維持するための仕組みです。この単純なメモリ領域は、Webサーバーまたはアプリケーションサーバーへの各TCP接続に関連付けられ、HTTPベースのアプリケーションで情報を保持するインメモリストレージとして機能します。
ユーザーが初めてサーバーに接続すると、セッションが作成され、その接続に関連付けられます。開発者は、そのセッションをアプリケーションに関連するデータの保存場所として使用します。保存されるデータは、顧客IDなどの重要な情報から、サイトのトップページの表示設定など、比較的重要度の低い情報まで多岐にわたります。
セッションの有用性を示す最も分かりやすい例はショッピングカートです。ほとんどの人が一度はオンラインで買い物をしたことがあるからです。ショッピングカート内の商品が「セッション」の間保持されるのは、カート内の各商品が何らかの形でサーバー上のセッションに記録されているためです。もう1つの好例は、ウィザード形式で製品を構成またはカスタマイズするアプリケーションです。こうした「ミニ」アプリケーションでは、一連のオプションを確認しながら選択できます。最後には、追加したさまざまな機能の見積額に驚くことも少なくありません。各「オプション画面」を順に進むと、それまでに選択したオプションがセッションに保存されるため、簡単に取得、追加、削除できます。
モダンアプリケーションはステートレスになるよう設計されていますが、そのアーキテクチャが必ずしもこの原則に従っているとは限りません。最新のスケーリング手法では、シャーディングのようなアーキテクチャパターンがよく使用されます。シャーディングでは、ユーザー名やアカウント番号など、インデックス化可能なデータに基づいてリクエストをルーティングする必要があります。適切なルーティングとアプリケーションの動作を確保するため、インデックス化可能なデータを各リクエストに付随させるという点で、これは一種のステートフルなアプローチです。この意味で、モダンな「ステートレス」アプリケーションやAPIにも、ステートフルだった従来のアプリケーションと同様の運用管理が必要になることがよくあります。
問題は、セッションが接続に紐付けられており、接続が長時間アイドル状態のままだとタイムアウトすることです。また、接続における「長時間」の定義は、セッションの場合とは大きく異なります。たとえば、一部のWebサーバーのデフォルト設定では、接続が15秒間アイドル状態、つまりリクエストが行われていない状態になると、その接続を切断します。一方、同じWebサーバー上のセッションは、デフォルトでは300秒(5分)にわたってメモリ内に保持されます。この2つは明らかに整合していません。セッションが接続に紐付けられているのであれば、接続がタイムアウトした時点で、そのセッションを保持していても意味がないためです。
この不一致は、接続のタイムアウト値をセッションのタイムアウト値に合わせて延長するだけで解消できると思うかもしれません。しかし、タイムアウトを延長すると、使用されるかどうかわからない接続を維持するためにメモリを消費する可能性があります。その結果、サーバーが同時に処理できるユーザー数が減少し、最終的にはパフォーマンスが低下するおそれがあります。一方、セッションのタイムアウト値を接続のタイムアウト値に合わせて短縮することも望ましくありません。多くのユーザーは、商品を見て回ったり、新しく購入する商品をカスタマイズしたりするのに5分以上かかるためです。
重要な注意事項: HTTP/2はこれらの問題の一部に対処する一方で、状態の維持に関する別の問題をもたらします。仕様上の要件ではありませんが、主要なブラウザーではTLS/SSL経由のHTTP/2のみが許可されています。どちらのプロトコルでも、再ネゴシエーションによるパフォーマンス低下を避けるために接続の維持が必要であり、そのためにはセッションを認識するステートフルな動作が求められます。
その結果、非アクティブ状態が原因で関連する接続が終了した後も、セッションがサーバーのメモリに残り続け、貴重なリソースを消費します。さらに、アプリケーションが動作しないユーザーの不満を招く可能性もあります。
幸い、この問題はCookieを使用することで解決できます。
Cookie
パンくずをたどればホームに戻る
Cookieは、ブラウザーによってクライアントに保存される小さなデータです。Cookieには、ユーザー、アプリケーション、アクセスしたサイトに関するさまざまな情報が保存されます。「Cookie」という用語は、UNIXコンピューティングで広く知られ、その概念と名称の由来となった「マジックCookie」に由来します。Cookieは、HTTPヘッダーのCookieを介してブラウザーとサーバーの間で作成、共有されます。
Cookie: JSESSIONID=9597856473431 Cache-Control: no-cache Host: 127.0.0.2:8080 Connection: Keep-Alive
ブラウザは、HTTPヘッダー内のCookieをコンピューター上のファイルに保存する必要があることを自動的に認識し、ドメインごとにCookieを管理します。各ドメインのCookieは、ブラウザによって常にHTTPヘッダーでサーバーに渡されるため、Webアプリケーションの開発者は、アプリケーションのサーバー側でCookieの値を要求するだけで取得できます。
セッションと接続の持続時間の違いに伴う問題は、Cookieによって解決されます。現在のほぼすべてのWebアプリケーションは「セッションID」を生成し、Cookieとして送信します。これにより、セッションを作成した接続が閉じられた後でも、アプリケーションはサーバー上のセッションを特定できます。このようにセッションIDをやり取りすることで、HTTPのようなステートレスプロトコルでも状態が維持されます。しかし、Webアプリケーションの利用規模が拡大し、単一のWebサーバーやアプリケーションサーバーの処理能力を超えた場合はどうなるでしょうか。通常はロードバランサー、現在のアーキテクチャではApplication Delivery Controller(ADC)を導入してアプリケーションを拡張し、すべてのユーザーに十分な可用性とパフォーマンスを提供します。
最新のアプリケーションでもCookieが使用される場合はありますが、その他のHTTPヘッダーが極めて重要になります。認証と認可に使用するAPIキーは、多くの場合HTTPヘッダーを介して送信されます。ルーティングやバックエンドサービスの適切なスケーリングに必要なデータを格納するその他のカスタムヘッダーも同様です。このデータの伝達に従来の「Cookie」を使用するか、別のHTTPヘッダーを使用するかよりも、アーキテクチャ全体におけるその重要性を認識することが大切です。
ここで問題となるのは、一般にロードバランシングアルゴリズムが、サーバー間でのリクエストの分散のみを目的としている点です。ロードバランシング手法は、ラウンドロビン、最小接続数、最速応答時間などの業界標準アルゴリズムに基づいています。これらはいずれもステートフルではないため、同じユーザーがアプリケーションに送信したリクエストが、その都度異なるサーバーに振り分けられる可能性があります。あるサーバーのセッションに保存されたデータが「プール」内の他のサーバーと共有されることはほとんどないため、HTTPに状態を実装するための作業がすべて無駄になってしまいます。
ここで役立つのが、パーシステンスという概念です。
パーシステンス
結びつきを支える仕組み
パーシステンス(スティッキネスとも呼ばれます)は、同一ユーザーからのリクエストが、最初に処理したサーバーに常に振り分けられるようにするため、ADCに実装される技術です。一部の負荷分散製品やサービスでは、この技術を「スティッキーセッション」と呼んでいますが、その仕組みを的確に表した名称です。
SSL/TLS対応サイトの負荷分散では、以前からパーシステンスが使用されてきました。これは、計算負荷の高いネゴシエーションプロセスが完了し、鍵が交換された後に同じプロセスを再実行すると、パフォーマンスが大幅に低下するためです。そのためADCには、ユーザーが最初に接続したサーバーへ常に振り分けられるよう、SSLセッションパーシステンスが実装されました。
長年にわたるブラウザ実装の変化に伴い、こうしたセッションで負荷の高い再ネゴシエーションを回避する手法が必要になりました。この手法は、Cookieベースのパーシステンスと呼ばれます。
SSL/TLSセッションIDに依存する代わりに、ロードバランサーは、クライアントが初めてサイトにアクセスした際にセッションを一意に識別するCookieを挿入し、以降のリクエストではそのCookieを参照して、適切なサーバーへの接続を維持します。
その後、Cookieベースのパーシステンスという概念はアプリケーションセッションにも適用されるようになりました。Webサーバーやアプリケーションサーバーが生成するセッションID情報を使用し、同一セッション中のユーザーリクエストが常に同じサーバーに転送されるようにします。この機能がなければ、負荷分散を必要とするアプリケーションでは、セッション情報を共有する別の方法を見つけるか、セッションと接続のタイムアウトを延長する必要があります。その結果、ユーザー基盤を支えるために必要なサーバー数が急増し、管理が困難になる可能性があります。
最も一般的なパーシステンスは、HTTPヘッダーで渡されるセッションIDを使用して実装されますが、現在のADCでは、その他のデータに基づいてパーシステンスを維持することもできます。Cookieに保存できるデータや、IP、TCP、HTTPの各ヘッダーから取得できるデータは、いずれもセッションのパーシステンス維持に使用できます。さらに、アプリケーションメッセージ内でユーザーを一意に識別できるデータであれば、インテリジェントなADCがブラウザーとサーバー間の接続を維持するために使用できます。
まとめ
HTTPはステートレスなプロトコルですが、この広く普及したプロトコルにステートを組み込むことが可能になっています。パーシステンスとApplication Delivery Controllerを使用すれば、ステートの維持に必要なCookieとセッションのやや脆弱な連携を損なうことなく、高可用性と高いパフォーマンスを備えたWebアプリケーションを設計できます。
HTTPの実装と処理自体はステートレスのままですが、こうした機能によってHTTPで状態を維持できます。Cookie、セッション、パーシステンスがなければ、アプリケーションを構築するためのステートフルなプロトコルが採用されていたことでしょう。しかし実際には、Application Delivery Controllerの機能がブラウザー(クライアント)とサーバーの間を仲介して状態維持を実現し、HTTPの用途を静的なWebページや従来型アプリケーションから、最新のマイクロサービスベースのアーキテクチャ、さらにはデジタル経済の中核を担うAPIへと広げています。
公開日:2018年1月19日
