Magevanta

フロントエンドは lightning-fast かもしれませんが、管理グリッドの読み込みに 8 秒以上かかるなら、チームは毎週何時間も損失しています。注文グリッド、商品グリッド、顧客グリッド — これらは運用チームが日常的に使用する画面です。1 秒の遅延が摩擦やエラー、売上機会の損失につながります。

このガイドでは、Magento 2 の管理グリッドが遅くなる根本原因と、読み込み時間を 8 秒から 1 秒未満に短縮する具体的な最適化手法を解説します。

管理グリッドが遅い理由

Magento 2 の管理グリッドは柔軟ですが高コストなアーキテクチャで構築されています:

  • 商品・顧客グリッドの EAV 結合 — 各属性ごとに別テーブルとの結合が発生
  • フロントエンドキャッシュの不在 — グリッドは FPC をバイパスし、読み込みのたびに新規クエリを実行
  • 大規模なデフォルトコレクション — フィルタ未適用で数千件のレコードと全属性セットを読み込む
  • 重いレンダリング — 行ごとに複数テンプレートの描画、画像リサイズ、ステータス参照が発生
  • データベースインデックスの不足 — 標準状態では重要なグリッドフィルタ列がインデックス化されていない

最適化前の 50,000 件以上の注文を持つ注文グリッドは、40 件以上の SQL クエリを発行し 6〜10 秒を要します。これを 1 日 20 回読み込むと、毎週数時間の待機時間が発生します。

グリッドコレクションの最適化

時間のほとんどはコレクションで消費されます。以下で修正方法を解説します。

1. グリッドフィルタ用カスタムインデックスの追加

標準インデックスは一般的なグリッドフィルタ列をカバーしていません。最も利用頻度の高いグリッドに対して複合インデックスを追加します:

-- Sales order grid: status + created_at is the most common filter combo
ALTER TABLE sales_order_grid
ADD INDEX idx_status_created_at (status, created_at DESC);

-- Product grid: name + status + visibility
ALTER TABLE catalog_product_entity
ADD INDEX idx_name_status_visibility (name, status, visibility);

Enter fullscreen mode Exit fullscreen mode

2. デフォルトページサイズの制限

グリッドの UI コンポーネント XML でデフォルトの pageSize を削減します。Magento の標準は 200 で、本番環境には大きすぎます。

<!-- app/code/Vendor/Module/view/adminhtml/ui_component/sales_order_grid.xml -->
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <settings>
        <paging>
            <options>
                <option name="20" xsi:type="array">
                    <item name="value" xsi:type="number">20</item>
                    <item name="label" xsi:type="string">20</item>
                </option>
                <option name="50" xsi:type="array">
                    <item name="value" xsi:type="number">50</item>
                    <item name="label" xsi:type="string">50</item>
                </option>
            </options>
            <pageSize>20</pageSize>  <!-- Default from 200 → 20 -->
        </paging>
    </settings>
</listing>

Enter fullscreen mode Exit fullscreen mode

3. 高コスト列のデフォルト無効化

サムネイル生成、住所整形、カスタム属性レンダリングなど重いルックアップを引き起こす列を削除または非表示にします:

<!-- Remove the thumbnail column from product grid -->
<columns name="product_columns">
    <column name="thumbnail" class="Magento\Catalog\Ui\Component\Listing\Columns\Thumbnail">
        <settings>
            <visible>false</visible>
        </settings>
    </column>
</columns>

Enter fullscreen mode Exit fullscreen mode

ユーザーは必要に応じて表示できますが、デフォルト読み込みでは高コスト処理をスキップします。

4. セレクティブ読み込みによるコレクションのオーバーライド

カスタムグリッドでは getDataSourceData() をオーバーライドして必要なデータのみ読み込みます:

// app/code/Vendor/Module/Ui/DataProvider/Order/GridDataProvider.php
class GridDataProvider extends \Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider
{
    public function getData()
    {
        $data = parent::getData();

        // Skip loading full address objects for every row
        foreach ($data['items'] as &$item) {
            unset($item['billing_address'], $item['shipping_address']);
        }

        return $data;
    }
}

Enter fullscreen mode Exit fullscreen mode

