【EC-CUBE 4】重い集計クエリをSymfony Cacheで高速化する方法
EC-CUBE 4の案件で、商品数を表示している箇所があり商品数が多くパフォーマンスが悪い問題がありました。
Symfony Cache を使ってある程度解決することができたので備忘録として記事にしました。
あくまで例として簡単に作ったものなので、ご自身の環境に合わせて適宜読み替えてください。
課題
- 集計クエリが毎リクエスト実行される
- 頻繁に更新されないデータなのに、毎回DBにアクセスしている
解決策
Symfony Cacheを使い、集計結果を**300秒(5分)**キャッシュする。
実装
1. カスタムRepositoryを作成
<?php
namespace Customize\Repository;
use Doctrine\Persistence\ManagerRegistry;
use Eccube\Common\EccubeConfig;
use Eccube\Repository\ProductRepository;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
class CachedProductRepository extends ProductRepository
{
private CacheInterface $cache;
private const CACHE_LIFETIME = 300;
public function __construct(
ManagerRegistry $registry,
EccubeConfig $eccubeConfig,
CacheInterface $cache
) {
parent::__construct($registry, $eccubeConfig);
$this->cache = $cache;
}
/**
* カテゴリ別商品数を取得(キャッシュ付き)
*/
public function getProductCountByCategory(): array
{
return $this->cache->get('product_count_by_category', function (ItemInterface $item) {
$item->expiresAfter(self::CACHE_LIFETIME);
return $this->createQueryBuilder('p')
->select('c.id, c.name, COUNT(p.id) as product_count')
->innerJoin('p.ProductCategories', 'pc')
->innerJoin('pc.Category', 'c')
->where('p.Status = :status')
->setParameter('status', 1)
->groupBy('c.id')
->getQuery()
->getArrayResult();
});
}
}
2. services.yaml
services:
Customize\Repository\CachedProductRepository:
autowire: true
arguments:
$cache: '@cache.app' # 書かなくてもいいと思うが明示してくと安心
cache->get() の動作
- キャッシュあり → 即座に返却(DBアクセスなし)
- キャッシュなし → クエリ実行 → キャッシュ保存 → 返却
効果
| 指標 | Before | After |
|---|---|---|
| キャッシュ有効期間 | なし | 300秒 |
| 5分間のDBクエリ | 最大300回 | 1回 |
補足
-
cache.appはファイルベースのキャッシュ(デフォルト) - キャッシュキーは一意になるよう命名すること