2004-11-06 (Sat) [長年日記]

_ [tdiary] 負荷対策:静的 HTML 化

CGI の高負荷で、CGI 止められたら困るので、何とかして 訪問者の方には静的 HTML にアクセスして欲しい。

静的 HTML と言ってまず思い浮かぶのが squeeze.rb なのだが、 squeeze.rb が吐き出す HTML は元々は namazu のインデックス用に 作られたものなので、訪問者の方々に見せるにはちょっと不満がある。 というわけで、 squeeze.rb とはちょっと違った 静的 HTML 化を「超」場当たり的にやってみた。

以下のような動的な機能は全て失われてしまうが、 xrea の負荷制限にひっかかって見られなくなるよりはマシだろう。

  • カウンタが回らない
  • リファラやトラックバックは記録されない

これぐらいは全然困らんよね?元々、私の blog の方はリファラの表示 しないようにしてたし。

以下のような機能はちゃんと動く。

  • コメント/ツッコミ機能
  • カテゴリの表示
  • tdiary-grep

実装の基本方針は以下。

  • (nph-)index.rb は表示する際に、表示するものと全く同じ 静的 HTML をキャッシュとして書き出す
  • mod_rewrite で「キャッシュがあるならキャッシュへ転送。 キャッシュがないなら (nph-)index.rb へ転送。」というルールを書く
  • comment が投稿されたら、キャッシュを削除
  • 記事を更新したらキャッシュを削除

キャッシュ置場を仮に

/virtual/tmaeda/public_html/cache

とする。

古くてすんません、tdiary 1.5.6 向けですが、 キモは mod_rewrite なので、この方針は 2.0.0 などにも適用できるでしょう。

(nph-)index.rb は表示する際に、表示するものと全く同じ静的 HTML をキャッシュとして書き出す

(nph-)index.rb で最終的な HTML 出力をやっている箇所

head['cookie'] = tdiary.cookies if tdiary.cookies.size > 0
print @cgi.header( head )
print body

の直前に、以下のようなキャッシュ書きだしのコードを追加。 単純に body をファイルにも出力するだけ。

if @cgi.valid?( 'date' ) then
  if /^\d{8}$/ =~ @cgi['date'] then
    File.open("/virtual/tmaeda/public_html/cache/#{@cgi['date']}.html", "w") {|f|
      f.write body
      File.open("/virtual/tmaeda/log/cache.log", "a+") {|f|
        f.write "#{Time.now} static cache written(#{@cgi['date']}.html).\n"
      }
    }
  end
end

comment が投稿されたら、キャッシュを削除

同じく、 (nph-)index.rb のだいぶ上の方、

if @cgi.valid?( 'comment' ) then
  tdiary = TDiary::TDiaryComment::new( @cgi, "day.rhtml", conf )
elsif @cgi.valid?( 'date' )

tdiary = TDiary::TDiaryComment::new( @cgi, "day.rhtml", conf )

の直前に、以下のようなキャッシュ削除のコードを追加。

date_string = @cgi['date']
cache = "/virtual/tmaeda/public_html/cache/#{date_string}.html"
if FileTest.exist?(cache) then
  File.delete(cache)
  File.open("/virtual/tmaeda/log/cache.log", "a+") {|f|
    f.write "#{Time.now} static cache cleared(#{date_string}.html).\n"
  }
end

記事を更新したらキャッシュを削除

今度は update.rb をいじる。

まずは頭の

BEGIN { $defout.binmode }

の直下に、キャッシュを削除する関数を追加。

def delete_cache(cgi)
  date_string = sprintf("%04d%02d%02d", cgi['year'], cgi['month'], cgi['day'])
  cache = "/virtual/tmaeda/public_html/cache/#{date_string}.html"
  if FileTest.exist?(cache) then
    File.delete(cache)
    File.open("/virtual/tmaeda/log/cache.log", "a+") {|f|
      f.write "#{Time.now} static cache cleared(#{date_string}.html).\n"
    }
  end
end

あとは、これを処理を分岐させている if ... elsif ... の中の 何か所からか呼び出す。 append, edit, replace, comment のモードのときだけ呼び出せば 十分だと思う。

if @cgi.valid?( 'append' )
  delete_cache(@cgi) # 追加
  tdiary = TDiary::TDiaryAppend::new( @cgi, 'show.rhtml', conf )
elsif @cgi.valid?( 'edit' )
  delete_cache(@cgi) # 追加
  tdiary = TDiary::TDiaryEdit::new( @cgi, 'update.rhtml', conf )
