put_lastmodified() の負荷軽減(案)

  • ページ: BugTrack2
  • 投稿者: 0
  • 優先順位: 低
  • 状態: 提案
  • カテゴリー: その他
  • 投稿日: 2006-01-15 (日) 04:37:34
  • バージョン:

修正

(PukiWiki 1.4.7)

  1. 関数 lastmodified_add() を新規に作成しました。
    • この関数は、file_write() でput_lastmodified() の代わりにコールされます。
      従来: file_write() => put_lastmodified()
      今後: file_write() => lastmodified_add() [必要なら => put_lastmodified()]
    • put_lastmodified() はディレクトリを走査するため、ページ数に比例した負荷(待ち時間)が発生します。
    • lastmodified_add()は、可能であれば put_lastmodified() の呼び出しをキャンセルし、できるだけ最小限の手順で必要な処理を行います。
    • put_lastmodified() がコールされる条件は以下の通りです:
      • RecentChanges のキャッシュファイル recent.dat が存在しない時
      • あらかじめ大目に recent.dat に記録しておいた件数(デフォルト:10件)を越えてページの削除が行われた時 (RecentChangesを設定通りの件数で表示できなくなるため。ただしページの更新などが行われれば、余剰分は既定の件数までを限度に回復する。また再作成される度に、余剰分は回復する)
      • AutoLinkが有効である時 (常にディレクトリ走査を要求しているため)
  2. put_lastmodified() が情報を格納するキャッシュファイル recent.dat について、必要最小限の件数だけ情報を保存する様になりました。
    • 従来は常に全ページのページ名とその時刻を保存していたため、ページ数に比例した書き込み負荷が発生していました。

関連

  • BugTrack2/150: recentプラグインでrecent.datを有効活用していない ※recent.datを読み込んでいる側
  • BugTrack/448: flockが有効に使われていない

メッセージ

official の WebTrack/59(←削除予定) から移動してきました。

ページ更新時の負荷の大部分が put_lastmodified() によるもの*1なので、その負荷軽減の案です。

official ではページ数が 2450 ページ弱*2あるので、更新時に get_filetime($page) が 2450 回呼び出されます。タイムスタンプを取得すべきは 2450 分の 1 ページのため、その他は recent.dat に記録されたタイムスタンプを使いまわすことができれば処理時間の短縮が可能になります。

ページ数が 1000 単位になった場合、処理時間は大きく変わるはずです。

以下に案として挙げておきます。

