Concrete CMS Express 大規模データ運用:ランダムソート高速化の実践事例

2026/02/07
Concrete CMS の Express 機能は柔軟で扱いやすい反面、大量データを扱う際にはパフォーマンス面で課題が生じることがあります。
本記事では、15,000 件以上の Express エントリーを高速にランダム表示するための最適解を、実際の改善プロセスとともに紹介します。

 

1. Express 属性を 1 件ずつ save() すると 24 時間以上かかる問題

Express の属性値を更新する際、通常は以下のように $entry->setAttribute()$entry->save() を行います。

しかし、この save() は内部で次のような重い処理を伴います。

  • Doctrine ORM によるエンティティ生成・永続化
  • Express 属性値テーブルへの INSERT/UPDATE
  • 検索インデックスの再構築
  • バージョン管理
  • キャッシュの更新
  • イベントフックの発火

これらが 1 件ごとに発生するため、
「1 分で 10 件」→「15,000 件で 24 時間以上」という結果になりました。

大量データの一括更新には、Express の属性更新は向いていません。


2. SQL で直接 random_value を書き換えてもソートできない理由

「では SQL で直接 random_value を更新すれば高速なのでは?」
と考えるのは自然ですが、Express の属性は単純な値ではありません。

Express 属性は以下の複数レイヤーで管理されています。

  • 属性値テーブル
  • 検索インデックス
  • Doctrine のキャッシュ
  • エンティティのメタ情報

そのため、SQL で値だけ書き換えても Express 側が変更を認識しません。
結果として、ソートに使われる値が更新されず、表示順が変わらないという問題が発生します。


3. 解決策:独立した random_value テーブルを作り、JOIN でソートする方式へ

Express の属性 random_value を使うのではなく、
完全に独立したテーブルを作成し、そこにランダム値を保持する方式に切り替えました。

● 新しいテーブル構造(例)

IllustRandomValue
  exEntryID     (Express のエントリーID)
  random_value  (ランダムソート用の値)

● ソートは INNER JOIN で実現

ExpressEntityEntries e
JOIN IllustRandomValue rv ON rv.exEntryID = e.exEntryID
ORDER BY rv.random_value ASC

これにより:

  • Express の属性システムに依存しない
  • Doctrine のキャッシュ問題も回避
  • SQL で高速に更新可能
  • 15,000 件でも 0.1 秒でランダム値更新が完了

という大幅な改善が実現しました。


4. 新規エントリーにも自動で random_value を付与するジョブを作成

CSV インポートで新規エントリーが追加された場合、
IllustRandomValue に対応する行がまだ存在しません。

そこで、次の処理を行うジョブを作成しました。

  • IllustRandomValue に存在しない exEntryID を検出
  • INSERT して初期 random_value を付与
  • 既存行は UPDATE でランダム値を更新

これにより、新規エントリーも自動でランダムソートに組み込まれるようになりました。


5. 最終的な運用フロー

① CSV インポート(Express に新規エントリーが追加される)
② update_random_value_joinsort ジョブを実行
    → 新規エントリーに random_value を付与
    → 既存エントリーも更新
③ 表示テンプレートで INNER JOIN ソート

この構成により:

  • 大量データでも高速
  • 表示順が安定
  • 新規データも確実に反映
  • Express の属性構造に依存しない
  • Concrete CMS のバージョン差異にも強い

という、長期運用に耐える仕組みが完成しました。


まとめ

Concrete CMS の Express は便利ですが、大量データの更新には工夫が必要です。
今回の改善では、Express の属性更新の重さを回避し、
独立テーブル + JOIN ソートというシンプルで高速な方式に切り替えることで、
15,000 件以上のデータを快適に扱えるようになりました。

同様の課題を抱える開発者の参考になれば幸いです。

 


#
2025/04/26
Concrete CMSのエクスプレスデータベースには標準でCSV入力が無いのですね。そこでJOBに登録して簡単に利用できるものを作ってみました。
#
2025/05/01
1万件を超えるConcrete CMSエクスプレスデータを削除しようと「エントリーをクリア」を実行したところ、タイムアウトエラーが発生。 手作業で一つずつ削除するのは現実的ではないため、一括削除プログラム を作成しました。
#
2025/05/12
Concrete CMSのExpressデータを大量に扱う中で、同一レコードの重複登録や既存データの更新が必要になることがあります。 このジョブプログラムは、最終更新日を基準に最新のレコードを残し、古い重複データを自動的に判別・整理することで、登録データの一括更新を効率化します。 これにより、管理作業の手間を減らし、常に最新かつ正確なデータを維持することができます。
#
2025/05/13
Concrete CMSの標準機能では、説明(description)がmeta descriptionに変換されない問題を解決するための「Description to Meta」Jobプログラムを開発しました。これにより、記事概要を自動でmeta descriptionへコピーし、検索結果の表示を最適化できます。SEO対策と運用効率を向上させるための便利なツールです。
#
2025/05/18
Concrete CMS の Express エントリ一覧において「最大 1000 件までしか取得できない」という制限に直面し、標準の EntryList や Express ブロックでは対応が難しかった。ORM の制約によりカスタマイズによる解決も困難だったが、Doctrine クエリビルダーを直接用いる方法へ移行。その結果、大規模データを扱う要件を満たす実装が実現できた。副次的にスペース区切りでのAND検索機能も獲得できた。
#
2025/12/27
Concrete CMS の Express を使ってイラスト管理システムを構築しているとき、思わぬ落とし穴にはまった。 「キーワード検索を illust_kw だけでなく、illust_title や illust_no(数値)にも広げたい」 ──ただそれだけの話のはずだった。 しかし、実際に手を動かしてみると、numeric 属性の扱いが想像以上に曲者だった。
#
2026/02/07
Concrete CMS の Express 機能は柔軟で扱いやすい反面、大量データを扱う際にはパフォーマンス面で課題が生じることがあります。 本記事では、15,000 件以上の Express エントリーを高速にランダム表示するための最適解を、実際の改善プロセスとともに紹介します。