CUEBiC TEC BLOG

キュービックTECチームの技術ネタを投稿しております。

メディアをダウンさせたmeta_queryの落とし穴

どうも、数年前複数メディアをバルスしたmikihoです。
実はこの内容自体はWordCamp2019でLTしたことあるんですが改めて振り返っていきます。
滅びの呪文もとい、WordPressの滅びのコードについて書いていきます。

前提の話

キュービックではWordPressで通常サイトとは違う、結構特殊な機能を盛り込んだテーマを使っていました。

その特殊機能の中の一つが「検索」です。
いわゆる「ユーザーが答えた質問によって、結果ページに表示させる内容を変える」というものでして。
キュービックは比較サイトを運営しておりますので、ユーザーにピッタリの内容を表示させるための機能でした

検索機能について

まず、比較対象の一つ一つを「案件」と呼んでおります。
そしてその案件は基本的に案件ごとに「カスタム投稿」で登録・管理を行なっておりました。
ただ、記事というわけではなくあくまで案件の情報を管理したいだけだったので、このカスタム投稿で多用していたのは「カスタムフィールド」。

そして、今思えば何を血迷ったのか、案件ごとの検索条件もなぜかカスタムフィールドに登録させてました。
勘がいい人たちはタイトルを加味してここら辺で気づきそうですね。

カスタムフィールドのDB保存

ちょっと本題の前のおさらい程度に。

そもそもカスタムフィールドの情報はwp_postsmetaというテーブルに保存されます。
1つの項目に対して1レコードで保存される形なので、たとえば1つの投稿に対して複数のカスタムフィールドの項目があればその分だけのレコードが紐づいていることになります。

今回で言えば、1つの投稿に対して大量のレコードとして登録されていたので、当然ながらwp_postsmetaのテーブルもかなりの容量になっていました。

問題を引き起こしたmeta_query

当時、検索のために使っていたのはWordPressのquery。
検索のために必要な情報はカスタムフィールド、つまりpost_metaに登録されてたのでそのためにmeta_query を使って引っ掛けておりました。

ちなみに、meta_queryというのはこんな感じ。

$args = array(
  'meta_query' => array( //カスタムフィールドに関するパラメーターをまとめた配列
    array(
      'key' => ‘meta_test’,  //カスタムフィールドのキー(meta_name)
      'value' => array(‘バルス’,’滅びの呪文’),   //検索したい値
      'type' => 'CHAR', //カスタムフィールドの値の型
      'compare' => 'IN'    //'value'パラメータの値に対する論理間関係を指定する
    )
  )
);

カスタムフィールドの内容を検索条件に加えるために、こんな感じのqueryを追加しておりました。
上記の例は1つだけですが、もちろん複数の内容を含めることもできます。

さて、少し切り口は変わりますがWordPressを触ってると当然 使う「WP_Query」。
こいつが裏側で生成しているSQLまで全部知ってるぜ、確認したぜって人はどれぐらいいますか?
WordPressが意外といい感じにしてしまうので、SQLのチェックまでしていなかったことが今回の滅びへの第一歩でしたね。

meta_queryの裏の顔

さて、こいつの仕様を調べてみるとそこまで変なことはしてないし便利ではないのかと思うところも結構あります。
事実、使っていた側からしてみれば問題が起きなければ便利だなーって思ってました。

ただし、実はこいつが裏側で生成しているSQLが曲者でした。

複数の条件を検索させる時、こいつ検索かけるwp_post_metaのテーブルを結合させていってたんです。
しかもそもそもwp_postsmetaはindexがなく、ただでさえカスタムフィールドを多用していると激重になりがちなテーブルを。

そう、検索させるための質問を増やせば増やすほどwp_postsmetaのテーブルをjoinさせていく悪魔みたいなSQLを使っていたのがこのmeta_queryでした。
ただでさえデータ量が膨大になったテーブルを質問数分結合させていき、最終的に負荷が高まってダウンするというとんでもない爆弾だったのです。

解決策

実は運用と兼ね合いですぐさまの解決は難しかったのです。

結論から言えば「カスタムフィールドを使うな」が大正解。
ですがそこを変えると膨大な登録のし直しが入るので運用側からしたら現実的じゃぁない。
というか、んなことしてられるかと思うだろう運用側の心の声が聞こえてきたので静かに却下しました。

そんなわけで、そもそも解決するにあたっての譲れない前提は。

  • 情報登録の方法は変えない
  • できるだけ停止時間を短くする

という二点

なにせ停止時間が長引けば売上にダイレクトに響いてきますからね。
ただでさえいつダウンするか分かったもんじゃない状態だったので、早急の解決が求められるのは当然でした。

というわけで、何をしたのかというとWP_Queryを全削除してSQLを自力で書きました。

原因はそもそもテーブルをjoinしまくるというとんでも仕様なので、それならば一時回避であればSQLをこちらがjoinしないように書いた独自のものにすればいいではないかと。
だいぶパワープレイでしたね。

ちなみに、まぁったくこれは根本解決じゃないのでぶっちゃけよろしくない実装ではあったと思います。
が、上で挙げた前提を考えると当時では最善に近いものだったんじゃないかと。

現在ではきちんと登録方法のあたりも含めて改善されました。(新しいテーマで)

まとめ

何気なく使っているWordPerssのQueryあたりは、裏側を気にせずに使いがちなので実装する時にSQLを確認することは大事です。
今回みたいなとんでもないことをしていることもあるし、そもそも無駄なことをしているなら記述を変えることでチューニングにもつながりますしね。

あと、この手の検索システムは負荷テスト大切。
あんまりWordPressって負荷テストするって印象がないのですが、こういうことが起こるとしみじみとテストの大切さを感じます。

当時のLT資料

www.slideshare.net

お知らせ!!

キュービックでは、一緒に働いてくれる仲間を募集しています。興味がある方はカジュアル面談Webエンジニアから応募をお願いします。ご応募お待ちしております。
herp.careers

また、エンジニアチームが運営しているYoutubeチャンネル【TEC TV】があるので、こちらでどんな人が働いているかを見ることができます!

www.youtube.com