2015/03/26(木)PukiWiki If-Modified-Since(条件付きリクエスト)対応

PukiWiki If-Modified-Since(条件付きリクエスト)対応

別の日記に少し書いてますが、ECO-Wiki (acronia)をIf-Modified-Since(条件付きリクエスト)に対応させたので、その方法をメモしておきます。

If-Modified-Since(条件付きリクエスト)とは

If-Modified-Sinceを使うと、ページが更新されている時だけデータを送って欲しい、という要求ができます。

  • 初回アクセス
    1. [クライアント] ファイルください。
    2. [サーバー] はいどうぞ。ファイルの更新日時は「Wed, 25 Mar 2015 15:07:33 GMT」。(200 OK 応答)
  • 2回目以降:更新されてない場合
    1. [クライアント] 更新日時「Wed, 25 Mar 2015 15:07:33 GMT」のキャッシュ持ってるけど最新版ある?(If-Modified-Sinceフィールド)
    2. [サーバー] 更新されてないから、内容は送らない。そのキャッシュ使って。(304 Not Modified 応答)
  • 2回目以降:更新されている場合
    1. [クライアント] 更新日時「Wed, 25 Mar 2015 15:07:33 GMT」のキャッシュ持ってるけど最新版ある?(If-Modified-Sinceフィールド)
    2. [サーバー] 最新版あるので送る。(200 OK 応答)

PukiWikiでIf-Modified-Since対応する場合の問題

PukiWikiの場合、各ページの更新日時をどのような基準で決めるかが問題となります。

  • ページを編集した日時にした場合:pcommentやinclude先、メニューバーが更新されても、そのページは更新されたと見なされない
  • ページを構成しているファイル郡の中の最新更新日時にした場合:正しく動作するが、追加の処理が必要。負荷も増える。

対応方針

この問題は、bodycache適用時の問題と似ています。そこで、今回はbodycacheファイルの更新日時を対応するページの更新日時と見なすようにしました。

  • bodycacheファイルはpcommentやinclude先が更新されたときもアップデートされるようにしているので、更新見落としが起こらない
    • bodycache patch適用範囲拡大を実施していることが前提
    • メニューバーの問題については、メニューバー更新のたびにbodycacheを全削除*1することで対応

改造内容

