WordPressを高速化する

もくじ > Web開発ノート > WordPress

WordPressが嫌われる理由のひとつに、表示速度が遅いというものがあります。

表示速度が遅い理由はいくつかありますが、よく言われるものが、データベースを使っているから重い。というもの。

PHPで直接SQL文を書いた場合は、ほぼ0秒で実行できているのに、WordPressで同じ処理をすると、何故か1.5秒くらいかかってしまうので、重いのはデータベースだけでなく、余計な処理をいくつも実行しているからだというのはわかるのですが、弱いレンタルサーバ上で動作させていると、投稿数が5000を超えたあたりから、明らかにデータベースが原因で重いという現象が出始めます。

データベースが原因で重くなり始めたら、サーバ移転を検討する必要があります。サーバ移転とは、安くて評判が悪いレンタルサーバから、少し高いけど評判が良いレンタルサーバに移転する。という事ではなくて、クラウドサーバなどに移転して、CPUのコア数をアップデートするとか、データベースサーバを独立化して高速なものを使うとか、サーバの構造を再検討するという事です。「WordPressってこんなに速いんだ!?」と、それまでの自分の思い込みを破壊するほどスピードアップします。

ここでは、サーバの構造を変える事なく、お金をかける事もなく、プログラム的な工夫を凝らして、WordPressが実用に耐えられないレベルまで重くならないような対策の一例を書いてみます。

重い理由をつぶしていく

投稿数1万件を超えたWordPressが重くなる原因

投稿数が多いWordPressがサーバダウンするほど重くなる理由は、SQL文が非効率的なためです。

SQL文で処理を重くさせる構文とは、LIKE文が多い事や、OR文が多い事です。LIKE文とは全文一致などで使われるもので、特定の文字列が含まれているかどうかを検索する時などに使います。

OR文は、ある条件、もしくは他の条件のどちらかが一致するものを探す。という判別処理で、これが多用されている状態で、1万件以上の投稿数から検索して表示するような仕組みを作ると、非常に重いです。

しかも、こういった効率の悪い構文を使って、最新の10件だけ表示みたいなパーツをいくつも並べたものが、トップページです。そんなトップページを表示しようとするとサーバがタイムアウトして、503エラーを出すか、レンタルサーバが自衛処理を発動して403エラーが出るかします。

高速化するプラグインを導入する

WordPressを高速化するプラグインというものがあります。

例えばカスタム投稿タイプに関するプラグインなどに、機能追加する形でSQL構文を効率化したものなどのプラグインのためのプラグインが出ています。

プラグインで重くなっているところに、プラグインを重ねて、なんだかバカバカしく思えますが、プログラムの効率化は、本当に効果的で、40秒間かかっていたコンパイルがプログラムの効率化で2秒にまで縮まる。といった開発現場を見てきた筆者としては、こういったプラグインの導入を検討しても良いんじゃないかと思うところがあります。

ただ自分が抱えた案件ではお勧めはしません。バージョンアップしていく中、本体と、機能追加プラグインと、その機能追加の効率化のためのプラグイン、の3つのバージョンが噛み合わなくなり、プラグインが誤動作した時にデータベースを破壊する可能性が高いし、そもそも弱いサーバで多少SQL文を効率化したところで、あまり画期的な効果は期待できず、5000件までは大丈夫でも8000件、1万件と増えていけば、また重くなるのは目に見えているからです。

投稿が5000件超えないようにする

弱いレンタルサーバで、WordPressが実用に耐えられないほど重くなり始める目安は、だいたい5000件程度です。投稿数が3000件や4000件を超えたあたりから、クラウドサーバやCPUの強化などを検討し始めたほうが良いでしょう。

でも、投稿数を増やしても収益が期待できるわけもないのに、投稿を止められないという事態だった場合は、仕方がないので、5000件あたりでもうひとつブログを作ってそっちで更新するようにしてみてください。

それだったらもうWordPressやめて無料ブログでも借りたほうがいいんじゃね?とかいう話なんですが、そこで、WordPressでは複数の投稿タイプを管理する事ができる事を思い出してください。

例えば、1つの対策例として、投稿を5000件ごとに小分けして、必要に応じて管理する仕組みを作ります。