diff -ur /org/file.php /dev/file.php
--- /org/file.php
+++ /dev/file.php
@@ -259,20 +259,68 @@
 // Update RecentChanges
 function put_lastmodified()
 {
-	global $maxshow, $whatsnew, $non_list, $autolink;
+	global $maxshow, $whatsnew, $non_list, $autolink, $vars;
 
 	if (PKWK_READONLY) return; // Do nothing
 
-	$pages = get_existpages();
+	$term = 60 * 60;// 1時間以上経過していれば作り直す
+
+	$recentFile = CACHE_DIR . 'recent.dat';
+	$recentTimefile = $recentFile . 'time';
+	
+	$timeFlag   = (file_exists($recentTimefile) && time() - $term < filemtime($recentTimefile));
+	$RecentFlag = (file_exists($recentFile) && $timeFlag);
+
 	$recent_pages = array();
 	$non_list_pattern = '/' . $non_list . '/';
-	foreach($pages as $page)
-		if ($page != $whatsnew && ! preg_match($non_list_pattern, $page))
-			$recent_pages[$page] = get_filetime($page);
+	if ($RecentFlag) {// recent.dat を使用する.
+		$varsPage = (isset($vars['page'])) ? $vars['page'] : '';// 空になる可能性は?
+
+		$fp = @fopen($recentFile, 'r');
+
+		// 不整合を防ぐため (排他的ロック && ノーブロック)
+		$wouldblock = true;
+		if ($fp && flock($fp, LOCK_EX + LOCK_NB, $wouldblock) && ! $wouldblock) {
+			$lines = file($recentFile);
+			$lines = str_replace("\n", '', $lines);
+			foreach ($lines as $line) {
+				if (empty($line)) {
+					break;// ないはず.
+				}
+				list($getTime, $getPage) = explode("\t", $line);
+				if ($getPage != $whatsnew && ! preg_match($non_list_pattern, $getPage)) {
+					$recent_pages[$getPage] = $getTime;
+				}
+			}
+			if (is_page($varsPage) && ! preg_match($non_list_pattern, $varsPage)) {
+				$recent_pages[$varsPage] = get_filetime($varsPage);
+			} elseif (isset($recent_pages[$varsPage])) {
+				unset($recent_pages[$varsPage]);
+			}
+		} else {
+			$RecentFlag = false;
+		}
+	}
+
+	// 通常の処理 (排他的ロック中に遭遇した場合も諦めてこちら)
+	// ロックで弾かれた場合、処理が追いつくことはないはず・・・
+	if (! $RecentFlag) {
+		$pages = get_existpages();
+		foreach($pages as $page) {
+			if ($page != $whatsnew && ! preg_match($non_list_pattern, $page)) {
+				$recent_pages[$page] = get_filetime($page);
+			}
+		}
+		@fopen($recentTimefile, 'w');// ファイルを作るだけ
+	}
 
 	// Sort decending order of last-modification date
 	arsort($recent_pages, SORT_NUMERIC);
 
+	if (isset($fp)) {
+		flock($fp, LOCK_UN);
+	}
+
 	// Create recent.dat (for recent.inc.php)
 	$fp = fopen(CACHE_DIR . 'recent.dat', 'w') or
 		die_message('Cannot write cache file ' .

ブラウザ複数窓, sleep() などで実験はしましたが、実用に耐えうるか(整合性が保てるか)が良く分からないので、上記処理は 1 時間に 1 度は現在と同じ処理をするようにしています。更新処理を行なった後 1 時間は軽減される、といった感じです。


コメント

  • 確かにお試しは軽いですねー。うちのサイト(約2000ページ&増量中)も更新に5秒以上かかるので更新が軽くなると嬉しいです。(サイト不具合のような新機能のような・・・これはきっとBugTrackじゃないですか?) -- かい? 2006-01-11 (水) 14:55:07
  • 関連 dev:BugTrack/763 負荷対策のまとめ -- teanan 2006-01-11 (水) 15:37:54
    • BugTrack に移動させた方が良いですかねぇ・・・? (^^; -- 0 2006-01-12 (木) 03:18:52
    • パフォーマンス改善ネタとしてBugTrackにあげて頂いたほうがいいと思います :) -- teanan 2006-01-12 (木) 10:51:56

ここまでが WebTrack/59 でのコメントです。


  • 実は recent.dat を get_filetimeのキャッシュにしてみようかと考えてたりします (^^; -- teanan 2006-01-15 (日) 07:24:21
  • 0さんの提案は毎度良い所突いてて素敵です。 気分的にはCVSに組み込みを待たずにofficialに反映させたい感じ。なのでWebTrackでも良かっ…ま、それはそれとして。$vars['page']から更新対象を貰っている感じですが、別ページを更新するpcomment、フォームから入力された内容でページ生成するTrackerやBugTrackプラグイン、添付時にtouchするattachでも、うまく反映されるでしょうか?*3 コンセプトについて異論はないので、更新する処理自体から更新ページを貰ったりできませんかね? -- にぶんのに 2006-01-16 (月) 00:37:30
    • う・・・普通に edit しか試していませんでした。確かに pcomment で不具合が出ました。ちょっと修正案追加です↓ -- 0 2006-01-16 (月) 05:23:11
  • put_lastmodified() に ページ名を与える。
    function file_write()
    {
    (-- 略 --)
    -	put_lastmodified()
    +	put_lastmodified($page)
    (-- 略 --)
    }
    
    -function put_lastmodified()
    +function put_lastmodified($editPage)
    {
    	$varsPage を $editPage に置き換える
    }
    
    plugin/pcomment.inc.php 
    function plugin_pcomment_insert()
    {
    (-- 略 --)
    -		put_lastmodified();
    +		put_lastmodified($refer);
    (-- 略 --)
    }
  • put_lastmodified() が呼び出されているのは現状二箇所だけ*4なので、これで対応できるのではないかと思いますが、まだ少し不安です (^^; -- 2006-01-16 (月) 05:27:37
  • ツボの突き具合について同感です -- henoheno 2006-01-16 (月) 23:02:11
  • 素晴らしい・・・最高のhackだとは思わんかね?見ろ!今までのストレスがゴミのようだ! -- 2006-01-17 (火) 16:03:09
  • PukiWiki-Officialへの導入はまだですか? -- 2006-01-19 (木) 22:44:00
    • Officialへ導入するなら、先にCVSに入れて冒険者達に人柱になっていただかないと。 -- 2006-01-19 (木) 23:57:39
  • ムスカさんまで現れましたね。とりあえず一点問題がありましたので報告を。同時に 3 更新が行なわれた場合ですが、 1. recent.dat, 2. get_existpages(), 3. recent.dat となった場合に、1->3->2 の順で更新*5されるかも知れません。なので、recent.dat が存在する場合は r+ で開いて排他的ロック*7にした方がいいかも知れません。ロックで足止めされる時間がどの程度になるかは環境によりけりでしょうが、同時に更新される*8可能性はそれほどないのではと勝手に思っています。 -- 0 2006-01-20 (金) 12:28:37
  • flockが競合した場合に重い処理に入るフローになっているのはあまり好ましくないでしょう。普通にLOCK_NBを立てずにflockする方向で構わないのでは? -- 2006-01-20 (金) 22:24:38
  • 運用に関する話になりますが、official/devは安定して稼動すること(タフという意味で)が求められているので、実験コードをいきなり追加する事は普通ありませんよ(する事はあるけれど) -- henoheno 2006-01-23 (月) 00:08:48
  • CVS版にはもう入っているんですか? -- 2006-01-26 (木) 13:10:33
  • まだだと思う。 -- 2006-01-26 (木) 19:31:43
  • 処理に recent.dat を使うという提案とサンプルコードなので、取り込むならば、もっとスマートな方法で書き換える必要があるでしょうね。for official/dev(sf.jp) の限定意見のつもりでしたし (^^; -- 0 2006-01-27 (金) 00:53:55
    • とりあえず処理の方向性を決めて具体化(コード化( -> 差分作成)) -> レビュー -> OKなら取り込み ですかね? どんな流れなのかイマイチ分かってなかったりします。 -- 0 2006-01-27 (金) 00:54:19
      • ファイルの扱いは fopen(r+) -> LOCK_EX -> file(recent.dat) -> recent.dat を使用した処理 -> ftruncate(0) -> rewind() -> fwrite() -> LOCK_UN -> fclose() のような感じでしょうか。 -- 0 2006-01-27 (金) 00:54:46
      • ファイルがない場合は現状の処理で。 -- 0 2006-01-27 (金) 00:55:09
      • ファイルの扱いに問題がなければ $term で処理している部分は削除ですね。ただ、何らかの理由で recent.dat とページの更新日時が一致しない場合*9にどうしようかといったところです。 -- 0 2006-01-27 (金) 00:55:31
  • ツッコミあればよろしくお願いします :) -- 0 2006-01-27 (金) 00:58:27
  • 人柱モードで私のサイトに導入しました(PukiWiki 1.4.6 + PHP 4.3.10)。pcommentで親ページのタイムスタンプを更新する設定にしていた場合に、 recent.dat および RecentChanges の親ページの時刻が、更新前の時刻となってしまう不具合がありました。get_filetime($editPage)の直前でclearstatcache()を実行すると回避できたので、以前に取得した時刻がキャッシュされているのだと思います。 -- machu 2006-02-12 (日) 21:44:48

recent.datの内部構造、想定している本来の利用方法について

  • 各種コメントありがとうございます。本日見たところでは「recent.dat を get_filetimeのキャッシュにする」のは無理ではないかと思います(下記)。ただお蔭様でrecent.dat周りに改善点がいろいろある事が判って来ました。上で挙げられている改善案も、何か良いエッセンスがあれば参考にさせていただきたいと思います。 -- henoheno 2006-03-06 (月) 00:51:32
    • (1) recent.datの内部構造は参照側(recentやrss)に特化されており、内部は時刻の新しい順にソートされている前提がある。また $non_listにあたるページなどは追記されない事になっている(等々、例外事項がある)。この時点でキャッシュに使うのはちょっと厳しい。キャッシュに使う入れ物ならソートなんて余計(オーバーヘッド)だし例外処理も余計。
    • (2) get_filetime()の結果をページ数分抱える意義が本当にあるのか、PHP自身のキャッシュを無視する意味があるのか、それは純テキストファイルでやるべきなのか。性能のためと言うならば結構シビアな話題です。
    • (3) 1を踏まえて考えるに、recent.dat は必要最小限の行数だけ用意するべきで、数千ページある時に数千行書き込むべきじゃない。つまり書き込む瞬間の処理がページ数に影響されるはずがない。(でも今はそうじゃない)
    • (4) 3を踏まえて考えるに、新たにページが更新された時には、「(RecentChangesに数十ないし百行程度表示したいなら)数十ないし百行程度の情報を一項目分だけ更新する処理」に専念できるはず。つまり更新時の作業量がページ数に影響されるはずがない。(でも今はそうじゃない)
    • (5) 4の状態まで来ると、RecentChangesを更新するのに全ページをスキャンする必要がそもそも無くなるはず。全ページをスキャンするのは recent.dat の更新時刻とその時の時刻に相応の開きがあった時など、ごく最小限でいい。(でも今はそうじゃない)
    • (6) これはデータ構造と、そのためのアルゴリズムの話であってキャッシュの話じゃないはず。キャッシュ(あるいは、キャッシュのキャッシュ)は基本的にオーバーヘッドになるし、本当にすべき事を見失いがちになるのでご用心。類似の話題は多分 BugTrack2/83
  • cvs:lib/file.php (1.51): BugTrack2/151: Cut unused lines from recent.dat
    • 上記(3)を実装しました。$maxshow で設定している (RecentChangesのための)項目数を越える行は recent.dat に保存されません。誰も使わないので。影響範囲としては、仮にRSSやrecentプラグインでその値以上の項目数を指定した場合、recent.datに存在するぶん以上はどうやっても表示されません。この点についてはどちらも、$maxshowより遥かに少ない項目数で利用される事が期待されている機能なので特に問題ないでしょう(そんなニーズがあるなら事例を添えて理由を教えて下さい)。 -- henoheno 2006-03-07 (火) 23:32:55
  • 上記(4)~(6)は、recent.datを新規に構築する (5) を発生させるべき状況をもう少し明確にしたいところ。 -- henoheno 2006-03-07 (火) 23:49:29
    • 仮に時間としましょう。6時間経ったら更新させるとして・・・仮に一日に一回書き込むか、書き込まないかという低頻度の編集ニーズがあるWikiだと、その書き手にとっては常に「重たいWiki」のまま変らないですね。つまりこれでは対外向けコンテンツや社内向けコンテンツを提供するためのWikiの管理者グループを楽にできません。 -- henoheno 2006-03-07 (火) 23:52:21
    • recent.dat が存在しない場合や、その項目数が設定と異なる時は強制更新すべき。管理者が設定を変える以外は普通起きない状態です。(global変数を悪意あるコードから書き換える状況は除く) 素早く更新するコードが上手くできており、内容がvalidであれば、特に強制更新しなくとも問題ないはず(素早く終わる事ができるはず) -- henoheno 2006-03-07 (火) 23:59:02
  • (3)について。BugTrack2/45のrecentの1機能、特定ページ以下のみ表示するケースはどうでしょう。recent.datのみで判断できないだけで無駄にはならない&本体未取込機能ですが、一応そういうニーズもあるという事で。 -- にぶんのに 2006-03-08 (水) 02:06:46
    • いつもありがとうございます。「現状の仕様において、recent.datに保存する情報は $maxshow の件数のみで事足りる」という事は上で示した通りです。それを踏まえてきちんと実装する事が、本来期待されてる最速の状態をもたらすはずです。 -- henoheno 2006-03-12 (日) 00:25:29
    • で、「特定の文字列でフィルタする」とか「特定の期間で考える」ニーズがある場合、「件数($maxshow)」という概念から考え直す必要があるでしょう。例えば「ここ一週間の間に更新されたhogeで始まるページを知りたい」というニーズがあった時、recent.datには「ここ一週間の更新情報」全てを事前に記録するべきです。重要なのは、全ページのデータを収める必要は(件数ベースの実装と同様に)無いという事です。また、期間ベースでデータを蓄積するのですから、大量にpostされた場合に、recent.datが恐ろしく大きく成長する可能性が生まれます。反面、だからこそある期間の変化を確実に知ることができる余地が生まれます。 -- henoheno 2006-03-12 (日) 00:26:12
    • 件数ベースでストアしている情報を強引にフィルタする場合、結果が0件に近づく可能性が高くなります。つまり購読者が意図していないかもしれない、妙なRSSを出力しやすくなります。期間ベースであっても、フィルタする内容によっては同様です。つまり利用者は「購読している物はあくまでもフィルタの結果である」という事を意識する必要があります。 -- henoheno 2006-03-12 (日) 00:26:58
    • このへんは設計(デザイン)の話でしょうね :) -- henoheno 2006-03-12 (日) 00:36:53
  • 件数ベースで記録している現状は、$maxshow で指定された件数より少し大目にrecent.datに記録する様にして、(頻発するページ削除により)それを越える件数が削られたことを検知したタイミングでのみディレクトリを走査すると良いでしょう。 -- henoheno 2006-03-21 (火) 22:48:28
    • 例えば 10件 余分に recent.dat に記録する様にしたサイトに、ページを110個連続で削除するスクリプトが働いたとして、recent.datの再生成が発生するのは10回(今までは110回)です。 ※個々の削除と同じタイミングで、別の誰かがrecent.datに載っていないページを110回編集/追加したならば、recent.datの件数は減らないので再生成は発生しない -- henoheno 2006-03-21 (火) 22:55:52
  • 更新時の処理のみ無理やり実装。ページ削除に対するケアも可能だし、cvs:lib/file.phpの他の部分もあわせて叩き直す余地がかなりあります。また今回作っている部分も見直しが足りないので、作業は当分続くでしょうけれど、これで一つ山を越えました。AutoLinkの更新処理が「ディレクトリの全チェック」を必要とするかのように作られている部分について、本当に必要かどうかが現時点では見えないので(今回のrecent.datのようにできるかどうかまで見ていないので)、とりあえず $autolink = 0 の時だけ動作する様にしてあります。 -- henoheno 2006-04-12 (水) 23:50:13
  • 質問箱を見るに、コンテンツの引越などの理由でwiki/*.txtの直接入れ替えをする方も一定存在するみたいなので、 linksプラグインの様に、put_lastmodifiedを呼び出してrecentキャッシュを明示的に最新化する機構が必要ですかね…。や、リリースまでにですが。 -- にぶんのに 2006-04-18 (火) 01:58:22
    • 今この瞬間のCVS版の実装で答えるならば、ページを適当に作ってそれを「削除」すれば、今まで通りにディレクトリを走査しますので強制更新が行われます。この半端な状態でも大部分のニーズは満たすので、タイミング的に今回(1.4.7)このままかもしれません。余談ですが管理用プラグインについては色々数とバリエーションがありすぎるので、何かまとめるいい案が欲しいです。 -- henoheno 2006-04-19 (水) 00:10:14
    • その他、recent.datを削除することで、再作成を強制することができるでしょう。 -- henoheno 2006-04-25 (火) 23:05:18
  • 削除のアクションは、RecentDeletedなどの挙動もフォローしなければいけないでしょう。 -- henoheno 2006-04-25 (火) 23:18:30
    • 削除時の処理で、lastmodified_add() に RecentDeleted のページ名を渡せばいい予感が。駄目かな? -- henoheno 2006-04-29 (土) 11:39:32
    • OK。cvs:lib/file.php (r1.67) -- henoheno 2006-04-30 (日) 12:59:11
    • これで、ページの削除に関しても、あらかじめ多めに取っておいた「遊び」の範囲内で、recent.dat が素早く(Wikiのページ数に依存しない速度で)更新されます。 -- henoheno 2006-04-30 (日) 13:01:21
  • 特にないなら、この見出し部分の話題は終了です。(あれば)質疑応答や、上にあるトピックに他にもお宝がないか検討しましょう。 -- henoheno 2006-05-05 (金) 19:55:22
    • (BugTrack2/179) put_lastmodified() に行った修正に含まれていた問題は、別のBugTrackにて修正されました。 -- henoheno 2006-05-20 (土) 00:23:01
  • 関連: BugTrack2/196, BugTrack2/273 -- 2008-05-26 (月) 21:43:41

コメント: バッファ操作?


コメント

  • BugTrack2/341 -- 2010-11-24 (水) 14:14:57
    • 5000件程度のデータ挿入をすると直っていないのがわかる -- 2010-11-24 (水) 14:16:12
  • それだけ1度で変更するなら、lastmodified_add()を5000回じゃなくて、最後にまとめてput_lastmodified()したほうがましなのでは・・・。(AutoLinkとかも有効にしているのなら、なおさら)
    似たような問題を抱えてそうなのは、ページ名変更の話題があるBugTrack2/196かな -- 2010-11-24 (水) 19:26:15
  • 挿入後 データを編集して、保存すればわかる。やりもしないで答えないように。 -- 2010-11-25 (木) 16:12:35
  • BugTrack2/56, BugTrack2/80 -- 2010-11-25 (木) 18:05:16


*1 オートリンク無効の場合
*2 2006年01月現在 一覧で表示されるページ数
*3 ゴメンなさい裏付けはとってないです。templateやincludeされたページでの更新も若干気にはなるんですが…
*4 のはず
*5 = 3 の変更点を 2*6 で上書き
*6 タイミングによっては拾ってくれるかも知れないが・・・
*7 もしくは適当なファイルを排他的ロック
*8 衝突ではなく純粋に put_lastmodified() に到達する時期が同じになる
*9 ftpでアップ -> wiki/***.txt の日付は変わったが recent.dat の中身は変わらない など

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2010-11-25 (木) 18:10:13
Site admin: PukiWiki Development Team

PukiWiki 1.5.2+ © 2001-2019 PukiWiki Development Team. Powered by PHP 5.6.40-0+deb8u7. HTML convert time: 0.381 sec.

OSDN