障害時の診断・バックアップ・復旧
2026-10-15、af8ba3d と開発作業ツリーを確認した手順。対象の開発環境は ASUS-ROG-STRIX の Z:\mts-service。本番への接続、停止、バックアップ取得、 復元、DB昇格、Discord送信は今回実施していない。以下の運用操作は未検証の手順・テンプレートであり、 担当者が対象・権限・バックアップを確認した上で実施する。開発環境での結果は 回帰テストの記録 を参照。
既存の詳細資料はリポジトリ内の operations/MAINTENANCE_DEPLOYMENT.md と operations/MAINTENANCE_DATABASE_RUNBOOK.md。DB切り替えの条件はそちらを優先する。 本文中のソースパスはリポジトリルートを基準とする。
1. 影響を止め、状況を記録する
- 発生時刻・タイムゾーン、影響する機能、直前のリリース/設定変更、HTTPステータス、 秘密を含まないエラー分類、操作IDを記録する。設定・残高の再更新や翻訳の連打を止める。
- 配備済みのモードと起動管理者を確認する。管理モードのSupervisorと旧Bot/APIを同時に起動しない。 PM2/Windowsサービス/タスクなど、実際の起動元と自動再起動設定を特定する。
- 管理モードで制御可能なら、既存の保護されたプロファイルで
statusを読む。 応答がないだけで担当が停止したと判断しない。新たなserveやactivateを診断代わりに起動しない。 - 正常な引き継ぎが必要な場合だけ、担当者が
drainを要求する。 受理応答に加え、statusの操作完了とBot/API停止を確認する。 ドレイン失敗・結果不明なら切り替えを進めず、旧担当の実体と未完了処理を照合する。
ルートで使用するPowerShellの例。$profilePath は運営者が選んだリポジトリ外の保護ファイルに置き換える。 今回実行を確認したのは、接続しない2つのhelpだけ。
$node = Join-Path (Get-Location) '.tools\node-v24.21.0-win-x64\node.exe'
& $node new-sharded/scripts/maintenance.js help
& $node new-sharded/scripts/maintenance-db.js --help
# 以下は運用時のみ。適切なプロファイルを確認してから実行する。
$profilePath = 'REPLACE_WITH_APPROVED_ABSOLUTE_PROFILE_PATH'
& $node "--env-file=$profilePath" new-sharded/scripts/maintenance.js status
# 新規の操作UUIDを記録し、通信結果不明の再試行には同じUUID・同じ要求を使う。
$operationId = 'REPLACE_WITH_RECORDED_OPERATION_UUID'
& $node "--env-file=$profilePath" new-sharded/scripts/maintenance.js drain --operation-id $operationId --timeout-ms 120000
& $node "--env-file=$profilePath" new-sharded/scripts/maintenance.js status.env、接続URL、トークン、pm2 env、scripts/debug-env.js の出力をチケットへ貼らない。 LocalLLMのJSON解析エラーには本文の一部が入ることを今回再現したため、ログ原本も機密として保管し、共有用は編集する。
2. 原因を切り分ける
| 症状 | 現行コードから確認する点 | 次の判断 |
|---|---|---|
| LocalLLMの待ち/タイムアウト | ollamaClient.js / lmstudioClient.js の待機数、同時数、タイムアウト設定、経路・モデル変更 | LOCALLLM_QUEUE_TIMEOUT と LOCALLLM_REQUEST_TIMEOUT を区別。無制限な再試行や待機枠拡張を避ける |
| JSON解析失敗/一部言語不足 | localLlmResponse.js / localLlmRepair.js、JSON_PARSE_FAILED、localllm-missing-language | Ollamaは不足言語などを1回だけ修復する。壊れたJSONは通常ハードエラーとして扱い、成功扱いにしない |
| 編集翻訳が更新されない | 元メッセージIDと既存relayの対応、翻訳失敗か配信失敗か | 翻訳失敗時は旧訳を保持する。編集先不明で新規投稿して埋め合わせない |
| Whisperが止まる/枠が減る | voiceTranscription.js の session_stopped、transcribe_failed、whisper_allowance_exhausted と精算結果 | 無音/認識失敗は非課金。成功した音声認識は後段の翻訳・配信が失敗しても秒数を記録する |
| JSON保存失敗 | src/utils/fs.js のrename失敗、容量・権限・共有ロック | 元ファイルと一時ファイルを保全。原本削除や空の設定作成で回避しない |
| SQLite移行エラー | migrate-json-to-sqlite.js の終了コード、対象キー、保存先 | awaitで書き込み失敗を検出するが全キー一括のトランザクションではない。途中状態を確認する |
| 設定APIが403/保存失敗 | ログイン主体・管理者権限、拒否されたフィールド、HTTP応答 | ユーザー設定と管理設定が混在する要求は拒否される。権限を迂回して復旧しない |
| 担当取得/DB準備が拒否される | Supervisorの状態、役割、操作ID、cluster identity、保存形式、未完了ジャーナル | 拒否条件を解消してから進める。lease/ジャーナル/lockファイルの削除で強制奪取しない |
Ollamaでは待機・初回応答・修復に同じ期限を引き継ぐ。LM Studioの現行サービス経路は 呼び出しごとのタイムアウトであり、未翻訳検出時の再呼び出しまで含む総時間保証とは区別する。 WhisperとLocalLLMの一部予約はプロセス内のMapなので、再起動で引き継がれない。 DBの永続予約・台帳・管理ジョブと混同しない。
3. 復旧前のバックアップ
まず、実際の STORE_BACKEND と GUILD_STORE_MODE / USER_STORE_MODE / TRANSLATION_DAILY_MODE / 残高・relayの保存モードを、承認された配備設定から確認する。 既定のデータストアはPG。rows形式を使用中に古いJSONや kv_store だけを戻しても全体は復旧しない。
| 対象 | 保全するもの | 注意 |
|---|---|---|
| PostgreSQL | 対象DBのスキーマと全データ、必要なロール/権限・拡張の情報 | 設定だけでなく購入・残高・台帳・予約・Stripe受信箱・翻訳ジョブ・relay・利用履歴を同じ整合したバックアップに含める |
| SQLite | SQLiteの整合したバックアップ、保存形式とNode/native-module版 | 稼働中のDB本体だけをコピーしない。SQLite対応バックアップAPI、または全writerを正常停止してからDBと残存WAL/SHM一式を保全する |
| JSON | 全writer停止後の対象DATA_DIR、関連ログ、更新前ファイル・残存tmp | JSONの構文確認はコピー上で行う。不明なtmpを正本へ昇格しない |
| 管理状態/配備 | DATA_DIR/maintenance-state.json とlock等の証跡、Git版・未コミット差分の保管先、プロファイルの安全な保管先 | ジャーナルは未完了操作の証跡。コピーしたlockを別ノードの所有権として使わない。秘密値は一般の成果物に含めない |
PGの論理バックアップ例。libpqサービスと保護されたpassfileを運営者が事前設定し、 pg_dump の版・対象・空き容量・出力先ACLを確認する。このコマンドは今回未実行。
$pgBin = 'REPLACE_WITH_APPROVED_PG_BIN_DIRECTORY'
$backupFile = 'REPLACE_WITH_NEW_ABSOLUTE_BACKUP_FILENAME'
& (Join-Path $pgBin 'pg_dump.exe') --no-password --format=custom --dbname='service=REPLACE_WITH_APPROVED_SOURCE_SERVICE' --file=$backupFile
if ($LASTEXITCODE -ne 0) { throw 'Backup failed; stop recovery.' }
& (Join-Path $pgBin 'pg_restore.exe') --list $backupFile
if ($LASTEXITCODE -ne 0) { throw 'Backup inventory could not be read.' }
Get-FileHash -LiteralPath $backupFile -Algorithm SHA256目録確認とハッシュは復元可能性の証明ではない。バックアップ日時・対象・ハッシュ・取得結果を記録し、 元データと別の保護された場所に保持する。レプリカには誤更新も伝播するため、独立したバックアップが必要。
4. 原因に応じて復旧する(運用操作は未実施)
設定/翻訳エンジンだけの問題
最後に正常だった確認済みの設定・コードとの差をレビューする。作業ツリーに多数の変更があるため、 git reset --hard、git clean、丸ごとの上書きは使用しない。LLMのみの退避なら既存の 臨時エンドポイントとdrain/handoffの手順を利用できるが、実行中HTTPやVC途中音声は移送されない。 代替経路がないとmainが最後の候補として残り得るため、設定変更だけで旧LLMが完全停止したと考えない。
JSONの保存失敗時は、原本が残っていても JsonStore.update のキャッシュだけが変化する問題がある。 writerを止めて原本と証跡を保全し、コピーを検証した上で管理された再起動による再読込を行う。 失敗した要求をそのまま繰り返し、メモリ上の未保存値を正本として後続処理へ渡さない。
データの破損/欠損
- 現在の状態も保全する。以後の購入や返金がある場合、古いスナップショットへの巻き戻しは新しい取引を失う。 DBA/運営者が復旧時点とWAL等による回復可否を決定する。
- 本番・開発DBと分離した新しい復旧先に復元し、アプリの自動起動と外部送信を無効にしたまま検証する。 次のテンプレートのserviceは、既存本番を指さない専用の空DBへ割り当てる。
- 全テーブルの件数、ID・version、残高と台帳、購入・返金・権利、予約・未完了ジョブ・relayの対応を照合する。 集計残高が一致するだけでは十分ではない。SQLiteは復旧コピーへ
PRAGMA integrity_checkを実施する。 - 整合性を確認後、全writerの停止と旧担当の再起動抑止を維持し、対象プロファイル/接続先だけを計画的に切り替える。 接続プールは起動時に作られるため、ファイル変更だけで切り替え済みと判断しない。
# 未実行。上のバックアップと、新設・空・隔離済みの復旧DBだけを指定する。
& (Join-Path $pgBin 'pg_restore.exe') --no-password --single-transaction --exit-on-error --dbname='service=REPLACE_WITH_ISOLATED_EMPTY_RECOVERY_SERVICE' $backupFile
if ($LASTEXITCODE -ne 0) { throw 'Restore failed; keep the recovery database isolated.' }migrate-json-to-sqlite.js、migrate-to-pg.js、migrate-old-to-pg.js は保存形式の移行ツールであり、 現行DB全体の復元ツールではない。特に前者の --force、後者の --overwrite、 migrate-to-pg.js の既存キー更新はデータを上書きする。通常実行や「修復目的」で使わない。
ホスト/DBを切り替える場合
maintenance-db.js はinspect/計画専用で、--apply は常に拒否する。 plan-basebackup / plan-switchover / plan-rejoin の出力は未実行であり、exit 0も切り替え成功を示さない。 独立した物理フェンス、正しいcluster identity/PG版、最終WALの再生を確認して、既存DB手順に従う。 rehearse-maintenance-db.js --apply は合成クラスタを実際に作る演習であり、診断用に実行しない。 新正本へ書き込み始めた後、旧正本をそのまま起動して戻さない。旧側を新正本から再同期する。
5. 復旧後の確認と再開
- DB正本・接続先・保存モード・コード版を確認する。管理モードは
statusの操作完了と各roleのreadyまで確認する。activateの受理やHTTP 200だけで復旧完了としない。異常終了したleaseの強制奪取は行わない。 - 隔離環境で設定参照と権限拒否を確認する。承認された試験用ユーザー/チャンネルでだけ、 通常翻訳・複数言語・編集・失敗時の旧訳保持を確認してから段階的に再開する。今回実Discord確認は未実施。
- 成功/失敗件数、Whisper秒数、残高・台帳・予約、ジョブ/配信記録を照合する。 課金や送信の結果不明は保持して手動照合し、再生成・再課金・再送で埋め合わせない。
- VCは再接続を案内し、途中音声を復元できたとは扱わない。待機数・エラー率・容量・未完了操作を監視する。
- 開始/終了時刻、復旧に使った版/バックアップ、操作ID、確認結果、未解決事項と担当を記録する。 今回のオフラインテストだけで本番の復元可能性やRTO/RPOを保証しない。