・投稿1~5,000件までの投稿タイプ
・投稿5,001~10,000件までの投稿タイプ
・投稿10,001~15,000件までの投稿タイプ
・投稿15,001~20,000件までの投稿タイプ
・投稿20,001~25,000件までの投稿タイプ

過去の投稿タイプはアーカイブ化して検索対象から外しても良いですし、あるいは検索対象の期間をわけるインターフェースを分けても良いでしょう。2019年、2020年、2021年と年ごとに投稿タイプを分けても良いかと思います。

ひとつの事例として、この方法を踏まえた上で、次の方法を探ります。

データベースへのアクセス回数を減らす

データベースの良いところは、情報の格納と取り出しがしやすい事です。WordPressも投稿した記事や、記録した情報と、その関連性をいつでも整理しなおす事ができます。Webサイトのトップページを表示するたびに、その何万件もの投稿と情報を再整頓させている状態である必要はないんじゃないかと思います。

取り出したい情報は記事だけなのに、その記事が参照しているなんらかの情報も取り出していたり、しばらく更新がないのに、毎日、最新記事の出力にデータベースが火を噴くほど回転させているんだとしたら、それをやめてしまえば良いと思います。それだったらもうHTMLで吐き出せば良い。

それってMovableTypeの事じゃないの?って感じなんですが、WordPressもせっかくPHPというサーバ上にファイル保存したり読み込んだりできるサーバプログラムを使っているんだから、あまり更新のない情報はJSONなどのファイルで保存しておいて、それを読み込むだけにしてしまえば、かなりデータベース負荷を減らす事ができます。

具体例

JSONで保存しておく場合

ここではWordPressのお得意ループ $queryPosts = new WP_Query()を1時間に1回だけ実行し、その結果をJSONに保存しておくサンプルを用意いたしました。

<?php

//データベースとの交信間隔の時間(例:1時間おき)
$nextUpdate = 1;

//最後にデータベースと交信した時間を保存
$update_url = "lastupdate.json";

//データベースからの返信内容を保存
$data_url = "blog.json";

$loadAllow = false;

//データベースの更新時間をチェック
$json = @file_get_contents(__DIR__ . $update_url);
$json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
$arr = json_decode($json, true);
$update_time = $arr["info"]["update"];

//最終更新時間からの経過
$timeUpdate = strtotime($update_time);
$timeToday = strtotime("now");
$diff = floor(($timeToday - $timeUpdate) / 60 / 60);

if($diff >= $nextUpdate){

 //データベースと交信

 $queryPosts = new WP_Query();
 $args = array(
  'post_type' => 'blog',
  'posts_per_page' => 10,
 );
 $queryPosts->query($args);

 if ( $queryPosts->have_posts() ) :
 while ( $queryPosts->have_posts() ) : $queryPosts->the_post();

  $data = [
   'title' => get_the_title(),
   'body' => get_the_content(),
   'link' => get_the_permalink()
  ];
  $blogObj[] = $data;

 endwhile;
 endif;

 $json = json_encode($blogObj);

 //投稿内容をサーバに保存
 $fileData = fopen(__DIR__ . $data_url, 'w+b');
 fwrite($fileData, $json);
 fclose($fileData);

 //更新した時間を保存
 $today_time = date("Y-m-d H:i:s");

 $obj = array(
  "info"=>array(
   "update"=>$today_time
  )
 );

 $data = json_encode($obj);
 $fileData = fopen(__DIR__ . $update_url, 'w+b');
 fwrite($fileData, $data);
 fclose($fileData);

 $loadAllow = true;
}

if($loadAllow == false){
 //更新しない場合はサーバ保存したデータを使う
 $json = @file_get_contents(__DIR__ . $data_url);
 $data = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
 $blogObj = json_decode($data, true);
}

//HTMLを出力
foreach ($blogObj as $key => $val) {
?>
<div>
 <a href="<?php echo $val['link'];?>">
  <h2><?php echo $val['title'];?></h2>
  <p><?php echo $val['body'];?></p>
 </a>
</div>
<?php
}
?>

これはあくまでもサンプルです。

意識したいのはWordPressの基盤を触らずに、表面の機能だけをカスタマイズして仕組みを作りこむ事です。

投稿タイプを小分して管理する方法や、JSONやHTMLに出力する仕組みを持たせて工夫する事で、WordPressのアップデートの影響を受けずに、大規模サイトの運用に対応させる事もできます。