ワーカーノードの分離
フォロワーが多い高トラフィックインスタンスの場合、Webサーバーとバックグラウンドワーカーを 分離することでパフォーマンスを向上させることができます。これにより、ワークロードに応じて 各コンポーネントを独立してスケールすることができます。
ワーカー分離を検討すべき場合
Section titled “ワーカー分離を検討すべき場合”次のような場合にワーカー分離が有用です:
- 数千人のフォロワーがいて、アクティビティの配信が遅い場合
- インスタンスが大量の連合トラフィック(多くの受信/送信投稿)を処理する場合
- ピーク時にWebサーバーの応答性が低下する場合
- 複数のサーバーで水平スケーリングしたい場合
動作の仕組み
Section titled “動作の仕組み”Holloは6つの主要コンポーネントで構成されています:
- Webサーバー: HTTPリクエスト(API、Web UI)を処理
- Fedifyメッセージキュー: ActivityPub inbox/outboxメッセージを処理
- インポートワーカー: バックグラウンドデータインポートジョブを処理
- クリーンアップワーカー: バックグラウンドクリーンアップジョブを処理
- リモート返信取得ワーカー: リモート返信コレクションをゆっくり取得
- 投票通知タスク: 期限後にローカルの作成者と投票者に通知
デフォルト(NODE_TYPE=all)では、6つすべてが単一プロセスで実行されます。
NODE_TYPE環境変数を使用して分離できます:
NODE_TYPE |
Webサーバー | Fedifyキュー | インポートワーカー | クリーンアップワーカー | 返信ワーカー | 投票通知 |
|---|---|---|---|---|---|---|
all (デフォルト) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
web |
✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
worker |
✗ | ✓ | ✓ | ✓ | ✓ | ✓ |
インポートとクリーンアップのタスク
Section titled “インポートとクリーンアップのタスク”インポートとクリーンアップは専用の PostgreSQL キューで Fedify タスクとして 実行し、ワーカープロセスごとに最大4項目を同時処理します。Web ノードは 登録と投入のみを行います。ワーカーと統合ノードは毎分、種類ごとに最大100件の 実行中ジョブを確認し、投入漏れや中断を復旧します。復旧処理自体は項目を実行しません。 進捗は処理開始数ではなく、最終結果の数を示します。
更新前に旧版の全 Web・ワーカーノードを停止し、マイグレーションを適用してから 全ノードを同じバージョンで起動してください。タスク定義が異なる版を混在させないでください。 既存の待機中・処理中項目は復旧します。不完全な旧アップロードは失敗となり、 再送信が必要です。既存の連合キューとメッセージは保持されます。
ワーカーは通常プール20本と項目ロック用4本、最大24本の DB 接続を使用します。 キュー分離は CPU・ストレージ負荷を分離しません。終了時は新規処理を停止し、 最大30秒待機します。強制終了した項目は再起動後に復旧します。一時的な失敗は 5秒間隔で再試行し、記録された5回の失敗で終了します。プロセス終了はこの回数に含みません。 中断後に外部アクティビティの配信が保存済み ID で繰り返される場合があります。 キャンセルは新規処理を防ぎますが、実行済みの操作を元に戻しません。 確定済みのインポート関係については、再試行中にキャンセルしても保存済みのアクティビティを送信します。
すべてのノードは同じPostgreSQLデータベースを共有し、
LISTEN/NOTIFYを使用してリアルタイムメッセージ配信のための
メッセージキューバックエンドとして機能します。
リモート返信収集タスク
Section titled “リモート返信収集タスク”リモート返信収集はインポートとクリーンアップの専用タスクキューを共有します。 ワーカーごとに最大4個のアプリケーションタスク、そのうち収集は最大2個を 同時実行します。キュー制限は即時実行できるメッセージを数えるため、遅延予約が 他の処理を妨げません。PostgreSQL のジョブとホストのレコードがワーカー間の クールダウン、要求間隔、HTTP 429 の待機時間を維持します。実行と再試行の予約は タスクで処理します。起動時と毎分の復旧処理は期限切れジョブと孤立したホストの リースを各最大100件確認し、実行可能な収集を最大50件投入します。 復旧処理自体はリモートコレクションを取得しません。トランザクション内で作成した ジョブはコミット後に復旧処理が投入します。
更新前に旧版ノードをすべて停止し、全ノードに互換性のあるタスク定義を配備して ください。既存の待機中ジョブは復旧し、処理中ジョブは15分のハートビートリースが 失効した後に回収します。終了時は待機を中断し、実行を終えた試行を待機状態に 戻します。強制終了ではリース失効を待ちます。HTTP 429 は記録した時刻に再試行し、 他の収集エラーは失敗として終了します。既存の環境変数とローカルアクターの 認証付き取得の動作は維持します。
投票通知タスク
Section titled “投票通知タスク”投票期限の通知も専用の Fedify タスクキューを使います。Web ノードは予約し、 ワーカーと統合ノードが現在の期限とローカルの受信者を再取得して実行します。 期限変更や重複メッセージで早期通知や重複集計は起きません。遠い期限は最大7日ずつ 予約します。実際の実行は記録された期限より遅れることがあります。
起動時と毎分の復旧スキャンは、通知のない期限切れ投票を最大100件ずつ予約します。 前のタスクが待機中や失敗でも走査位置を進め、既存投票、停止中の期限切れ、commit 後の 予約失敗、再試行の消尽を復旧します。スキャン自体は通知を生成しません。遅延メッセージは 即時実行キューの負荷制限に含まれません。一時的エラーは指数 backoff で最大3回試行し、 その後も復旧で再予約できます。終了時は実行中のトランザクションの完了を待ち、 中断した作業は復旧可能です。更新前に旧ノードを停止し、全ノードで互換性のある タスク定義を使ってください。
共有データベースの待機中投票メッセージは、遅延と再試行を含め最大100件です。 新しい予約は投票ごとに1件にまとめます。繰り返しの投票や期限更新を統合し、 期限が早まると既存の予約を前倒しします。期限の延長は実行時に再取得します。 上限では、早い予約が最も遅い予約を置き換えます。延期・置換された投票は期限後に 復旧します。集中した負荷で入場ロックを1秒以内に取得できない場合も延期します。 この上限は全ノードで共有し、実行中のハンドラーや通知件数ではなく保存件数を 制限します。
遅延予約はワーカーごとのタイマーではなく、キューの5秒間隔の確認を使います。 復旧は共有キューの即時実行メッセージが200件になると引き続き停止します。 100件は復旧1ページ分であり、処理能力の保証ではありません。更新前に旧版の 生成ノードをすべて停止してください。旧メッセージも上限に数えますが、既に100件を 超えるバックログは徐々に減少します。新規予約はその件数を増やしません。
hollo.poll-notifications.queue ロガーは容量による延期、予約の置換、ロックの
タイムアウトを warning レベルで記録します。理由ごと・プロセスごとに毎分最大1回
です。保存負荷は以下のSQLで確認できます。enqueue カウンターは統合・延期された
試行も含むため、保存メッセージ数ではありません。
SELECT count(*) AS queued, count(*) FILTER (WHERE created + delay <= CURRENT_TIMESTAMP) AS ready, count(*) FILTER (WHERE created + delay > CURRENT_TIMESTAMP) AS delayed, count(*) FILTER ( WHERE message->>'type' = 'task' AND message->>'taskName' = 'hollo.poll-notification.v1' ) AS pending_pollsFROM hollo_task_message_v1;Docker Compose設定
Section titled “Docker Compose設定”以下は、別々のWebとワーカーノードを実行するためのcompose.yamlの例です:
services: db: image: postgres:17-alpine restart: unless-stopped environment: POSTGRES_USER: hollo POSTGRES_PASSWORD: password POSTGRES_DB: hollo volumes: - ./data/postgres:/var/lib/postgresql/data
web: image: ghcr.io/dahlia/hollo:latest restart: unless-stopped depends_on: - db ports: - "3000:3000" environment: - NODE_TYPE=web - DATABASE_URL=postgresql://hollo:password@db/hollo - SECRET_KEY=${SECRET_KEY} - DRIVE_DISK=fs - FS_STORAGE_PATH=/data/storage - STORAGE_URL_BASE=https://hollo.example.com/assets - BEHIND_PROXY=true volumes: - ./data/storage:/data/storage
worker: image: ghcr.io/dahlia/hollo:latest restart: unless-stopped depends_on: - db environment: - NODE_TYPE=worker - DATABASE_URL=postgresql://hollo:password@db/hollo - SECRET_KEY=${SECRET_KEY} - DRIVE_DISK=fs - FS_STORAGE_PATH=/data/storage - STORAGE_URL_BASE=https://hollo.example.com/assets volumes: - ./data/storage:/data/storageワーカーのスケーリング
Section titled “ワーカーのスケーリング”複数のワーカーノードを実行するには、ワーカーサービスを追加します:
services: # ... dbとwebサービス ...
worker-1: image: ghcr.io/dahlia/hollo:latest restart: unless-stopped depends_on: - db environment: - NODE_TYPE=worker # ... その他の環境変数 ...
worker-2: image: ghcr.io/dahlia/hollo:latest restart: unless-stopped depends_on: - db environment: - NODE_TYPE=worker # ... その他の環境変数 ...PostgreSQLのLISTEN/NOTIFYにより、各メッセージが1つのワーカーでのみ
処理されることが保証されます。
手動インストール
Section titled “手動インストール”手動インストールの場合、異なるNODE_TYPE値を使用して別々のプロセスを実行できます。
Webノード
Section titled “Webノード”NODE_TYPE=web pnpm prod設定されたポートでWebサーバーのみを起動します。
ワーカーノード
Section titled “ワーカーノード”NODE_TYPE=worker pnpm prod# またはpnpm workerWebサーバーなしで、Fedifyメッセージキューとインポートワーカーのみを起動します。
Systemdの例
Section titled “Systemdの例”systemdを使用している場合、別々のサービスファイルを作成します:
/etc/systemd/system/hollo-web.service
Section titled “/etc/systemd/system/hollo-web.service”[Unit]Description=Hollo Web ServerAfter=network.target postgresql.service
[Service]Type=simpleUser=holloWorkingDirectory=/opt/holloEnvironment="NODE_TYPE=web"EnvironmentFile=/opt/hollo/.envExecStart=/usr/bin/pnpm prodRestart=on-failure
[Install]WantedBy=multi-user.target/etc/systemd/system/hollo-worker.service
Section titled “/etc/systemd/system/hollo-worker.service”[Unit]Description=Hollo WorkerAfter=network.target postgresql.service
[Service]Type=simpleUser=holloWorkingDirectory=/opt/holloEnvironment="NODE_TYPE=worker"EnvironmentFile=/opt/hollo/.envExecStart=/usr/bin/pnpm workerRestart=on-failure
[Install]WantedBy=multi-user.target次に、両方のサービスを有効化して起動します:
sudo systemctl enable hollo-web hollo-workersudo systemctl start hollo-web hollo-workerモニタリング
Section titled “モニタリング”ワーカーステータスの確認
Section titled “ワーカーステータスの確認”Docker Composeの場合:
# Webノードのログを表示docker compose logs -f web
# ワーカーノードのログを表示docker compose logs -f workersystemdの場合:
# Webノードのログを表示sudo journalctl -u hollo-web -f
# ワーカーノードのログを表示sudo journalctl -u hollo-worker -fワーカーログメッセージ
Section titled “ワーカーログメッセージ”ワーカーノードが起動すると、次のように表示されます:
Worker started (Fedify activity/task queues + Job recovery)ワーカーが正常に機能していることを確認するには、 アクティビティ処理、インポート、クリーンアップ、リモート返信取得に関する メッセージを確認してください。
トラブルシューティング
Section titled “トラブルシューティング”Webノードは動作するがアクティビティが処理されない
Section titled “Webノードは動作するがアクティビティが処理されない”問題: Web UIにアクセスできるが、受信アクティビティ(フォロー、いいね、投稿)が 処理されない。
解決: NODE_TYPE=workerで少なくとも1つのワーカーノードが実行されているか確認してください。
ワーカーノードが起動しない
Section titled “ワーカーノードが起動しない”問題: ワーカーノードがエラーで終了する。
解決: 次を確認してください:
DATABASE_URLが正しく、データベースにアクセス可能か- データベースに最新のマイグレーションが適用されているか
- ストレージ設定(
DRIVE_DISK、FS_STORAGE_PATHなど)が正しいか
アクティビティ処理が遅い
Section titled “アクティビティ処理が遅い”問題: 連合アクティビティの処理に遅延がある。
解決: メッセージを並列処理するためにワーカーノードを追加してください。 PostgreSQLのメッセージキューが、利用可能なすべてのワーカーに作業を分散します。
ストレージアクセスエラー
Section titled “ストレージアクセスエラー”問題: ワーカーノードがアップロードされたファイルにアクセスできない。
解決: 次を確認してください:
- すべてのノード(Webとワーカー)が同じストレージにアクセスできるか
- ファイルシステムストレージの場合: ストレージボリュームがすべてのノードにマウントされているか
- S3ストレージの場合: すべてのノードが同じS3認証情報を持っているか
パフォーマンスの考慮事項
Section titled “パフォーマンスの考慮事項”- Webノード: 軽量で最小限のリソースで実行可能(512MB-1GB RAM)
- ワーカーノード: 特に高い連合活動中にリソース集約的(1GB-2GB RAM推奨)
- データベース: すべてのノードで共有; 十分なリソースを確保 (ビジーなインスタンスには2GB+ RAM推奨)
- 並行性: 各ワーカーは別々のキューで最大10件の ActivityPub メッセージと最大4件のアプリケーションタスクを同時に処理します。 リモート返信収集は、この4枠のうち最大2枠を使用します。制限は ワーカープロセスごとに適用され、ワーカーを追加すると全体の並行数が増えます。
ベストプラクティス
Section titled “ベストプラクティス”- パフォーマンス問題が発生するまで
NODE_TYPE=all(デフォルト)で開始してください - リソース使用状況をモニタリングして、分離が必要な時期を判断してください
NODE_TYPE=web使用時は、少なくとも1つの専用ワーカーノードを実行してください- Webノードの前にリバースプロキシ(nginx、Caddy)を使用してロードバランシングしてください
- すべてのノードからストレージにアクセスできるようにしてください(共有ボリュームまたはS3)
- エラーとパフォーマンス問題のために、すべてのノードのログをモニタリングしてください