[WordPress] WP Offload Media Lite プラグインに データベースを殺されかけた話

WP Offload Media Lite プラグインは、Wordpressに保存される画像をAmazonS3などのストレージにうpしてくれるやつ。
Jetpackにwordpress.comの配信サービスでやっつけてくれるお手軽なオプションがついてるので、あえてAmazonS3を使おうという個人ユーザーは少ないでしょう。
AmazonS3を使おうとするのはほとんど商業ベースだと考えられます。なので同じ轍を踏んでヴァーとならないために出来事をまとめました。

⚠️ この記事はWP Offload Media Lite を使ってない人には関係ない内容となっています。

経緯

Wodpressで作られた商用サイトがありました。
そのサイトでは、以下2つのプラグインを利用しています。

最近このサイトリニューアルしました。
するとリリース後しばらくしてサーバーサイドエンジニアが青い顔で言いました。

「CPU負荷がやばいっす…」

んーーーーーーーーーーー?

商用サービスなので事前のクエリチェックや負荷チェックなどしてもらっていて、重いクエリはないと言われてたんですが。

SELECT `post_id`
            FROM `wp_postmeta`
            WHERE `wp_postmeta`.`meta_key` = 'amazonS3_info'
            AND ( `wp_postmeta`.`meta_value` LIKE '%s:3:\"key\";s:20:\"category/icon/1234.jpg\";%'
            OR `wp_postmeta`.`meta_value` LIKE '%s:3:\"key\";s:13:\"icon/1234.jpg\";%' );

このクエリで0.5s〜1sかかっていることが判明した。

metaキーがamazonS3_info、つまり WP Offload Media Lite が発行しているクエリでした。

対応

まず、そのicon/1234.jpgが保存されているのが自前で設定したカスタムフィールドだけだったので、
そのカスタムフィールドを使わないようにしてみた。幸い、数字の部分が記事IDだったので難なく終わった。

しかしまだ似たようなクエリが残ってると言われてしまった。
他になんかあるかしらとググってみたところ、Githubで完全一致するissueを発見。
This query is killing my database #458
タイトルもまさにその通りでだいぶ感動しましたね…。

issueには classes/as3cf-plugin-compatibility.php#L65 をコメントアウトしたら治った、と書いてある。

add_filter( 'attachment_url_to_postid', array( $this, 'customizer_background_image' ), 10, 2 );

attachment_url_to_postid は WordPressの関数で、その名前の通り画像URLからIDを取得するためのもの。
これが使われたときに、 WP Offload Media Lite がフィルタを実行するが、それが重いと言われたクエリの発行場所だった。

classes/as3cf-plugin-compatibility.php#L529

$sql = $wpdb->prepare( "
	SELECT `post_id`
	FROM `{$wpdb->prefix}postmeta`
	WHERE `{$wpdb->prefix}postmeta`.`meta_key` = 'amazonS3_info'
	AND ( `{$wpdb->prefix}postmeta`.`meta_value` LIKE %s
	OR `{$wpdb->prefix}postmeta`.`meta_value` LIKE %s )
",
	"%s:3:\"key\";s:{$length1}:\"{$key1}\";%",
	"%s:3:\"key\";s:{$length2}:\"{$key2}\";%"
);

これだ!🙄

attachment_url_to_postid は、 Categories Images が保存したアイコン画像URLから画像IDを取得するのに使っていた。

こんな感じ:

$attachment_id = null;
$img = null;
$taxonomy_image_url = get_option('z_taxonomy_image'.$term->term_id);

if(!empty($taxonomy_image_url)) {
  $attachment_id = attachment_url_to_postid($taxonomy_image_url);
  $img = wp_get_attachment_image($attachment_id, $size);
}

Categories Imagesが保存した画像をカテゴリーとかの出力時に使おうとしたら、
タームIDを使ってオプションとして保存されている画像URLを取得してから、そのURLで画像IDを取得して、
wp_get_attachment_image などを実行するという手順になるわけです。

で、元々は画像URLから画像IDを取得するのにプラグインの関数を利用してたんですが:

$attachment_id = z_get_attachment_id_by_url($taxonomy_image_url);

WordPress内臓の関数でできるなら、そっちを使う方がいいのでは?🤔と思ってリニューアル時に変えました:

$attachment_id = attachment_url_to_postid($taxonomy_image_url);

そうしたところ、例の重いクエリが発行されるフィルタが発動するようになってしまったのでした。

確かにクエリの重さ、全然違います:

//categories-images.php
function z_get_attachment_id_by_url($image_src) {
    global $wpdb;
    $query = $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE guid = %s", $image_src);
    $id = $wpdb->get_var($query);
    return (!empty($id)) ? $id : NULL;
}

以下はCategories Imagesの関数使うように戻した、タームに設定された画像返す関数:

/**
 * taxonomy画像を得る
 * @param WP_Term $term 画像を得たいタームのオブジェクト
 */
function get_tax_image_by_term($term = NULL, $size = 'full') {

  $attachment_id = null;
  $img = null;
  $taxonomy_image_url = get_option('z_taxonomy_image'.$term->term_id);

  if(!empty($taxonomy_image_url)) {
    if (function_exists('z_get_attachment_id_by_url')) {
      $attachment_id = z_get_attachment_id_by_url($taxonomy_image_url);
    } else {
      $attachment_id = attachment_url_to_postid($taxonomy_image_url);
    }

    $img = wp_get_attachment_image($attachment_id, $size);

    $img = $this->parse_image($img);
  }

  return array(
    'html' => $img,
    'url' => $taxonomy_image_url,
  );

結果、

負荷なくなりました。

attachment_url_to_postid に対するWP Offload Media Liteのフィルタは生きたままなので、
画像URLからIDを取得するロジックの実行回数が多い場合は、 Categories Imagesのように直接$wpdbからクエリを発行しないとデータベース殺られるかもしれません。

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください