7.9 KiB
Counts エクスポートワーカー
Counts の画面からのエクスポートは永続ファイルキューへ登録する。 Active Job の既定のプロセス内 async アダプターは使用しない。 追加 gem、外部キュー、DB マイグレーションは不要。
起動・配置
backend で bundle exec rake counts:export_worker を、systemd 等の監視対象として
別プロセスで起動する。Web プロセス内では起動しない。
1監視プロセスは同時に1ジョブを専用の子プロセスで生成し、全体の実行枠は
既存の2枠を共有する。fork 前に DB 接続を閉じ、子が独立した接続を取得する。
Web 内のスレッドや Active Job async では実行しない。Linux / POSIX の fork が必要。
監視側は SQL 中も1秒間隔でジョブとワーカーの heartbeat を更新する。
docs/systemd/btrc-counts-exports.service を配備用 unit として追加している。
Restart=always、KillMode=control-group、停止猶予150秒を指定している。
User、WorkingDirectory、bundle が見つかる PATH を実際の配備先へ合わせる。
/etc/btrc-hub/counts-exports.env に Web と同じ Rails secret・接続設定・
COUNTS_EXPORT_DIRECTORY を設定し、同じ Unix ユーザーで読み書きできるようにする。
OS 起動時の有効化・サービス起動は運用側で行う必要がある。
今回 unit のインストール、有効化、本番での起動・再起動は行っていない。
Web とワーカーは同一リリース、Rails secret、アプリケーション環境、DB 接続先を
使用する。DB コネクションはワーカープロセスごとに必要。
COUNTS_EXPORT_DIRECTORY を同じ永続 POSIX 共有ディレクトリに設定する。
省略時は backend/tmp/counts_exports。
全プロセスで同一の所有者とアクセス権を使用する。
複数ホストでは、flock・同一ファイルシステム内の atomic rename・fsync が正しく
機能する共有ボリュームが必須。オブジェクトストレージや各コンテナ独立の tmp は不可。
ジョブ JSON と完成ファイルが格納されるため、このディレクトリを静的公開しない。
API と進捗
- POST
/counts/export/jobs:要求検証・実行時点の固定・署名付きジョブトークンの発行。 - GET
/counts/export/jobs/:token:状態、実際の処理済みレコード数、判明している総数。 - DELETE
/counts/export/jobs/:token:取消を永続記録。 - GET
/counts/export/files/:token:生成完了後の署名付きファイルトークンによる取得。
ジョブ・ファイルのトークンは作成元のセッションに紐付く。 既存の BAN・CSRF 制御を使用する。フロントは約800ms間隔で状態を取得する。 明細の総数は同じ読み取りトランザクション内の SQL 件数を使用する。 queued と running に加え、running 内の phase で aggregating(集計中)と writing(出力中)を区別する。最初の SQL より前に aggregating を記録する。 heartbeat_at は監視側の生存時刻、updated_at は生成処理が状態を書いた時刻であり、 処理件数と分離する。SQL 中に件数は増やさない。 集計の総出力数は生成終了まで未確定とし、不確定進捗として扱う。 processed は CSV では実際に生成したデータ行数、JSON のクロス集計では 実際に生成した pivot_cell 数。ヘッダーは含めず、小計・合計の物理レコードは含む。 通常の集計行だけを数える logical_count_unit=regular_result_rows と区別する。 出力中は約250ms間隔と最終レコードで実測件数を保存する。 完成状態はファイルの生成完了であり、ブラウザの保存完了を意味しない。
取消・障害・制限
通常は約250ms間隔の生成チェックポイントで取消を検知する。 監視側も取消を確認し、子へ TERM、5秒後も終了していなければ KILL を送る。 子の回収後に一時ファイルを削除する。DB 側の実行停止は切断検知等に依存するため、 取消 API の受理と DB の即時停止は同義ではない。 取消済みへの遷移は生成処理の終了と一時ファイル削除後に行う。 既に始まった HTTP 転送やブラウザ側の保存は取り消せない。
ワーカーが異常終了したジョブは、次のワーカーが実行ロックを取得した際に失敗状態にし、 そのジョブの残存生成ファイルを削除する。別の読み取り状態で自動再実行しない。 原子的に書き換える状態ファイルとプロセス間ロックにより、二重処理・取消との競合を防ぐ。
既存の同時実行2、1ファイル128MiB、保存領域512MiB、生成120秒、期限10分を維持する。 生成120秒は監視側でも強制する。ワーカー未起動なら登録 API は503を返す。 待機中のワーカー消失、15秒以上の heartbeat 停止、150秒以上の待機は失敗とする。 ネットワーク切断時も UI が無期限に処理中にならないよう、状態取得は1回15秒で中断し、 30秒以上状態を取得できなければ失敗表示へ移る。処理件数を捏造しない。 画面からの通信断だけではサーバーの取消完了を保証しない。サーバー側の期限は独立して働く。 期限切れのジョブ・ファイル・残存状態書込ファイルは登録時またはワーカーの巡回時に削除する。 ワーカーと Web の両方を停止した間は巡回削除も停止する。
これらの処理は静的確認のみで、実環境のキュー処理・取消・転送・共有ボリュームでの 排他動作は未確認。
Counts の件数と性能
通常の Counts 応答では、使用していない全履歴の population_count は計算しない。 キーは null、population_count_computed は false とし、0に置き換えない。 現在条件の output_count は権限適用前、visible_output_count は権限適用後の 通常の集計行数(明細では明細行数)。小計・合計の物理行は含めない。 CSV の物理行と JSON の pivot_cell には集計レコードを含むため、両者を混同しない。
除票境界・参照禁止集合は GROUP BY で識別キー単位にまとめる。 投稿の参照判定は、全除票履歴を JSON_CONTAINS / FIND_IN_SET で反復照合せず、 保存済みスナップショットから参照IDを JSON_TABLE で展開して照合する。 権限条件付きの母集団件数は同一 export インスタンス内で再利用する。 多段小計は、認可済み母集団の CTE を MySQL の無制限 LIMIT で実体化させ、 各枝が同じ認可判定をやり直すことを避ける。計数は COUNT(DISTINCT v.id) のまま。
スキーマ上、以下の追加インデックスが候補となる。migration は作成・実行していない。
- post_versions: (event_type, post_id, version_no)
- tag_versions / nico_tag_versions: (event_type, tag_id, version_no)
- tag_description_versions: (event_type, tag_id, language_code, version_no)
- tag_group_versions: (event_type, tag_group_id, version_no)
- material_versions: (event_type, material_id, version_no)
- 各履歴テーブル: (event_type, id)。ファイル認可の policy revision 用 MAX(id) の候補。
- post_versions 等で未整備の (created_at)。履歴の異動期間を絞る場合の候補。
- post_versions: (post_id, created_at, version_no)。日時指定の最新版探索の候補。
追加は許可後の新規 add_index migration として行い、既存 migration は変更しない。 実DBでのインデックス容量・作成時ロック・実行計画・時間は未確認。 索引追加なしでの最大データ量に対する応答時間は保証しない。