WordPressでページとカスタム構造の組み合わせで遅くなる問題の解決(v2.9版)
WordPressのページ機能を使って大量にページを作成し、かつパーマリンクでカスタム構造にしている場合、ページ生成がすごく遅くなる問題がある。ちょっと改善するだけで速度が大幅に向上するのでその方法を公開。
大概、ページ機能を多用するCMS的な使い方をする場合はパーマリンクでカスタム構造にしないし、通常はページ機能はあまり使用せずに投稿機能でサイトを作成するから、あまり問題にならないのかもしれない。
現時点でのWordPressの最新は2.9です。現在を含めたバージョン以下にはこの問題が存在します。
ほぼ同じ記事が前にも存在するのですが、アタッチメント部分を書き換えたので新しい記事にしました。
問題点について
パーマリンクでカスタム構造にしている場合、ページ数*(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;
}
解決方法
page_uri_index()メソッドは同じWP_Rewriteクラスの中から1回だけ、get_page_uri()は他に2箇所参照されているため、他に影響を及ぼすことが怖いため、WP_Rewriteクラスを改造することで解決しています。
page_uri_index()メソッドを変更し、get_page_uriとget_page_hierarchyメソッド、get_atts_parentメソッド、get_att_uriメソッドを追加しています。
自分はアタッチメントを使用しないため、コメントアウトして使用しています。
function page_uri_index() {
global $wpdb;
//get pages in order of hierarchy, i.e. children after parents
$posts = $wpdb->get_results("SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_type = 'page'");
$posts = $this->get_page_hierarchy($posts);
/* アタッチメントを使用しない場合はコメントアウト(開始) */
$atts = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_type = 'attachment'"));
$atts = $this->get_atts_parent($atts);
/* アタッチメントを使用しない場合はコメントアウト(終了) */
//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 = $this->get_page_uri($id,$posts);
/* アタッチメントを使用しない場合はコメントアウト(開始) */
if ( isset($atts[$id]) ) {
$attachments = $atts[$id];
foreach ( $attachments as $attachment ) {
$attach_uri = $this->get_att_uri($attachment, $uri);
$page_attachment_uris[$attach_uri] = $attachment->ID;
}
}
/* アタッチメントを使用しない場合はコメントアウト(終了) */
$page_uris[$uri] = $id;
}
return array( $page_uris, $page_attachment_uris );
}
function get_atts_parent(&$atts) {
$result = array ( );
if ($atts) { foreach ( (array) $atts as $att) {
$result[$att->post_parent][$att->ID] = $att;
} }
return $result;
}
function get_att_uri($att, $post_uri) {
$uri = $att->post_name;
// A page cannot be it's own parent.
if ( $att->post_parent == $att->ID )
return $uri;
$uri = $post_uri . "/" . $uri;
return $uri;
}
function get_page_uri($page_id, &$posts) {
$page = $posts[$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 = $posts[$page->post_parent];
$uri = $page->post_name . "/" . $uri;
}
return $uri;
}
function get_page_hierarchy($posts, $parent = 0) {
$result = array ( );
if ($posts) { foreach ( (array) $posts as $post) {
if ($post->post_parent == $parent) {
$result[$post->ID] = $post;
$children = $this->get_page_hierarchy($posts, $post->ID);
$result += $children; //append $children to $result
}
} }
return $result;
}
変更適用後のrewrite.php
上の変更を適用後のrewrite.phpです。wp-includeディレクトリの中のrewrite.phpに上書きして使います。
変更後の結果
あるサイトで画像をふんだんに使用して136ページ作成したのですが、改造前はクエリを1949個というとんでも無い結果だったのですが、165個に改善されました。
アタッチメント部分をコメントアウトすると17個にまで減りました。
キャッシュされるのでページ生成時間は比例して増加することは無いのですが、さすがにここまでクエリが多いと恐ろしく遅いです。
最後に
アタッチメントを使うとデータの取得効率を大幅に改善しても遅いですねぇ。。。(汗
2.9だと速度が速くなった感じがします。(キャッシュが早くなった??)
速度的には以下のような感じです。
改造後(アタッチメントコメントアウト) >> 改造前(アタッチメントコメントアウト) >>>>> 改造後(アタッチメント使用) > 改造前(アタッチメント使用)
アタッチメント部分のコードをコメントアウトするのが最大の改造かもしれません(汗
あわせて読む
2件のトラックバック
-
[...] rewrite.phpを変更して、クエリ発行数を抑える 詳しくは、WordPressでページとカスタム構造の組み合わせで遅くなる問題の解決(v2.9版)に詳しく書いてあります。 リンク先からバージョンに合 [...]
-
[...] sを高速化する方法が書かれていたので試して みました WordPressでページとカスタム構造の組み合わせで遅くなる問題の解決(v2.9版) 少し速くなりましたよ <関連しているかもしれない [...]
コメント投稿
3件のコメント
はじめまして。堺と申します。
この現象は「ページ」の場合だけでしょうか?
ページかつ、カスタム構造の場合ですね。
ページは自分から見て明らかに変でしたが記事に関しては、おそろしく動作速度が下がるような処理は現在見つかっていません。
記事に関してもカスタム構造を使うと、どれくらいかは分かりませんが、遅くなることは確かです。
早速の返信ありがとうございます。
なるほど。
カスタム構造の場合は記事を特定するのに時間がかかりそうですね。
今、rewrite.php のソースを追ってみたのですが、
apply_filters(’page_rewrite_rules’, $page_rewrite);
とか
apply_filters(’url_to_postid’, $url);
とかあるので、
記事番号がわかっていてURLをきれいにしたい場合は
ここにフィルターをかけると
特定のページなら速度を早くできそうですね。