WordPressでページとカスタム構造の組み合わせで遅くなる問題の解決

WordPressのページ機能を使って大量にページを作成し、かつパーマリンクでカスタム構造にしている場合、ページ生成がすごく遅くなる問題がある。ちょっと改善するだけで速度が大幅に向上するのでその方法を公開。

大概、ページ機能を多用するCMS的な使い方をする場合はパーマリンクでカスタム構造にしないし、通常はページ機能はあまり使用せずに投稿機能でサイトを作成するから、あまり問題にならないのかもしれない。

現時点でのWordPressの最新は2.8.52.9.1です。現在を含めたバージョン以下にはこの問題が存在します。

問題点について

パーマリンクでカスタム構造にしている場合、ページ数*(2 or 3)+画像数のクエリが発行されるため、ページ機能を多用すると使い物にならないくらい遅くなる問題が発生する。

例えばページとして5ページ作成し、画像を3点使用している場合、クエリが最低13個発行される。20ページある場合、最低でも40個ものクエリが発行される。

問題のコード

wp-include/rewrite.phpの784行目当りのpage_uri_index()メソッドで初めにページに関する情報を全て取得し、その後、ループでアタッチメントに関する情報を取得している。つまりこのメソッドの中で最低でもページ数と同数のクエリが発行される。

function page_uri_index() {
	global $wpdb;

	//get pages in order of hierarchy, i.e. children after parents
	$posts = get_page_hierarchy($wpdb->get_results("SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_type = 'page'"));
	//now reverse it, because we need parents after children for rewrite rules to work properly
	$posts = array_reverse($posts, true);

	$page_uris = array();
	$page_attachment_uris = array();

	if ( !$posts )
		return array( array(), array() );

	foreach ($posts as $id => $post) {
		// URL => page name
		$uri = get_page_uri($id);
		$attachments = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_type = 'attachment' AND post_parent = %d", $id ));
		if ( $attachments ) {
			foreach ( $attachments as $attachment ) {
				$attach_uri = get_page_uri($attachment->ID);
				$page_attachment_uris[$attach_uri] = $attachment->ID;
			}
		}

		$page_uris[$uri] = $id;
	}

	return array( $page_uris, $page_attachment_uris );
}

前のpage_uri_index()メソッドで呼び出されているpost.phpの2193行当りのget_page_uri()関数を見るとget_page()関数でページを取得するためにSQLが発行されている。後のget_page()関数でSQLが発行されないとしても、全てのページ総なめのため、ページ数と同数のクエリが発行される。

function get_page_uri($page_id) {
	$page = get_page($page_id);
	$uri = $page->post_name;

	// A page cannot be it's own parent.
	if ( $page->post_parent == $page->ID )
		return $uri;

	while ($page->post_parent != 0) {
		$page = get_page($page->post_parent);
		$uri = $page->post_name . "/" . $uri;
	}

	return $uri;
}

解決後のrewrite.php

解決後のrewrite.phpです。wp-includeディレクトリの中のrewrite.phpに上書きして使います。

各バージョンのrewrite.php置き場

変更後の結果

あるサイトで画像をふんだんに使用して136ページ作成したのですが、改造前はクエリを1949個というとんでも無い結果だったのですが、165個に改善されました。

アタッチメント部分をコメントアウトすると17個にまで減りました。

キャッシュされるのでページ生成時間は比例して増加することは無いのですが、さすがにここまでクエリが多いと恐ろしく遅いです。

最後に

後のWordPressでページとカスタム構造の組み合わせで遅くなる問題の解決(v2.9版)で書いていますが、クエリ数が多いだけではなく、この問題の部分の後のコードにおけるアタッチメントURLの処理も重いっぽいです。

また、以前の解決コードはアタッチメントを使用していなかったため、検証が不十分だったので消去し、アタッチメント部分も改良したコードにしました。

ページトップ

ブックマーク!


