客がいる間は止めてはならぬ

4年前、ECサイトの運用・開発を担当していたときの話です。

俺『しゃちょー、WEBサーバの再起動をしたいので一瞬サーバを止めてもいいですか?』
社長『客がいないときならいいよっ』
俺『えっ』
社長『えっ』
 つまり、社長の主張としてはこういうことらしい。わりかし顧客単価が高いサイトだったので、購入シーケンスに入っている顧客の足を止めたくない....と。インフォメーションでメンテナンス表記してても、入ってくる客はいるのでその場合でも機会損失としたくないというのは分かる。が、そんなこと気にしたことなかったので、さて実際問題どうしたものか。アクセスログを見ていればある程度は、クライアントの状況は把握できるものの、客がいないことを確認してWEBサーバを再起動するのはちとめんどくさい。

 そこで、p0tさんのAllsessionというのがあったなと思い出す。
 Allsessionはphpのデフォルトのセッション保存場所/tmp/sess_xxxx*1のファイルをすべて取ってきてデコードするツールで、サーバ上の全ユーザのセッションを取ってこれます。

さて、これをどのように使うかというと、まず、事前準備としてコントローラー側でセッションにアクセス元IPアドレス、アクセスカウンタ、アクセス日時、アクション名等を入れるようにしておきます。

 次に、管理画面でAllSessionを使い全ユーザのセッションを取ってきます。その取ってきたデータにフィルタをかけて直近5分以内にアクセスしているもので、購入シーケンスに入っているものだけ表示するようにします。unixコマンドのwhoみたいな感じですね。

 このツールで状況を確認すれば、お客さんがいないときにWEBサーバを再起動することができましたとさ。

 えぇ、ぶっちゃけ、WEBサーバーの再起動くらいなら気にしなくてもいいと思うんですよ。僕もそう思います。そんな社長さんでしたが、今ではちゃんとメンテナンス時間を取るようになりました。

 このツールわりかし面白いです。たとえば、新規購入のお客様が購入を迷っている様とか、お得意様がやってきたとか、このツールでリアルタイムに状況を把握できます。ECサイトのパッケージには標準で欲しいですねー。

追記

<?php
class ss{
  static function unserialize($str){
    if($str=='') return $str;
    // 正規表現で頑張るにはちとツライ
    // 補正かけて頑張っているが、まだ漏れがあるかも
    // 本当は字句解析すべきだが...
    $array = array();

    //正規表現で行頭};が引っかかるのでそれのfix
    $fix_chr='';
    if(in_array($str[0],array('}',';'))){
      $fix_chr = $str[0];
      $str = substr($str,-(strlen($str)-1));
    }
    $a = preg_split("/([^|};][^|;]*)\|/", $str, -1,
        PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    $a[0]=$fix_chr.$a[0];
    for($i = 0; $i < count($a); $i = $i+2) {
       // 配列のキーの行頭が{;だった時の対策
       // unserializeが、行末のゴミを無視することを利用して
       // 差分からfix文字列を取得する
       if(serialize(unserialize($a[$i+1]))!=$a[$i+1]){
         // 文字列の差分を取得
         $fix_chr = str_replace(serialize(unserialize($a[$i+1])),'',$a[$i+1]);
         $a[$i+2]=$fix_chr.$a[$i+2];
       }
       $array[$a[$i]] = unserialize($a[$i+1]);
    }
    return($array);
  }

  static function serialize($array){
    foreach($array as $key => $value){
      $str[]= $key.'|'. serialize($value);
    }
    return implode('',$str);
  }
}

foreach(glob('/tmp/sess_*') as $f){
    echo $f."\n";
    print_r(ss::unserialize(file_get_contents($f)));
}

※ セッションにmemcachedとかを使っている場合はmemcachedで一覧を取得してくるコードを書くことになります。

*1:セッションの保存場所は/tmp/とは限らないので、適時、ini_get("session.save_path")を使ってください。