165 行
12 KiB
Markdown
165 行
12 KiB
Markdown
# 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`:取消を永続記録。
|
|
- POST `/counts/export/jobs/:token/download`:セッション所有者の開始確認要求を登録。
|
|
- GET `/counts/export/files/:token?job=...&attempt=...`:実 GET の認可・送出開始の確認。
|
|
- 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に置き換えない。
|
|
summary は非公開履歴を含む同じ母集団を集計する。非公開の軸はサーバーの CASE 式で
|
|
単一の予約値へまとめてから GROUP BY し、output_count / visible_output_count はその
|
|
安全化した通常行数を表す。excluded_count は返さない。details は従来どおり
|
|
output_count が権限適用前、visible_output_count が適用後の明細行数である。
|
|
小計・合計の物理行は行数に含めない。
|
|
CSV の物理行と JSON の pivot_cell には集計レコードを含むため、両者を混同しない。
|
|
|
|
除票境界・参照禁止集合は GROUP BY で識別キー単位にまとめる。
|
|
投稿の参照判定は、全除票履歴を JSON_CONTAINS / FIND_IN_SET で反復照合せず、
|
|
保存済みスナップショットから参照IDを JSON_TABLE で展開して照合する。
|
|
権限条件付きの母集団件数は同一 export インスタンス内で再利用する。
|
|
多段小計は、全軸の JOIN へ実際に寄与するレコードを CTE に保持し、各枝で同じ
|
|
母集合を使う。changed_field には直前版がある実変更だけが寄与する。タグが行軸に
|
|
ない場合は DISTINCT 投稿版を数える。タグが行軸にある場合は投稿版×タグを数え、
|
|
全行軸の葉単位から小計・合計を求める。変更項目も行軸ならその寄与も保持する。
|
|
private のタグ・変更項目をまとめても、内部の寄与は捨てず、ラベルだけを保護する。
|
|
|
|
スキーマ上、以下の追加インデックスが候補となる。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でのインデックス容量・作成時ロック・実行計画・時間は未確認。
|
|
索引追加なしでの最大データ量に対する応答時間は保証しない。
|
|
|
|
|
|
## GET 応答開始の確認(静的実装・未実測)
|
|
|
|
進捗画面は ready 後に owner/job に紐づく download_attempt を登録し、同じ attempt を
|
|
native iframe の実 GET に渡す。サーバーは署名・所有者・権限 revision・期限・ファイルを
|
|
再検証し、64KiB の最初のチャンクを Rack 出力側へ渡した後に started を記録する。
|
|
HEAD や受付 POST では started にしない。確認には30秒の独立した期限がある。
|
|
サーバーの出力開始はブラウザー受信・保存完了を保証しない。途中の切断もあり得る。
|
|
開始確認前の失敗はダイアログに残る。再実行はダイアログを閉じてエクスポートをやり直す。
|
|
|
|
Web と worker は同じ COUNTS_EXPORT_DIRECTORY、Rails secret、DB、実行ユーザーで
|
|
共有 POSIX filesystem を使うこと。別ホストではローカル tmp を共有できない。
|
|
systemd の EnvironmentFile と Web の環境を照合し、directory の owner と0700相当の
|
|
アクセス制限、容量、期限切れ削除、worker heartbeat と restart を公開前に確認する。
|
|
この作業では systemctl・worker 起動・本番環境への適用を行っていない。
|
|
|
|
これらのコード対策と回帰テストを追加しているが、テスト・型チェック・build は未実行。
|
|
middleware/proxy 経由の転送は別途実環境で確認する。
|
|
|
|
|
|
## 開始確認の競合と権限
|
|
|
|
開始受付は pending、実 GET の単独取得は streaming、最初の Rack yield 後は started。
|
|
同じ job の重複受付は既存 attempt を返す。streaming への遷移はプロセス間ロックで
|
|
単独取得し、同一 attempt の二重 GET を拒否する。開始前の attempt は45秒で失効する。
|
|
Frontend は30秒の独立タイマーで確認を打ち切る。一時的なポーリング通信断は同じ
|
|
attempt で再取得するが、別の GET は始めない。取消失敗後の再試行は利用者の再操作で行う。
|
|
|
|
ファイル・ジョブは owner、署名、期限、現在の権限 revision 及び artifact_policy を
|
|
検証する。最初の送出前にも現ユーザー・BAN・ファイル権限を再確認する。旧ポリシーの
|
|
生成物は再利用しない。送出中は各チャンクで取消状態を確認し、例外時はログと failed
|
|
状態を記録して body を閉じる。コミット後に head で別レスポンスを書かない。
|
|
|
|
ResponseBody に to_path を提供せず、sendfile middleware による ACK の迂回を避ける。
|
|
Last-Modified と X-Accel-Buffering: no を設定するが、プロキシの動作まで保証しない。
|
|
サーバーの最初の Rack yield は、クライアントの受信開始の厳密な証明ではない。
|
|
|
|
非管理者の summary では、公開集計属性は操作日時・異動区分。その他の属性に対する
|
|
抽出条件・補助タグ検索は拒否する。measures / column_order はマスク後の式だけと
|
|
比較するため、推測した private ID は一致しない。予約値そのものの条件指定は拒否する。
|
|
private ID の正誤に応じて許可可否を変えないため、条件の一致探索を避けられる。
|
|
非公開列を含む列順変更は API の column_values_editable でも不可と通知する。
|
|
公開列の値を使うソートは引き続き利用できる。
|
|
明細には元の認可を適用し、抽出条件自体や取得済みの全言語値を表示設定で改変しない。
|
|
外部タグ履歴の linked_tags は名前しか記録していないため、非公開タグの記録名を含む
|
|
スナップショットを保守的に非公開とする。名前全体を空白区切りの境界で照合する。
|
|
区切りが曖昧な旧データでは過剰な秘匿の可能性はあるが、参照 ID がない旧形式から
|
|
名前の公開可否を楽観的に推定しない。
|