ここひと月ほどお仕事が忙しくて更新できなかったのですが、やっと一段落しました。その際、WordPressでわりと大きめのデータを扱うことになったので、備忘録としてまとめます。今月から行われる破滅派リニューアルにも役立つかなーと。
データの規模
今回扱ったデータは全国のある施設を取り扱いました。不動産サイトのようなものをイメージしてください。カテゴリーとタグはそれぞれ数十件と常識的な範囲だったのですが、地理情報のタクソノミーが全国の都道府県〜市区町村〜町域のレベルまで。ざっとまとめるとこんな感じです。
- 投稿……200,000件
- カテゴリー……70件
- タグ……20件
- カスタムタクソノミー(address)……120,000件
ボトルネックになった部分
デフォルトの状態で使う分にはいくらデータが多くてもそのままで動くんじゃないかなー? と思ったんですが、駄目な部分がでてきました。また、プラグインとして対応していないものも幾つかありました。
タクソノミーが多過ぎてスタック
WordPressで開発を行う理由の一つに「管理画面がついてくる」というのがありますが、120,000件もタクソノミーを登録するとそうはいかなくなりました。
階層を持つタクソノミーの管理画面には親タクソノミーを表示するプルダウンがついてくるのですが、これが120,000件全部表示しようとしてタイムアウト。フックを探したのですが、どうにもならなかったので管理画面を表示しないようにしました。
同様にナビゲーションメニュー管理画面や投稿編集画面でも120,000件縦に並べるわけにもいかず非表示に。結果的にタクソノミーを管理するための画面を自分で作る必要がありました。
階層を取得するためにAjaxでいちいちリクエストを投げるインターフェース作ったり、けっこうめんどくさかったです。
カスタムフィールドが増え続ける
投稿が200,000件あったので、カスタムフィールドが増え続けました。wp_postmetaテーブルのレコードとしては2,000,000件以上でしょうか。もっとも、100万件以上というレコードの数自体はMySQLの性能として問題ない量なのですが(大手サイトでは何億件とか普通らしいです)、meta_valueフィールドだけインデックスが貼ってないので、そこで検索かけると遅いなーってことになります。
ちなみにインデックスというのは辞書でいうツメのようなもので、MySQLのようなRDBMSが検索するときにこれが設定されているといないとでは速度が段違いなんですね。設定されていない場合、毎回フルスキャンしてしまうようです。100件とかのデータ数だとあまり問題になりませんが、数十万件以上のデータだと如実に速度差が出ます。MySQLパフォーマンスチューニングのためのインデックスの基礎知識とか参考にしてください
meta_valueフィールドにインデックスを張るなどの対応策も考えられますが、検索性が求められるデータの場合は別テーブルにして専用の構造にした方がいいかもしれません。
ただし、その場合はadd_post_meta, update_post_meta, get_post_metaなどの関数群が使えなくなるので、自分で実装することになります。まあ、ここら辺はトレードオフですかね。
全文検索を使うならエンジンから見直し
WordPressには検索機能がついていますが、これは最終的にLIKE検索を行います。レコード件数が少ないとそんなに気になりませんが、ある程度の件数になるともっさりしてきます。
これを解決するための機能としてMySQLにはFULLTEXT検索というのがあるのですが、この機能が日本語に対応していません。正確に言うと、対応していないわけではないのですが、スペースやカンマで分かち書きをしたテキストしか全文検索できないのです。詳しくはMySQLで全文検索 – FULLTEXTインデックスの基礎知識というエントリーを見てください。
上のエントリーでも触れられていますが、こういう場合、日本の巨大サイトではtritonnという魔改造MySQLを使うケースが多いそうです。ニコニコ動画やPixivなどで実績があります。
このtritonnはすでにメンテナンスフェーズに入っており、MySQLのバージョンは5.0.87までです。今後新規でサイトを起こすときにはどうなのかなーと思う向きもあるかもしれませんが、tritonnの後継であるgroonga(こっちはストレージエンジンのようです)はまだそんなに情報がないので試していません。月一ぐらいで更新されているので、冒険心のある方は試してみてもいいんじゃないでしょうか。破滅派ではgroonga使ってみようかなと思ってます。WordPressはInnodbで運用することもできるので、たぶんgroongaでも大丈夫でしょう。
パフォーマンスが悪化したらプラグインを疑う
決め打ちはよくないですが、パフォーマンスが悪化(投稿保存後に止まっちゃうなど)したら、だいたいプラグインが原因です。多くのプラグインはデータが200,000件もあることを想定していません。
具体的な例を挙げるとGoogle XML Sitemapでしょうか。こちらは投稿保存後にサイトマップを更新しようとするので、200,000件一気に読み込もうとするんですね。あと、Googleは一件のサイトマップが50,000件以上のレコードを送信することを許可していないので、そもそも仕様上対応できません。これに対応しているのがBetter WordPress Google XML Sitemapsとなります。
他にもSearch Regexなどのように、データベースを一気に置換・修復しようとするものは大体タイムアウトしてしまいました。
なければ作るしかないので、今回の案件では5個ぐらいプラグイン作るはめになりました。中でもデータの置換作業が結構発生したのですが、これがけっこう時間かかります。インポートで40時間とかかかりました。このときに作ったデータベースの置換作業をAjaxで小分けにして行うためのプラグインを置いときます。
大規模データを扱う場合に問題なく稼働するプラグインはけっこう少なかったです。WordPressの開発でプラグイン依存度が高い方(PHP詳しくないけどプラグインでなんとかしてきた方)は注意です。
検索機能を作りこむ
データが200,000件もあると、WordPressによくあるインターフェースだけでは役に立たなくなってきます。WordPressは元がブログなので、時系列が中心なんですね。200,000件全部見るという暇な人もいないと思いますので、当然絞り込み検索とかを実装することになるのですが、基本はWordPressのループ機構を乗っ取るような形にするのがいいと思います。
今回の案件では僕がテーマも作ったので問題なかったですが、「デザイナーだけどWordPressもいじれるよ」というスキルセットの人と一緒にやる場合は自前の関数の使い方を教えなきゃいけなかったりして、けっこうめんどくさいんですね。以下のような形にすれば元のクエリを壊すことはないので、パンくずもページネーションもプラグインなどで動いてくれます。
/** * 複合検索のときにJOIN節を返す * @global wpdb $wpdb * @param string $join * @return string */ function _advanced_search_join($join){ global $wpdb; //オリジナル検索ページだったら if(is_page('my-search')){ //適当なテーブルをJOIN if(isset($_GET['address']) && !empty($_GET['address'])){ $address = implode(',', array_map('intval', explode('+', $_GET['address']))); $join .= <<<EOS INNER JOIN ( SELECT object_id, count(object_id) AS count FROM {$wpdb->term_relationships} LEFT JOIN {$wpdb->term_taxonomy} ON {$wpdb->term_taxonomy}.term_taxonomy_id = {$wpdb->term_relationships}.term_taxonomy_id WHERE {$wpdb->term_taxonomy}.taxonomy = 'address' AND {$wpdb->term_taxonomy}.term_id IN ({$address}) GROUP BY {$wpdb->term_relationships}.object_id ) AS address ON {$wpdb->posts}.ID = address.object_id EOS; } } //デフォルトのクエリだけに適用したいので、一回目で削除する remove_filter('posts_join', '_advanced_search_join'); return $join; } //query_postsの結合条件を追加 add_filter('posts_join', '_advanced_search_join');
フィルターは他にもposts_order_byとかposts_whereとかいっぱいある(フィルターAPI参照)ので、これを組み合せれば複雑な検索ができます。
まあ、どうせフォームを作らなきゃいけないので、テーマに手を入れなければいけないことにかわりないんですが。ここら辺の分業はけっこう難しいなーと感じます。
WordPressは大規模サイトに向いているか?
この案件をやっている途中に「それってWordPressじゃない方がいいんじゃないですかー?」と知人に言われたのですが、基本的に問題なかったです。WordPressはWordPress.comというかなりユーザー数の多いブログサービスをやっているので、データの多寡はあまり問題になりません。HyperDBなどのプラグインもあるので、スケールすることは可能です。
WordPressでやるべきか否かという問題を判断する場合、僕はサービスの質を見るようにしています。
Facebookなどがそうですが、サービスにおけるコンテンツの発信者とコンテンツの受信者が限りなく近いものはWordPressじゃない方がいいっぽいです。具体的にどれぐらいかという閾値はわかりませんが、サービスが拡大するにつれて必要とされる部分がWordPressっぽくなくなっていくので。やろうと思えばできるので、無理してやるのもありだとは思いますが。
逆にコンテンツを作る人とコンテンツを受け取る人が違う場合はWordPressだと管理画面もユーザー管理もついてて楽です。
おわりに
というわけで、WordPressで大規模データを扱うときの注意点についてまとめました。備忘録なので気づいたことがあったら追加します。
[429] [429] Client error: `POST https://webservices.amazon.co.jp/paapi5/getitems` resulted in a `429 Too Many Requests` response: {"__type":"com.amazon.paapi5#TooManyRequestsException","Errors":[{"Code":"TooManyRequests","Message":"The request was de (truncated...)