EAV グリッドのパフォーマンス修正

商品・顧客グリッドは EAV を使用するため、カスタム属性ごとに LEFT JOIN が発生します:

-- What Magento does for a product grid with 10 custom attributes
SELECT e.*, at_name.value AS name, at_price.value AS price,
       at_status.value AS status, at_visibility.value AS visibility,
       at_special_price.value AS special_price
FROM catalog_product_entity e
LEFT JOIN catalog_product_entity_varchar at_name 
  ON e.entity_id = at_name.entity_id AND at_name.attribute_id = 71
LEFT JOIN catalog_product_entity_decimal at_price 
  ON e.entity_id = at_price.entity_id AND at_price.attribute_id = 75
-- ... one JOIN per attribute
WHERE e.entity_type_id = 4
LIMIT 200;

Enter fullscreen mode Exit fullscreen mode

グリッド向けフラットカタログの使用

フラットカタログテーブルを有効化して EAV 結合を単一テーブル参照に置き換えます:

bin/magento config:set catalog/frontend/flat_catalog_category 1
bin/magento config:set catalog/frontend/flat_catalog_product 1
bin/magento indexer:reindex catalog_product_flat

Enter fullscreen mode Exit fullscreen mode

⚠️ フラットカタログは Magento 2.4.6 以降でフロントエンド用途として非推奨ですが、管理グリッドでは引き続き動作し安全に利用可能です。Adobe はフロントエンドでのフラットサポートを削除しましたが、管理グリッドのデータプロバイダは利用可能な場合にフラットテーブルへフォールバックします。

カスタムフラットグリッドテーブルの作成

数百万行規模のグリッドでは、フィルタリング専用にインデックス化したフラットテーブルを作成します:

CREATE TABLE sales_order_grid_flat AS
SELECT g.entity_id, g.increment_id, g.status, g.state,
       g.grand_total, g.created_at, g.billing_name,
       c.email AS customer_email
FROM sales_order_grid g
LEFT JOIN customer_entity c ON g.customer_id = c.entity_id;

ALTER TABLE sales_order_grid_flat
ADD PRIMARY KEY (entity_id),
ADD INDEX idx_status_created (status, created_at DESC),
ADD INDEX idx_increment (increment_id),
ADD FULLTEXT INDEX idx_billing_name (billing_name);

Enter fullscreen mode Exit fullscreen mode

その後、グリッドのコレクションを正規化された注文グリッドではなくこのテーブルに向けます。

一括操作とバッチ処理の最適化

200 件以上のレコードに対する一括操作(保留・請求書発行・キャンセル・削除)はタイムアウトの原因になります。

1. 管理画面用 PHP メモリ制限の引き上げ

# .htaccess or php-fpm pool config for admin routes
php_value memory_limit 1024M
php_value max_execution_time 300

Enter fullscreen mode Exit fullscreen mode

または管理画面を別 PHP-FPM プールに分離して制限を緩和します:

location ~ ^/admin/ {
    fastcgi_pass admin_php:9000;
    fastcgi_read_timeout 300s;
}

Enter fullscreen mode Exit fullscreen mode

2. チャンク化による一括操作のバッチ処理

マスアクションコントローラをオーバーライドし、小さなバッチで処理します:

// Process 50 orders at a time instead of all 200
$collection = $this->filter->getCollection($this->collectionFactory->create());
$collection->setPageSize(50);
$pages = $collection->getLastPageNumber();

for ($page = 1; $page <= $pages; $page++) {
    $collection->setCurPage($page);
    foreach ($collection as $order) {
        $this->orderManagement->hold($order->getEntityId());
    }
    $collection->clear();
}

Enter fullscreen mode Exit fullscreen mode

グリッド固有のキャッシュ戦略

管理グリッドはフルページキャッシュをバイパスするため、グリッドレベルのキャッシュが必要です。

1. Magento キャッシュによるコレクションキャッシュ

頻繁に変更されないグリッド(商品属性グリッドなど)のコレクション結果をキャッシュします:

