PHPでRASIS四方山話

著:古庄道明氏

「大量アクセス」を考慮したPHP第03回 17年12月更新

前回の「変更容易性」のお話を踏まえつつ、になるのですが。
1つの例として「アクセス数が増えた場合」というのを、少し考えてみたいと思います。
基本的にはServiceability(保守性)のお話になろうかと思うのですが、「大量アクセスを意識した設計」はIntegrity(保全性)にも関わってくるものではなかろうか、と思います。

さて。
「アクセス数が増えた」時に、もっとも困るシナリオを、まず考えてみたいと思います。
色々な事象があろうかと思うのですが「サーバが過負荷で落ちる」と「データの整合性が取れない状態で壊れてしまう」というあたりが、おおよそ双璧であるように思われます。

「サーバが過負荷で落ちる」については。
「アクセスが一段落すれば落ち着く」ようにも思われるのですが、過負荷の原因によっては「サーバが復旧したそばから過負荷で再度落ちる」を繰り返すと、延々と「落ち続ける」ので、状況としては最悪に近いように思われます。
一方で「データの整合性が取れない状態で壊れてしまう」については。
もちろん「データの整合性を取り直すコードを書いてどうにかする」という方法が、壊れ方によっては可能かもしれませんが、とはいえそういったものが「瞬時に組める」ものでもないと思われるので、これもまた、状況としては極めて悪いように思われます。

こういった状況については「インフラ側の設計に依るところ」も少なからずあろうかと思うのですが。
とはいえプログラム側が「全く無考慮でよい」わけでもありませんので、「プログラム側で出来る事」について、考察を重ねていきたい、と思います。

少し余談を。
「鶏を割くに焉んぞ牛刀を用いん」というのは比較的知られていると思いますし、少し調べますと「employ a steam-hammer to crack a nut」といった表現もあり、洋の東西を問わず、こういったことはあるものなのだなぁ、と改めて思います。
プログラムの世界にも、さまざまな技法や考え方、手法やテクニックが存在します。
もちろん「学習のために」使う分にはどんな乱用もよいかと思うのですが、お仕事で考えた場合に「明らかに不要なテクニック」をこれ見よがしに使われても、どちらかというと「コードが読みにくくなる」事のほうが多くなるように思われます。
ただ、では「どこまでが適切で、どこからが不適切か」の線引きは、それはそれで難しい問題ではあろうか、と思うので。そのあたりについては、定期的に自問自答や議論などを重ねていくと、いざという時に困らないかと思います。
個人的には「それがないと明らかに出来ない/困る事象があるなら使う」というような線引きをすることが多いように思います。

お話を戻しまして。

PHPで「アクセス数が増えた」時におこなうインフラ作業、に引きずられて困るお話の筆頭に「セッション」があるかと思います。
この場合のセッションは、直接的には「session_start()関数」を起点にする、PHPで提供しているセッションになります。
典型的には「再現性がない(或いは時々)なのですが、ユーザのセッションが消えてしまう事があるんです」といったアラートが上がってくるような状態、になります。

少し、背景を噛み砕いてみていきましょう。
PHPのセッション情報は、デフォルトでは「ファイルに保存」されます。保存される場所は session_save_path() 関数などで見てみるとわかりやすいでしょう。
また、アクセス数が増えた時に一番初めによく行われるのが「Webサーバを2台(以上)に増やして処理を分散する」事で性能を上げる方法です。
「分散にDNSラウンドロビングを使うのかロードバランサを入れるのか」といったあたりは興味深い所ですが、昨今の、特に「IPv4の枯渇」+「クラウドを鑑みた」事情で申し上げますと、おおむね「用意されているロードバランシングサービス」一択かと思われますので、一旦そちらで話をすすめます。

繰り返しになりますが、PHPセッションは、デフォルトでは「localのファイルシステム」に情報が保存されます。
一方で、Webサーバは「2台」となります。ロードバランサの設定やモードにもよりますが、ユーザのアクセスは「そのどちらか」に割り振られる、可能性があります(L4スイッチを使った場合)。
そうすると、現状出している数値だけで申し上げると「1/2の確率」で「セッションを保存しているサーバ”以外”のWebサーバにアクセス」が入る事となり、結果として「セッション情報が失われる」という事象が発生します。
そこから「時々、ユーザのセッションが消えてしまう」につながります。

この事象を「プログラム側に何も変更を加えず”インフラのみ”で」解決する方法が、あるかないか、と言えば、あるにはあります。
ロードバランサの設定を、L7スイッチまたはSticky Session(スティッキーセッション)と呼ばれる方法にすると、「1ユーザのアクセスは、かならず1サーバ(1インスタンス)に行くように調整をしてくれる」機能が、あります。

では「Sticky Sessionがあるからプログラム側では何も考えなくてよい」のか? と問われると、些か微妙な点があります。
最も端的には「1インスタンスにしか情報がないため、その1インスタンスが壊れたら、そのインスタンスに紐づくユーザのセッション情報は壊れる」というところがあります。
特にクラウドの場合、DBはともかくとして、Webサーバ等のインスタンスは、比較的「使い捨て」的な雰囲気もあり「問題があったら入れ替える」「バージョンアップがあったら入れ替える」といった使い方や、それを前提にした運用方法も、少なからず存在します。
そのため「1インスタンスと密接に紐づいた情報」は、どちらかというと運用の足かせになりがちなのではないか、と思われます。

では、プログラム側でなにか出来る事はあるのでしょうか?
PHPセッションは session_set_save_handler() という関数を使って「セッション情報を読んだり書いたりする時の挙動」を制御する事が出来るので、全てのプログラムの先頭でこの関数を使って「ユーザー定義のセッション保存関数を設定」すれば、問題なく運用が可能です。
保存先は、とりあえずとしてはRDB。もう少し高圧なアクセスになってきたら、KVS等を視野に入れてみるとよいと思います。

ただ、この「全てのプログラムの先頭」というのが、些か曲者になりまして。
適切に共通化などがなされていて「全てのプログラムが必ず通る1か所」があれば、そこに記述をすればよいのですが。
そういったファイルがないと「全ファイルに対する修正」が発生し、その作業は一定の確率で「作業漏れ」を発生させる可能性が想起されます。
「それは二重チェックなどで確認をすればよい」という根性論もあろうかとは思うのですが、「忙しくてせっぱつまって絶対にはずしてはいけないタイミングで」二重チェックをすり抜けてくるのが「バグ」というものでございます。
ですので、そういった事は「人力馬力に頼る」よりも「仕組みとして作っておいた」ほうが、より、安全ですし、安心できます。

ですので。
もし「全てのプログラムが必ず通る1か所、がない」場合、まずは「全てのプログラムが必ず通る1か所を作る」ところから修正をしていくとよいのではないか、と思います。
そういった箇所の有無が1つの「変更容易性」ですし、なければ作り直していく事が「変更容易性を上げる」事につながるのではないか、と思います。

少しお話が長くなってきましたので、続きは次回にて。
次回も、「変更容易性」にまつわるお話を、もう少し重ねていきたいと思います。