elsif @cgi.valid?( 'replace' )
  delete_cache(@cgi) # 追加
  tdiary = TDiary::TDiaryReplace::new( @cgi, 'show.rhtml', conf )
elsif @cgi.valid?( 'appendpreview' ) or @cgi.valid?( 'replacepreview' ) 
...(スッ飛ばして、次は comment)
elsif @cgi.valid?( 'comment' )
  delete_cache(@cgi) # 追加
  tdiary = TDiary::TDiaryShowComment::new( @cgi, 'update.rhtml', conf )

かなり恥ずかしいコードだが、場当たり対応なので良いことにする。

mod_rewrite の設定

元々はこんな感じの設定を .htaccess に書いていた。

RewriteRule ^([0-9]+)\.html$ nph-index.rb?date=$1

私の場合、アクセスの多い 20041101.html だけを キャッシュ転送の対象にしたいので、以下のように変更する。

RewriteCond %{REQUEST_URI} ^\/(20041101\.html$)
RewriteCond /virtual/tmaeda/public_html/cache/%1 -f
RewriteRule .* /virtual/tmaeda/public_html/cache/%1 [L]

RewriteRule ^([0-9]+)\.html$ nph-index.rb?date=$1

もし全部の記事をキャッシュに転送したいのであれば、 こんな感じに書くと良いだろう。

RewriteCond %{REQUEST_URI} ^\/([0-9]+\.html$)
RewriteCond /virtual/tmaeda/public_html/cache/%1 -f
RewriteRule .* /virtual/tmaeda/public_html/cache/%1 [L]

RewriteRule ^([0-9]+)\.html$ nph-index.rb?date=$1

以上。

台風がやんだら、今回追加した RewriteCond/RewriteRule を 消すだけで済むし(まぁ、表示の度に 毎回キャッシュに書き出すのはウザいけど)、また別のページに台風が 来たら RewriteCond のルールを追加するだけで良い。

欲を言えば、「index.rb がエラーを返したらキャッシュに転送する」 ってのができると最高なんだけどな〜。ごく稀に原因不明の Ruby の エラー([BUG] unknown node type 0 だか何だか)が出て 何度リロードしても表示されなくなることがあるので。

場当たり対応にしてはなかなか良いかもしれない。 「いつ CGI 止められるかわからない」という不安から開放され、 精神衛生上かなりよろしい。正直、「カウンタが回らない」なんてのは 「本文が表示されない」のに比べればどーでも良い問題だし (どうしても必要ならカウンタだけ別 CGI にする方法だってある)、 全てのページでこの挙動をデフォルトにしてしまおうかと 思っているぐらいだ。

参考

2004/11/07 追記

その後の話。

効果絶大。

アクセス数は前日と同じぐらいだったけど、 負荷率は(xrea で禁止されてるので具体的な数値については伏せますが) 今までの平常時と同じレベルに戻りました。 20041101.html への負荷が事実上ゼロになるので当り前っちゃー当り前ですが。

これでどこからリンク張られても恐いもの無し。

2004/11/07 もう一つ追記:不具合を見付けた気がする

コメントの「名前」欄や「E-mail」欄に最後にコメントを 投稿した人(正確にはキャッシュを作る際にアクセスしてきた人)の クッキーの情報が入った状態でキャッシュが生成されてしまうので、 デフォルト値として赤の他人の名前/E-Mailが入った状態で表示されてしまう ことになる。どーしたもんか、、、

すぐに思い付く方法としては、 「skel/dairy.rhtml でクッキーの値を入れないようにする」って方法だけど、 これじゃキャッシュの対象にしていないページの挙動にも影響が出ちゃうなぁ。 まぁ、コメント投稿する機会なんてそんなに頻繁ではないので、 これぐらい良いっちゃー良いか、、、

本日のツッコミ(全2件) [ツッコミを入れる]
_ 小林 (2004-11-22 (Mon) 23:31)

確かに入ってますね

_ tmaeda (2004-11-22 (Mon) 23:59)

小林さん。えーと、何が入ってますか?(^^;<br>名前とEmailの話ですか?<br>名前とEmail欄が自動で入らないようにしてるのは、<br>tmaeda.s45.xrea.com/ の方だけで tmaeda.s45.xrea.com/td/<br>の方は、(負荷対策をしてないので)前回のコメント時の名前とEmailが<br>自動で入るようにしてます(というから元々そうなっているのを<br>そのまま利用しているだけです)。<br><br>tmaeda.s45.xrea.com/20041101.html 等では、自動で他人の名前が入ってないはずですけど、、、入ってます?

[]

トップ «前の日記(2004-11-05 (Fri)) 最新 次の日記(2004-11-08 (Mon))»