mcpField
W
woocommerce-performance

woocommerce-performance

github.com/finsilabs開発6084 回インストール1 週間前に更新
リポジトリ

woocommerce-performance について

WooCommerceの開発者や運営者向けに、ストアの性能調査と改善方法をまとめたスキルです。クエリ分析、オブジェクトキャッシュ、データベース最適化、定期的なクリーンアップを扱います。

主な機能

  1. 01Query Monitorによるクエリ分析
  2. 02Redis永続オブジェクトキャッシュの設定
  3. 03WooCommerceのDBクエリ最適化
  4. 04セッションと一時データの削除
  5. 05自動読み込みオプションの確認

活用シーン

  1. 01ストアページの表示遅延の調査
  2. 02AJAXによるDB負荷の軽減
  3. 03増加したWooCommerceセッションテーブルの管理
  4. 04wp_optionsの自動読み込み負荷の削減

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill woocommerce-performance

インストール後、Claude Code で/woocommerce-performanceを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

tile.json
evals/database-cleanup-and-session-maintenance/
criteria.json
task.md
evals/query-optimization-and-n-1-prevention/
criteria.json
task.md
evals/redis-object-cache-configuration/
criteria.json
task.md

詳細

WooCommerce のパフォーマンス

概要

WooCommerce のパフォーマンス問題は、通常、インデックスのないメタテーブルに対する高コストな商品クエリ、制限のない AJAX のカート/チェックアウト呼び出し、永続オブジェクトキャッシュの欠如、肥大化した wp_options の自動読み込みデータ、そしてクリーンアップされずに増え続ける wp_woocommerce_sessions と注文テーブルという、5 つの原因から生じます。このスキルでは、プロファイリング、クエリの最適化、Redis オブジェクトキャッシュ、定期メンテナンスのルーチンを扱います。

このスキルを使う場面

  • 中程度のトラフィック(同時接続ユーザー数百人)で、ストアのページ読み込みが遅い場合
  • WooCommerce の AJAX 呼び出し(カートへの追加、送料計算)中にサーバーの CPU 使用率が急上昇する場合
  • New Relic または Query Monitor で、MySQL のクエリ時間が主なボトルネックとして表示される場合
  • wp_options テーブルの自動読み込みサイズが 1~2 MB を超える場合
  • wp_woocommerce_sessions の行数が数百万件に達し、セッション検索が遅くなっている場合
  • トラフィックの多いストアで MySQL の負荷を軽減するために、Redis または Memcached を導入する場合

