自炊書籍3冊をOCRで一括取り込み、Embedded Replicaのハングを潰した日

午前中だけで、自炊した投資関連の実務書3冊をyomitoku OCRに通してTurso DBへ取り込んだ。1冊あたり200〜400ページ。OCRはバックグラウンドで回り、その間に次の冊のDB格納スクリプトを準備してもらう流れができてきた。ただ、取り込み直後の検証クエリが2冊連続で沈黙し、ここで足が止まった。Embedded Replicaの読み取りハング。6月9日に一度解消したはずの問題だった。

朝イチ: 蔵書の確認とNotionエクスポートの取り込み

「あの著者の本って何か入ってましたっけ?」と聞くところから1日が始まった。蔵書DBのbooksテーブルを検索してもらい、続けてNotionのExport機能で出力したHTML+画像+CSVのセットを取り込んでもらった。

  • CSVが0件と読まれる問題 → 実際の列構成を確認して、実書籍の章構成でルートCSVを生成し直すスクリプトを作ってもらった
  • 概要7件がマッチしない問題 → CSV側のTitleがファイル名より長い(切り詰めなし)ことが原因と判明
  • dry-runで93チャンクを確認してから本実行

取り込み後、restructure(章節単位の統合)で93→87チャンクに整理。FTS検索テストとブラウザでの表示確認まで通した。

yomitoku OCRパイプライン: 3冊連続で回す

そこから自炊PDFの取り込みを3冊連続で依頼した。パイプラインはどの冊もほぼ同じ形に収まった。

  1. PDFの冒頭ページ・目次・奥付をpymupdfで画像化して確認(ファイルサイズ制限でReadできないため)
  2. yomitoku OCRをバックグラウンドで起動(1冊あたり数分〜)
  3. 完了後、図ファイルをリネーム
  4. DB格納スクリプトでチャンクをTursoへ
  5. amazon_metadataへの紐付け確認とFTS検索テスト
  6. restructure-bookで目次に基づきページ単位チャンクをセクション単位に統合

3冊の数字はこうなった。

ページ数抽出図格納チャンクrestructure後
1冊目225ファイル分28点17857
2冊目399ページ45点36258
3冊目241ページ41点20356

2冊目では取り込み後に図の評価(/book-cleanup)も実行してもらった。45点の図をサブエージェント5並列で目視評価させ、装飾図18点のタグを除去、情報図27点を残した。帯やイメージカットまで検索に引っかかる状態を、これで防げる。

途中、OCR実行中に画面へ赤いエラーが出て「なんかエラー出てますけど」と聞いたら、「60秒待ってからログ確認」のためのsleepコマンドがツール側にブロックされただけで、OCR本体には無関係という説明だった。待機には専用の仕組みを使えという制約らしい。

Embedded Replicaの読み取りハング再発

2冊目の取り込み後、検証クエリの出力が、いつまで待っても空のまま動かなかった。モニターはタイムアウトし、出力ファイルを直接覗いても空。Embedded Replica接続でハングしている可能性が高いと判断して、タスクを停止しHTTP直接接続で検証をやり直してもらった。

3冊目でも同じ場所で止まった。amazon_metadata紐付けとFTS検索テストのタスクが返ってこない。再びタスクを殺してHTTP直接接続に切り替え。2回続いたので、「一回解消したと思ったんですけどね」と原因を聞いた。

原因: 6/9の修正はpush側、今日のはpull側

推定ではなく実物で確認してもらった。レプリカファイルの状態、過去のissueファイル、db.pyの実装を順に調べた結果がこれだった。

  • 前回(6/9)に解消したのは「push側」の問題。ローカルの書き込みをクラウドへ送る経路の修正だった
  • 今日ハングしたのは「pull側」。取り込み直後にレプリカがクラウドの変更を引き込む同期処理で止まっていた
  • issueファイルには実は「未解決の残課題」として記録が残っていた

「もしかして別のセッションで取り込んでたから?」とも疑ったが、答えはノー。別セッションは直接の原因ではなく、同じセッション内の取り込み直後の読み取りで、レプリカ同期そのものがハングしていた。レプリカは「読み込むタイミングでクラウドの内容が反映される」仕様で、その反映処理こそが今回止まった本体だった。

対応: 読み取りもHTTPデフォルトに切り替え

「そうしましょうか。スムーズな方に書いてください」と決めて、db.pyの読み取りをEmbedded ReplicaからHTTP直接接続デフォルトに書き換えてもらった。他にEmbedded Replica前提の箇所がないかも確認。

切り替え後、今朝ハングしたのと同一の読み取りパターン(取り込み直後の_get_conn()経由)をテストしたら、0.71秒で返ってきた。さっきまで無限に待たされていたクエリが1秒を切る。ローカルレプリカの同期待ちを抱えるより、最初からWebに聞きに行く方がこのワークロードには合っていた。

devサーバーのポートを3000→3100に変更

合間に、book-knowledge-baseのdevサーバーポートを3000から3100に変えてもらった。localhost:3000は他のプロジェクトで使うことが多く、実際このときもポート3000で別プロセス(mdx-playground側)が動いていた。nuxt.config.tsの変更とドキュメント内の3000参照の更新、起動中サーバーの確認までやってもらって完了。

技術書のコードOCR、試す前に判明したこと

午前の最後に、設計を扱った技術書のPDFを出した。コード部分がOCRでどこまで拾えるか読めなかったので、「試しに最初の方だけやってみて」と精度確認から入る判断をした。

ところが調べてもらうと、この本は前日(6/10)のセッションで全400ページ取り込み済みだった。Turso DBに102チャンクで登録されていて、restructure用のスクリプトもリポジトリに残っていた。コンソールでタイトルが文字化けして見えたのは出力側の文字コードの問題で、DB上は正常。

午後に出版社公式のサンプルコードZIPを展開してもらって、コードの取り込まれ方の答え合わせができた。

  • 本文中のコードリストは、テキストではなく図画像(PNG)としてDBに入っていた。yomitoku OCRがコードブロックを図として切り出すため
  • 本文チャンクには「リスト1.1 …」のような説明文と<img>タグだけが残る
  • つまりDB内のコードは検索もコピーもできない。公式サンプルコードが唯一の「正」のソースになる

コード混じりの技術書は、本文OCR+公式サンプルコードの二本立てで扱うのが正解、というのが今日の結論。この本を題材にしたリライト計画は別途立てた(それは別記事に譲る)。

学び

  • Embedded Replicaの「解消した」は経路ごとに確認する。push側を直してもpull側は別の問題として残る。issueファイルの「未解決の残課題」を読み返していれば、2回のハングは1回で済んだ
  • 取り込み→即読み取りのワークロードでは、レプリカ同期の待ちがそのままハングに化ける。書き込み直後に読むならHTTP直接接続の方が速くて確実だった(実測0.71秒)
  • yomitokuはコードブロックを図として切り出す。技術書を取り込むときはコードがテキスト検索できない前提で、公式サンプルコードの確保をセットにする
  • OCRをバックグラウンドで回しながら次の冊の格納スクリプトを準備する並走パターンで、3冊を午前中に流しきれた