eurekapu-nuxt4のデプロイ復旧と、画像・音声をR2配信に寄せ切った一日
eurekapu-nuxt4のデプロイ復旧と、画像・音声をR2配信に寄せ切った一日
朝一、「残しなかったでしたっけ」と自分に聞き返すところから始まった。昨日の英単語(gogen-eitango)作業、どこまで進めて、どこからコミットしていないのか。記憶があいまいなまま画面を開くのは怖いので、まずディスク上の状態から確認することにした。7/19までの進捗と、未コミットの変更を洗い出す。作業自体は残っている。コミットだけが追いついていない。積み残しの正体はそれだった。
途中で一度、確認作業そのものが通信エラーで切れた。「続けて」とだけ返してやり直させる。ローカルのgrepパターンが実際のJSON構造と噛み合っていなかったのが原因で、構造を見直してからようやく「昨日どこまで進んだか」が言葉になった。答えが出るまでに、確認のための確認が一段挟まっていた形だ。
検証→デプロイ→コミットを、順番どおりに流す
「検証してデプロイしてコミット。自然な流れで」と決めて、そのとおりに進めることにした。飛ばしたくなる気持ちを抑えて、まずは足元から。
- データ整合の全数チェック
_redirectsのR2リダイレクト定義の確認- テスト実行
- 画像6枚を自分の目で見て確認
ここまでは順調だった。ビルドも通り、デプロイもNitroサーバービルドの段階まで進んだ。あとは完了通知を待つだけ、のはずだった。
wranglerが認証切れで撥ねる
デプロイがexit 1で落ちた。ログを見ると、ビルド自体は最後まで通っている。落ちていたのは投入コマンドの方で、原因はwranglerの認証切れだった。
ここで最初の詰まりが起きる。ビルドをやり直す必要はない。再ログインさえすれば、できあがったdist/をそのまま投入できるはずだった。pnpm exec wrangler loginを実行してOAuthでログインし直す。
ログイン成功を確認してから、ビルド済みの成果物をそのまま流し込んだ。孤児ファイルのdev/やpublic/images/vocabulary/は除外して、コミット用のステージングも並行で進める。アップロードが終わるのを待つ間に手を止めずに動けたのは、ビルドが無傷で残っていたからだった。
本番確認は、Chrome拡張が繋がらずagent-browserへ
デプロイ完了後、本番のJSONを見ると画像・音声パスが50/50で反映されていた。数字としては合っている。ただ、数字が合っているだけでは実際の画面がどう見えるかはわからない。ブラウザで実物を確認しようとしたところ、Chrome拡張が接続しない。代わりにagent-browserスキルに切り替えて、実際の描画を見にいった。ツールが一つ使えなくても、別の経路で最後まで自分の目で確認する。
クイズ5問で受領証をもらう
コミットの前に学習ゲートを通す。ステージした変更について直感優先の解説を作ってもらい、ブラウザに提示されたクイズに自分で答える番になった。出題は5問。Workerのマニフェストをなぜ退避させたか、認証エラーをfail扱いにしない理由、Piperバッチが速くなった主因、?w=ディープリンクのフォールバック挙動、そして画像338MBをgit管理外に出した根拠。手を動かした本人でも、理由を言葉にして問われると一瞬止まる問いだった。5問とも答えて、全問正解。受領証が発行され、コミットが通った。
「ローカルにすら置きたくない」という新しい注文
ここで話が変わる。「.gitignoreで除外してコミットしないのは当然として、R2にデプロイしているんだから、ローカルにすら実体を持たせたくない。dev環境で試すときも、CloudflareのURLで聞けるようにしてほしい」。
もっともな要望だと思った反面、即答はできない。まずdev配信が今どう実装されているか、ローカルに実体がどれだけ溜まっているかを確認することにした。
調べてみると、音声の遅延キャッシュ(audio-assets/)はたった304KBで、gogen分の重複蓄積は起きていない。設計どおり「devで再生した分だけ」が残る状態だった。ここは問題なし。問題は別のところにあった。アトラス画像がgitignore済みのまま338MB分、ローカルに残っていた。
裏でR2の全数チェック(11,859件)を走らせつつ、削除の準備を進める。途中でチェックが2回止まった。1回目は自分の操作で、2回目は外部要因で。そのたびに結果ファイルをのぞいて、どこまで書けているかを確認しながら進めた。
削除する前に、middlewareのフォールバック動作だけは確実に確認しておきたかった。欠落ファイルにアクセスすると302でhttps://assets.info-accounting.com/...へリダイレクトされる。この一点さえ動いていれば、ローカルの実体を消しても再生は途切れない。検証はパスした。
削除対象は、gitignore済みのアトラス画像webp 3,859枚(約338MB)と音声118MB。破壊的な操作なので、実行前に「この2コマンドでいいか」と確認を求められ、「y」と答えてから消させた。消したあとdevを開いて、実際にR2フォールバック経由で画像と音声が出ることを確認する。ここでも短いクイズ(3問)が出て、全問正解してから受領証を発行、コミットまで進めた。
R2の全数監査も最終的に11,859件すべて200で終わった。初回にエラーだった53件も、再試行では全部通っている。欠落ゼロ。この日の当初のタスクはここで一区切りついた。
SSD移設で、一度作り直すことになった
区切りがついたと思った直後に、もう一つ注文が来た。「原本の重いデータ、Cドライブじゃなくて自分のSSD(Fドライブ)に移せないか」。容量とファイルシステム、gogen-assetsの内訳、スクリプトからのパス参照を確認してから、コピーを実行する。並行でリポジトリ内のパス参照も洗い出す。
移設とコミットが終わり、Cドライブから約9.5GBが空いた。数字だけ見れば成功だった。
ところが、あとから引っかかりを覚えた。「画像バッチと音声再生成を走らせるとき、保存先自体をSSDにしたということ? あー、なるほど、相対パスでやってて、ファイルごと入れてるからってことっすかね」と聞かれて、自分の実装を振り返る。答えはそのとおりだった。piper本体と生成時の保存先まで一緒にF:へ寄せてしまっていたので、バッチを回すたびにSSDの接続が前提になる構成になっていた。相対パスでファイルごと移していたのが原因だ。指摘されるまで、自分でもそこまで踏み込んで移していた自覚が薄かった。
これは設計として良くなかった。SSDは完成品のアーカイブ置き場であって、日々の作業がそこに依存する場所ではないはずだった。
作り直す方針はシンプルだった。SSD=完成品のバックアップ専用置き場、実装(コード・スクリプト・一時ステージング)はリポジトリとCドライブだけで完結させる。piperも生成スクリプトの保存先も元に戻し、完成後のアーカイブだけをF:に送る形に組み替えた。「これで切り離せますよね、実装とデータの保管場所と」と確認され、そのとおりだと答えられる状態になった。
学び
- 「ビルドは通っているのにデプロイだけ落ちる」ときは、まず認証を疑う。ビルドが無事なら再ビルドせずに再投入できる
- ローカルの実体を消す前に、フォールバック経路(middlewareの302リダイレクト)を単体で検証しておくと、削除後の不安が残らない
- 「保存先をSSDに向ける」実装は、パスを寄せた瞬間にそのドライブへの依存を生む。バックアップ置き場と実行環境は、意識して分けないと簡単に混ざる
- 一度組んだ設計でも、問い直されたら素直に作り直す。数字上の成功(空いた9.5GB)と設計の健全さは別の軸
今日の終わりに
朝は「昨日どこまでやったっけ」から始まって、デプロイの認証切れを再ログインで越え、ローカルの大容量アセットをR2配信一本に寄せ、最後はSSD移設の設計を一度間違えてから組み直した。どの詰まりも、原因は小さい。認証トークンが切れていただけ、保存先のパスを一箇所寄せすぎただけ。それでも、直す前に「なぜそうなっていたか」を自分の言葉で説明できるところまで戻ってから直す。今日はそれの繰り返しだった。