主な手順

  1. まず Query Monitor でプロファイリングする

    Query Monitor プラグインをインストールして、管理画面とフロントエンドの遅いクエリを特定します。

    # Via WP-CLI
    wp plugin install query-monitor --activate
    

    次の項目に注目します。

    • ページ読み込みごとのクエリ数(50 件を超える場合は要注意)
    • 重複クエリ(同じクエリが複数回実行されている)
    • 遅いクエリ(個々のクエリに 100ms 以上かかる)
    • 大きな結果セット(数千行を返すクエリ)

    本番環境のプロファイリングでは、MySQL のスロークエリログを有効にします。

    SET GLOBAL slow_query_log = 'ON';
    SET GLOBAL long_query_time = 0.5;
    SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
    
  2. Redis の永続オブジェクトキャッシュを有効にする

    WooCommerce で最も効果の高い最適化です。これがないと、ページの読み込みごとに同じ商品データを MySQL から再取得します。

    # Install Redis server
    sudo apt install redis-server
    
    # Install the Redis Object Cache plugin
    wp plugin install redis-cache --activate
    wp redis enable
    

    wp-config.php で設定します。

    define('WP_REDIS_HOST', '127.0.0.1');
    define('WP_REDIS_PORT', 6379);
    define('WP_REDIS_DATABASE', 0);
    define('WP_REDIS_TIMEOUT', 1);
    define('WP_REDIS_READ_TIMEOUT', 1);
    define('WP_REDIS_MAXTTL', 86400); // 24 hours max TTL
    
    // Selective cache groups — exclude user sessions from Redis (they change constantly)
    define('WP_REDIS_IGNORED_GROUPS', ['wc_session_id', 'counts', 'plugins']);
    
  3. WooCommerce 固有のデータベースクエリを最適化する

    <?php
    
    // Add missing indexes for common WooCommerce product queries
    // Run once via WP-CLI: wp eval-file add-indexes.php
    
    global $wpdb;
    
    // Index for product meta queries (stock status, visibility, price)
    $wpdb->query("
        ALTER TABLE {$wpdb->postmeta}
        ADD INDEX wc_product_meta_lookup (meta_key(32), meta_value(64))
    ");
    
    // Index for order meta queries
    $wpdb->query("
        ALTER TABLE {$wpdb->postmeta}
        ADD INDEX wc_order_customer_lookup (meta_key(32), meta_value(100))
    ");
    

    WooCommerce の HPOS(High-Performance Order Storage)を使用して、注文データを投稿メタから移動します。

    // wp-config.php — enable HPOS (WooCommerce 7.1+)
    // This is configured via WooCommerce Settings → Advanced → Features
    // Enables dedicated order tables: wp_wc_orders, wp_wc_order_items, etc.
    
    # Check HPOS status
    wp wc hpos status
    # Migrate orders to HPOS
    wp wc hpos migrate --batch-size=500
    
  4. トランジェント、セッション、ログテーブルをクリーンアップする

    <?php
    // Scheduled cleanup — run daily via Action Scheduler
    
    add_action('my_plugin_daily_cleanup', function () {
        global $wpdb;
    
        // Delete expired WooCommerce sessions (older than 48 hours)
        $expiry_threshold = time() - (48 * HOUR_IN_SECONDS);
        $wpdb->query($wpdb->prepare("
            DELETE FROM {$wpdb->prefix}woocommerce_sessions
            WHERE session_expiry < %d
            LIMIT 5000
        ", $expiry_threshold));
    
        // Delete expired transients
        $wpdb->query("
            DELETE FROM {$wpdb->options}
            WHERE option_name LIKE '_transient_timeout_%'
              AND option_value < UNIX_TIMESTAMP()
            LIMIT 5000
        ");
        $wpdb->query("
            DELETE t FROM {$wpdb->options} t
            LEFT JOIN {$wpdb->options} e
                ON e.option_name = REPLACE(t.option_name, '_transient_', '_transient_timeout_')
            WHERE t.option_name LIKE '_transient_%'
              AND e.option_id IS NULL
            LIMIT 5000
        ");
    
        // Rotate WooCommerce logs older than 30 days
        $wpdb->query("
            DELETE FROM {$wpdb->prefix}woocommerce_log
            WHERE timestamp < DATE_SUB(NOW(), INTERVAL 30 DAY)
            LIMIT 10000
        ");
    });
    
    // Register the scheduled event
    add_action('init', function () {
        if (!as_next_scheduled_action('my_plugin_daily_cleanup')) {
            as_schedule_recurring_action(strtotime('tomorrow midnight'), DAY_IN_SECONDS, 'my_plugin_daily_cleanup');
        }
    });
    
  5. wp_options の自動読み込みテーブルを最適化する

    自動読み込みオプションは、ページリクエストごとに読み込まれます。自動読み込みデータの肥大化は、大きなオーバーヘッドを引き起こします。

    -- Find large autoloaded options
    SELECT option_name, LENGTH(option_value) as size_bytes
    FROM wp_options
    WHERE autoload = 'yes'
    ORDER BY LENGTH(option_value) DESC
    LIMIT 30;
    
    <?php
    // Fix: Store plugin data as transients or post meta instead of autoloaded options
    
    // Bad — autoloaded, loaded on every request
    update_option('my_plugin_product_cache', $large_array, true);
    
    // Good — not autoloaded
    update_option('my_plugin_product_cache', $large_array, false);
    
    // Better — use transient with expiry
    set_transient('my_plugin_product_cache', $large_array, HOUR_IN_SECONDS);
    
    // Disable autoload for existing options
    global $wpdb;
    $wpdb->update(
        $wpdb->options,
        ['autoload' => 'no'],
        ['option_name' => 'woocommerce_attribute_taxonomies']
    );
    

例

商品ループのフラグメントキャッシュ
<?php

// Cache rendered product card HTML in Redis to avoid re-rendering
function render_product_card_cached(int $product_id): string {
    $cache_key = "product_card_{$product_id}_" . get_locale();
    $cached = wp_cache_get($cache_key, 'product_cards');

    if ($cached !== false) {
        return $cached;
    }

    $product = wc_get_product($product_id);
    if (!$product) return '';

    ob_start();
    wc_get_template('content-product.php', ['product' => $product]);
    $html = ob_get_clean();

    // Cache for 30 minutes; invalidate on product update
    wp_cache_set($cache_key, $html, 'product_cards', 30 * MINUTE_IN_SECONDS);

    return $html;
}

// Purge cache when product is updated
add_action('woocommerce_update_product', function (int $product_id) {
    wp_cache_delete("product_card_{$product_id}_" . get_locale(), 'product_cards');
    // Also delete all locale variants
    wp_cache_flush_group('product_cards');
});
N+1 商品クエリを検出して修正する
<?php

// Bad — triggers N+1 queries (one per product in loop)
$product_ids = wc_get_featured_product_ids();
foreach ($product_ids as $id) {
    $product = wc_get_product($id); // <-- separate query for each product
    echo $product->get_name();
}

// Good — prime the cache before the loop
$product_ids = wc_get_featured_product_ids();
// Pre-warm the object cache with all products in a single query
$products = array_map('wc_get_product', $product_ids); // Still N queries without this:

// Better — use WC_Product_Query with ID filter (single query)
$products = wc_get_products([
    'include' => $product_ids,
    'limit'   => count($product_ids),
    'return'  => 'objects',
]);

foreach ($products as $product) {
    echo $product->get_name(); // data already loaded
}

ベストプラクティス

  • WooCommerce 7.1 以降のストアでは HPOS(High-Performance Order Storage)を有効にします — 注文データを適切なインデックスを持つ専用テーブルに移動し、注文データに対する wp_postmeta のボトルネックを解消します
  • 何よりも先に Redis/Memcached のオブジェクトキャッシュを追加します — WordPress と WooCommerce がすべてのリクエストで実行する冗長な MySQL クエリをなくし、データベースの負荷を 60~80% 削減できることもよくあります
  • Action Scheduler を使って、アクセスの少ない時間帯にクリーンアップを実行します — クリーンアップ中にテーブルが長時間ロックされるのを避けるため、DELETE 操作は 1 回の実行につき 5000 行を上限としてバッチ処理します
  • WP_DEBUG_LOG と SAVEQUERIES は開発環境でのみ使用します — 本番環境でこれらを有効にするとパフォーマンスが低下します。本番環境のプロファイリングには APM ツール(New Relic、Datadog)を使用します
  • 管理画面の注文クエリをページネーションします — すべての注文を一度に読み込む(posts_per_page: -1)と、MySQL がロックされてタイムアウトが発生します。常に paged を使用し、posts_per_page は 100 以下にします
  • WP_REDIS_IGNORED_GROUPS を設定して、変動の多いデータ(カート、セッションカウンター)を Redis から除外します — トラフィックの多いチェックアウトページでキャッシュスタンピードが発生するのを防ぎます
  • wp_options テーブルのサイズを毎週監視します — 自動読み込みデータが 800KB を超えた場合にアラートが出るよう設定します。よくある原因は、大きな配列を自動読み込みオプションとして保存するプラグインです

よくある落とし穴

問題解決策
Redis キャッシュを使っても MySQL クエリが減らないQuery Monitor で wp_cache_get のヒット率を確認します。ヒット率が低い場合、キャッシュキーが頻繁に無効化されすぎているか、TTL が短すぎます
クリーンアップクエリによってテーブルレベルのロックが発生するDELETE クエリで行レベルの LIMIT 句を使用し(1 回の実行につき最大 5000 行)、トラフィックが少ない時間帯にスケジュールします
HPOS への移行によってサードパーティ製プラグインが動作しなくなるHPOS を有効にする前に、すべてのプラグインで get_post_meta(order_id, ...) が使われていないか監査します。該当箇所は $order->get_meta() を使うよう更新する必要があります
インデックスがあるのに商品一覧の表示が遅いwc_lookup_table_enabled が有効か確認します。WooCommerce 3.7+ では wp_wc_product_meta_lookup テーブルが導入され、商品クエリが大幅に高速化されます
商品インポート後にオブジェクトキャッシュが古いままになる一括インポートスクリプトの最後で wp_cache_flush() または wp_cache_flush_group('products') を呼び出し、古い商品キャッシュを無効化します
クリーンアップを行ってもセッションテーブルが増え続けるWC_Session_Handler がデータベースバックエンド(PHP セッションではなく)を使用していることを確認します。デフォルトの Cookie ベースのセッションでは DB 行は作成されませんが、カスタムプラグインによって DB セッションが強制される場合があります

関連スキル

  • @woocommerce-plugin-development
  • @woocommerce-rest-api
  • @database-query-optimization
  • @caching-strategies
  • @infrastructure-performance

コメント