主にread.inc.phpを書き換えます。

  • lib/bodycache.php
    • キャッシュファイルの更新日時を元にLast-Modifiedヘッダフィールドを返す関数
      • どこに書いても良いですが、bodycacheを前提にした記述なので、bodycache.phpに書きました。
        // Last-Modified header
        function header_lastmod_cache($page = NULL)
        {
            global $lastmod;
        
            if ( is_cache($page) && get_cachetime($page) != 0 ) { //get_cachetime==0の時は、そのキャッシュは破棄予定なので無視
                pkwk_headers_sent();
                header('Last-Modified: ' .
                    date('D, d M Y H:i:s', get_cachetime($page)) . ' GMT');
            } else if ( is_page($page) ) { //キャッシュが存在しない問いは現在時刻を使う
                header('Last-Modified: ' .
                    date('D, d M Y H:i:s', UTIME) . ' GMT');
            }
        }
        
  • plugin/read.inc.php
    • If-Modified-Sinceフィールドヘッダの内容をunixtimeに変換して返す関数
      function get_if_modified_since()
      {
          static $unixtime = null ;
          if ($unixtime !== null ) {
              return $unixtime ;
          }
      
          if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
              $unixtime = false ;
              return false ; // If-Modified-Since なし
          }
          
          $unixtime = strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
          return $unixtime ;
      }
      ?>
      
    • plugin_read_action関数内の改造
      • 変更前
        if (is_page($page)) {
            // ページを表示
            check_readable($page, true, true);
            header_lastmod($page);
            return array('msg'=>'', 'body'=>'');
        
      • 変更後: 更新の有無を確認して、更新されていなかったら304 Not Modifiedを送って処理を打ち切ります。また、header_lastmod_cacheでLast-Modifiedフィールドを送るようにします。
        if (is_page($page)) {
            // ページを表示
            check_readable($page, true, true);
            
            $ims = get_if_modified_since();
            if ( $ims ) {
                $ctm = get_cachetime($page);
                if ( $ctm && $ctm <= $ims) {
                    // 更新されていない
                    header('HTTP/1.1 304 Not Modified') ;
                    header_lastmod_cache($page);
                    exit;
                }
            }
        
            //header_lastmod($page);
            header_lastmod_cache($page);
            return array('msg'=>'', 'body'=>'');
        

*1 : メニューバー更新と同時に、bodycacheファイルの更新日時を現在時刻に書き換えてしまう手もありますが、間違えて古いbodycacheを最新扱いしないように注意が必要です。

2015/03/10(火)ECO-Wiki アクセス状況 ~2015/03/09

ECO-Wiki アクセス状況 ~2015/03/09

2月末からのアクセス状況は以下のようになっています。

20150310_access_log.png

グラフ中のA、B、Cは、以下の対応を実施した時点です。

  • A: 画像の同時読み込み数制限
  • B: CSSとJSファイルについて、サーバーへの更新問い合わせ無しでキャッシュ参照する設定にした
  • C: Wikiページをif-modified-sinceに対して、304 Not Modified応答(キャッシュ参照)するように改良

土曜日、日曜日のアクセス数はそもそも多いという点に注意する必要がありますが、だいたいの傾向は以下のような感じでしょうか。

  • Aの後: 503がやや減ったかも
  • Bの後: 304 Not Modified応答が減少(更新問い合わせがなくなったため)
  • Cの後: 304 Not Modified応答割合がやや増えたかも?
    • ログ詳細を確認するとWikiは3000件/日くらい304 Not Modified応答を返している。今後に期待?
  • 最近1週間の503エラーの発生数は100件/日以下(ピーク時は1000件/日程度)

とりあえず、問題の503エラーについては改善傾向にあるようです。連絡しないと制限解除してくれないようですし、アクセス制限の解除依頼をしてもよいかもしれないですね。


追記: 2015/03/11

サポートに連絡して、アクセス数制限は現在解除されています。

制限を実施する条件は「サーバ運用に支障のある負荷が発生している場合」とのことで詳細はよく分からず。

追記: 2015/03/30

サポートからの回答があり、今回の制限については「サーバ負荷のピーク時にアクセス数が多かった」ためで、転送量やCPU使用量で判断したわけでは無いとのことでした。

2015/03/01(日)アクセス数制限と負荷対策

アクセス数制限と負荷対策

アクセス数制限

20150301_access_limit.png

さくらインターネットのコントロールパネルを見ると、しばらく前からアクセス制限がかかっているようです。

20150301_access_log.png
(1時間ごとのHit数、503だけ右軸)

アクセスログを見ても、上のグラフの通り、21日以降は「503 - Service Unavailable」がいくつか発生しています。

原因

ちょっと調べてみました*1が、この制限は、転送量ではなくて同時アクセス数によるようです。

ログを見ると503になっているのは画像が多くて、他はJavaScript/CSSファイル、若干のWikiページでした。(もっとも、これだけだと単にアクセスされるリソースの中で画像が一番多いからそうなっただけかもしれませんが。)アクセスされている画像を確認してみると、モンスター、家具ページなど画像が多いページから参照されている画像が多く、画像が多いページへのアクセスが要因になっている可能性があります。

対策

画像が多いページへの対策としては、表示されている範囲+αの画像だけを読み込む遅延ロードを既に行っていますが、これはどちらかというと転送量対策で同時アクセス数はあまり気にしていませんでした。(ただ、快適さをできるだけ損なわないように+αをかなり大きくしていたので、2/26頃に少し範囲を絞りました。)

そこで、現在使っている遅延読み込みプログラムを少し改造して、画像の同時読み込み数を最大2に制限してみることにしました。とりあえず、これで少し様子を見てみます。

JavaScript/CSSファイルも影響しているかもしれないので、画像のようにキャッシュを強く効かせる*2ようにしても良いかもしれません。


もうちょっと根本的な対策としては、画像を別のサーバーに置くことでしょうか。

制限の解除

リソース情報に記載されている「制限」について|さくらインターネット公式サポートサイトによると、対応完了後にメールで連絡すると、制限を解除してくれるようです。

でも、制限されない程度に対策する必要があるなら制限はあってもなくても同じような・・・。

最近2ヶ月は、負荷状況はほとんど変化していなかった(どちらかというと減少傾向)ので、何がトリガーになって制限されたのかは聞いてみても良いかもしれません。(制限されたタイミングがたまたま2/21だっただけで、もともと制限されるようなアクセス数だったのかもしれないですが。)

追記

21日頃からサーバーのload averageが増えていたので、サーバー全体としての負荷が増えたのを契機に制限をかけることにしたのかもしれません。