クラスタリング

クラスタリングは、共有リポジトリに対する複数ノード構成を可能にします。コンテンツ(データベース)とブロブストレージを共有しつつ、各ノードはローカルの検索インデックス・一時領域を持ちます。ノード間の協調はデータベースのリース(lock)と、トランザクションを再生するジャーナルで行われます。

構成

リポジトリ

# <repository>/etc/repository.yml
cluster:
  enabled: true
  nodeId: node-1   # 任意。ノードごとに一意

ノードごとに環境変数 CMS_CLUSTER_NODE_ID、またはフレームワークプロパティ org.mintjams.jcr.cluster.nodeId / org.mintjams.jcr.cluster.enabled で上書きできます。nodeId 省略時はホスト名(取得できなければランダム)。

ワークスペース(共有ストレージ)

# <workspace>/etc/jcr/jcr.yml
datasource:
  jdbcURL: jdbc:postgresql://db:5432/jcr_\${workspace.name}
  username: jcr
  password: secret
  driverClassName: org.postgresql.Driver
blobstore:
  type: fs
  directory: /mnt/shared/cms/blobs/\${workspace.name}
search:
  indexPath: /var/lib/cms/search/\${workspace.name}   # ノードローカルの高速ストレージ

\${repository.home}\${workspace.name}\${cluster.nodeId} などの変数置換に対応します。検索インデックスはノードごとに保持され、空であればコンテンツから自動再構築されます。

永続状態の置き場所

状態 スタンドアロン(既定) クラスター
コンテンツ・ACL・ジャーナル 埋め込み H2 共有 DB(PostgreSQL 等)。ワークスペースごとに DB
ブロブ(バイナリ) ローカルファイル 共有ストレージ(NFS 等)
全文検索インデックス ローカル ノードローカル

全ノードで一致させる必要があるファイル

次の「アイデンティティファイル」は、全ノードで同一でなければなりません(初回起動時に自動生成。2 台目以降は再生成せず、1 台目からコピー)。

  • secrets/secret-key.yml(保存秘密の暗号鍵)
  • etc/boot.id(リポジトリ識別子。マスク値の鍵導出に使用)
  • etc/idp-keystore.p12 / etc/sp-keystore.p12(SAML の鍵)
  • etc/idp.yml / etc/saml2.yml

推奨は、リポジトリディレクトリを共有ストレージに置くことです(etc/secrets/ が自動的に共有されます)。一時ディレクトリ(tmp/)は起動時に消去されるため、クラスターでは自動的に tmp/nodes/<nodeId> を使い、共有してはいけません。

ジャーナルと協調

各トランザクションはジャーナルに記録され、各ノードのポーラー(2 秒間隔)が他ノードのトランザクションを再生します。これによりキャッシュ無効化・インデックス更新・OSGi イベント(Camel ルートの再デプロイ、CMS イベント、SSE/GraphQL 購読)がクラスター対応になります。

協調用テーブルが自動作成されます。

  • jcr_cluster_nodes — ノード登録。30 秒ごとに last_heartbeat を更新
  • jcr_cluster_locks — リースロック(TTL 付き。クラッシュしても無期限にブロックしない)
  • jcr_cluster_signals — 短命な制御通知のためのシグナルバス

ワークスペース起動・ブロブ掃除・コンテンツデプロイなどの単一ノードで行うべき作業は、リースで直列化されます。

手順(概要)

  1. ワークスペースごとに PostgreSQL の DB を用意(BPM を使うなら BPM 用も)
  2. PostgreSQL JDBC ドライバのバンドルを Felix に導入
  3. リポジトリディレクトリを共有ストレージへ(最低でも blobstore.directory を全ノードで共有)
  4. 各ワークスペースの jcr.yml#datasource(必要なら bpm.yml#jdbcURL)を全ノードで同一に設定
  5. アイデンティティファイルを全ノードで共有(初回はまず 1 台だけ起動)
  6. cluster.enabled を有効化し、各ノードに一意な nodeId を付与。ノードの時計を NTP で同期
  7. ロードバランサーの背後に配置(スティッキーセッション推奨)

アプリからの協調

スクリプト API で、特定の処理をクラスター内の 1 ノードだけで実行できます。

def lease = cluster.tryLock("nightly-report", 600000)
if (lease != null) {
    try {
        // ... 1 ノードでのみ実行 ...
    } finally {
        lease.close()
    }
}

cluster.isClusterEnabled()cluster.nodeIdcluster.listMembers() も利用できます。スタンドアロンでは即座にロックが付与され、同じコードがそのまま動きます。

監視

GraphQL の cluster クエリ(管理者)や、Dashboard の Operations セクションのクラスターカードで、各ノードのハートビート(死活)を確認できます。3 間隔(約 90 秒)応答がないノードは警告としてログに記録されます。

注意点

  • 時計のずれは安定化ウィンドウ(10 秒)を壊します。NTP 同期は必須です。
  • 外部 DB・ブロブストレージは自動管理されません。ワークスペース削除後の DB/ブロブの後始末や、同名再作成前の DB クリアは手動です。
  • 検索インデックスはノードごとで複製されません(空なら自動再構築)。