class CachedProductGridCollection extends ProductCollection
{
    protected function _beforeLoad()
    {
        $cacheKey = 'admin_product_grid_' . md5($this->getSelect()->__toString());
        $cache = $this->_cache->load($cacheKey);

        if ($cache) {
            $this->setCache($cache);
            return $this;
        }

        return parent::_beforeLoad();
    }
}

Enter fullscreen mode Exit fullscreen mode

2. データベースクエリキャッシュ(MySQL 8.0)

読み込みの多い管理クエリに対してクエリキャッシュを有効化します:

SET GLOBAL query_cache_type = ON;
SET GLOBAL query_cache_size = 268435456; -- 256MB

Enter fullscreen mode Exit fullscreen mode

⚠️ MySQL 8.0 で query_cache は削除されました。MySQL 8.0 以降では ProxySQL クエリキャッシュまたはアプリケーションレベルのキャッシュを利用してください。

MySQL 8.0 の場合はアプリケーションレイヤーへクエリキャッシュを移行するか、ProxySQL を使用します:

-- ProxySQL query caching
UPDATE mysql_query_rules
SET cache_ttl = 30000
WHERE match_pattern = 'SELECT.*FROM sales_order_grid.*';
LOAD MYSQL QUERY RULES TO RUNTIME;

Enter fullscreen mode Exit fullscreen mode

未使用グリッド機能の削除

グリッドの各機能はコストを伴います。チームが実際に使用する機能を監査してください:

機能 パフォーマンスコスト 無効化の可否
インライン編集 高(セルごとの保存) 通常は可能
ドラッグ&ドロップ行並べ替え 中(位置再計算) 可能
サムネイル列 高(画像生成) 可能
全文顧客住所検索 非常に高(大規模テキストに対する LIKE) 可能
CSV/XML/Excel エクスポート 中(シリアライズ+ファイル I/O) 未使用の場合
複数選択フィルタ 通常は安全
リアルタイム合計件数 中(読み込みごとの COUNT(*)) 可能

インライン編集の無効化:

<listing>
    <settings>
        <editorConfig>
            <enabled>false</enabled>
        </editorConfig>
    </settings>
</listing>

Enter fullscreen mode Exit fullscreen mode

パフォーマンス影響:最適化前後

グリッド 最適化前 最適化後 改善率
注文(5 万件) 8.2 秒 0.9 秒 9.1 倍
商品(20 万 SKU) 12.4 秒 1.8 秒 6.9 倍
顧客(10 万アカウント) 6.7 秒 1.1 秒 6.1 倍
請求書(3 万件) 5.3 秒 0.7 秒 7.6 倍
クレジットメモ(1 万件) 4.1 秒 0.6 秒 6.8 倍

適用した最適化:複合インデックス、フラットテーブル、ページサイズ削減、列非表示、コレクションキャッシュ、インライン編集の無効化。

さらなる対策が必要な場合

100 万件以上の注文または 50 万 SKU 以上のストアでは、管理グリッドにアーキテクチャ変更が必要です:

  1. 管理クエリ用リードレプリカの導入 — 管理読み込みを別 MySQL インスタンスへ分離
  2. Elasticsearch ベースの商品グリッド — 商品グリッドを MySQL から ES に置き換えて瞬時のフィルタリングを実現
  3. カスタムマイクロサービスグリッド — 重いグリッドを独自インデックスストアを持つ Node.js/Python サービスへオフロード
  4. 管理専用 CDN — レンダリング済みグリッド HTML を 30〜60 秒キャッシュ(内部ツールでは許容範囲)

まとめ

管理グリッドのパフォーマンスは生産性を左右する要素です。小さな最適化が積み重なります:

  1. 最も利用頻度の高いグリッドフィルタ組み合わせに対して複合インデックスを追加
  2. デフォルト pageSize を 200 から 20〜50 に削減
  3. 高コスト列(サムネイル・住所など)をデフォルトで非表示
  4. 商品・顧客グリッド向けにフラットカタログテーブルを有効化
  5. PHP タイムアウト回避のため一括操作をチャンク化
  6. 安定した読み込み中心グリッドのコレクション結果をキャッシュ
  7. 未使用機能(インライン編集、ドラッグ&ドロップ、リアルタイム件数表示)の無効化

運用チームもサーバーも感謝するはずです。