4件のコメント

  1. HS
    コメント日時 2010 年 1 月 8 日 at 6:54 PM

    初めまして。

    同様の問題に直面して、サーバ会社(Coreserver)からアクセス制限をされてしまい、大変困っておりましたので、使用中のWordpressがVer2.5.1なのですが、こちらのコードをrewrite.phpに部分上書きして使用させていただきました。

    すると、パーマリンクをカスタム構造にして変更を保存した時に

    Warning: Missing argument 2 for WP_Rewrite::get_page_uri(), called in /virtual/(アカウント名)/public_html/wp-includes/rewrite.php on line 309 and defined in /virtual/(アカウント名)/public_html/wp-includes/rewrite.php on line 321

    というエラーメッセージが画面上に1953回リピートされて出力されました。。。
    しかし、公開画面、管理画面共に、問題が無いようです。

    お忙しいところを大変恐縮ではありますが、
    このまま使用し続けて良いものなのか、
    お手隙の際にコメントいただければ幸いです。

    どう宜しくお願い申し上げます。

  2. torisan
    コメント日時 2010 年 1 月 9 日 at 12:59 AM

    アタッチメント部分の検証が不十分だったようです。申し訳ありません。

    2.9でアタッチメント部分も改良したコードを掲載しています。またリンク先に2.5.1用も作成しました。よろしければそちらをお試しください。(後のVerでrewrite.phpがだいぶ変更されているため)

    2.5.x環境がないため2.8.xでテストしたコードを2.5.xの該当部分にだけ転用しています。

    基本的に最新を使うことはお勧めしています。。。

  3. HS
    コメント日時 2010 年 1 月 10 日 at 3:31 PM

    迅速なご対応をしていただいたにもかかわらず。お返事が遅くなりましてすみません。

    実は、WP Super CacheとWP Widget Cacheを導入しようということになり,
    お返事を拝見する前に2.7系にUpgradeしてしまっていました。
    折角、ご対応頂いたのに、試せなくて大変申し訳ありません!

    ちなみに、2.8系用の改造部分を2.7系に適応したところ、
    2.5系の時に発生したようなエラーは発生しませんでした。
    (お試し済みでしょうか…)

    ちなみに、torisanさんのおっしゃられているクエリ数は、get_num_queries()で取得出来る値とは違うものなのでしょうか?
    現在、私のサイトは363ページ、4投稿、画像は2000弱で、カスタム構造のパーマリンク(/%category%/%post_id%.html)を使用していますが、上の関数では、Widget Cacheを効かせると20前後、効かせていないと80〜100クエリ位の値となっており、主に、Widgetで使用しているナビゲーションを自動生成するPluginによるクエリというのは分かっています。先述の1920回のエラーメッセージと合わせて考えると、上記関数で取得出来るクエリ数はtorisanさんのおっしゃられているクエリ数とは別のカウントなのかが分からなくなってしまいました。。。
    本当にお手隙の際で構いませんので、この記事で話題にされているクエリ数とget_num_queries()で取得出来る値に根本的な違いがあるようでしたら、ご教授いただければ幸いです。

    どうぞ宜しくお願い申し上げます。

    PS
    Wordpressは最新版を使うべきとは認識しておりますが、一部の必須Pluginが非対応のため、今は2.7系迄しかUpgrade出来ないのです。。。

  4. torisan
    コメント日時 2010 年 1 月 12 日 at 9:08 PM

    WP Widget Cacheは使ったこと無いので分かりません。。。(汗

    get_num_queries()で取得しているクエリ数はMySQL関数にクエリを引き渡しているものをカウントしています。

    ただ特定の関数でカウントしているため、その関数を通らなければ、カウントされません。(うろ覚えですが。。。)

    クエリ数はキャッシュ関数を使うものの場合、キャッシュが存在する場合、キャッシュを見てクエリを発行しないのでカウントされません。

    キャッシュは生存期間が決まっているので、キャッシュ無しの1からページ生成する場合はクエリが多い場合、遅いです。
    (MySQLのキャッシュも関係するのでWPは全くキャッシュ無しでも、初めの実行時と二回目以降は速度が違います)

    自分も2.7系からアップデートをすっかり忘れていたプラグインがあったので、代表してすいません(汗

    P.S.
    あるサイトはアップデートでデータ移行と検証をしなくてはいけないのですが、料金出てこないんですよね。。。アップデート出来ない気持ちも分かります(汗

1件のトラックバック

  1. 九州旅行しませんか?旅行トレンドブログ 西海道ホテル
    トラックバック日時 2010 年 2 月 13 日 at 1:41 PM

    wordpressに気をつけろ!パーマリンクの甘い誘惑

    Wordpressは人気のブログソフト。西海道ホテルブログで使用して1年ほどになります。採用した理由はページ機能だったのですが、パーマリンクの甘い誘惑があったのです。 (c) Rm。|写真素…

コメント投稿

* は必須項目です。入力したメールアドレスは公開されません。

*
*



AccessやExcel、.NETソフトウェア開発、WordPress等を使用したWebのシステム化、PCサポート、コンサルなどを行っています。全国対応可能です。お問い合わせください。
TEL (0776)56-8539