インデックス作成のメインフロー¶
扱うこと: インデックス作成の起動から終了までの全体の流れ、取り込みのワーカーごとの取り込み、中止・強制終了・二重起動・エラー・進み具合の受け渡し。扱わないこと: 取り込み一覧の形式と再開の判定(取り込み一覧)、クロールと取り込み対象の決定(クロール対象フォルダと取り込み対象・取り込み対象の決定)、ワーカーの並列化そのもの(取り込みの並列化)。先に読むページ: インデックス作成。
インデックス作成の本体は invokeIndexer(indexer/indexer_run.ps1)である。画面は自分のプロセスのスレッド(インデクサの司令。IndexingSession)で indexer.ps1 -Channel を実行し、indexer.ps1 は invokeIndexer を呼ぶだけである。1 ファイルの取り込みは、取り込みのワーカー(1 種類。Office のアプリごとに持ち主が決まる)の取り込みのスレッドが並べて行う(取り込みの並列化)。
flowchart TD
S(["画面の[すべて更新]<br>インデクサの司令のスレッドで indexer.ps1 -Channel"]) --> WS{"ワークスペースを使える?<br>(getWorkspaceBlockMessage)"}
WS -- いいえ --> E0
WS -- はい --> M{"ほかのインデックス作成が実行中?<br>(newAppMutex)"}
M -- はい --> E0
M -- いいえ --> PRE["前回の indexing_log.txt を<br>indexing_log_previous.txt へ移し、<br>indexing_log.txt を開いて版と環境の見出しを書く"]
PRE --> T{"getTargetFolders 成功?<br>チェックの付いたフォルダがある?"}
T -- 失敗 --> E0["受け渡しの口の Error にメッセージを入れる<br>終了コード 1"]
T -- 成功 --> LG{"前の前の版のしるしがある?<br>(getLegacyIndexState)"}
LG -- はい --> NOTICE["前の版のインデックスは使えないという知らせを<br>ログに黄色で出す(getLegacyIndexMessage)"] --> OS
LG -- いいえ --> OS["前の版が作った検索用のファイル(system_index)を消す<br>(removeOldSystemIndex。消せなくてもログに黄色で出して続ける)"]
OS --> PK["前回のジャーナルを読み、書き出していないフォルダがあれば、<br>先に本文インデックスに書き出す(recoverIngestJournal。書き出せなければ次の回へ「未」で持ち越す)"]
PK --> L["取り込み対象の決定<br>インデックスごとに件数を数える"]
L -- "クロール中に中止" --> C0
L --> CF{"受け渡しの口の ConfirmTargets?"}
CF -- いいえ --> RF["前回失敗は RetryFailed のときだけ対象に加える<br>クラウドにだけあるファイルは、設定 cloudFiles が download のときだけ対象に加える"]
CF -- はい --> CW["取り込み予定を受け渡しの口に入れて<br>画面の返事を待つ"]
CW -- "[キャンセル]・中止・返事が無い" --> C0["取り込み対象にした行は前回のまま、<br>無くなったファイルの削除だけ反映して書き直す<br>終了コード 2"]
CW -- "[更新を開始]" --> RF
RF --> W2["取り込み一覧・元のフォルダの記録を書き出す"]
W2 --> Z{"対象が 0 件?"}
Z -- はい --> Z1["無くなった元のファイルを本文インデックスから外す<br>gram 索引を合わせる(updateGramIndexesOfRound)<br>『取り込みが必要なファイルはありません。』<br>終了コード 0"]
Z -- いいえ --> N["件数を表示<br>ワーカーの列を用意する(IngestDispatcher の Start)"]
N --> LOOP{"並びの先 200 件までに、<br>ワーカーに空きがあるまだ渡していないファイルがある?<br>(getIngestApp・selectIngestWorker)"}
LOOP -- はい --> STOP{"中止を求められている?<br>(受け渡しの口の Stop)"}
STOP -- はい --> STOP1["残りは『未取り込み』のまま<br>次のファイルを渡さない"] --> WAIT
STOP -- いいえ --> SRC{"元のファイルがある?"}
SRC -- "無い(フォルダはある)" --> SKIP["『元のファイルが無くなったため…』<br>TSV を削除して一覧から除く<br>(本文インデックスからも外す)"] --> LOOP
SRC -- "クロール対象フォルダごと無い" --> LOST["残りは『未取り込み』のまま<br>次のファイルを渡さない"] --> WAIT
SRC -- ある --> F["ジャーナルに「始」を記録<br>渡し先のワーカーの列にファイルを渡す(IngestDispatcher の Submit)<br>ワーカーのスレッドが無ければ始める"] --> LOOP
LOOP -- いいえ --> WAIT{"取り込み中のファイルがある?"}
WAIT -- はい --> RR{"結果を 1 つ受け取る<br>Office が要る(Reroute)?"}
RR -- "はい(中止を求められていないとき)" --> RT["Word・PowerPoint の持ち主に渡し直す(getRerouteApp)<br>取り込み中のまま"] --> LOOP
RR -- "中止で止めた(Stopped、または中止後の Reroute)" --> RS["失敗に数えず、『未取り込み』のまま<br>今の行を取り込み一覧に追記<br>以後、次のファイルを渡さない"] --> LOOP
RR -- いいえ --> R["ログを書き、結果(済・失敗)を取り込み一覧に追記"]
R --> PF["取り込み中のファイルも、まだ渡していないファイルも無いフォルダを、<br>本文インデックスに書き出す(flushPending → publishIndexFolders。前後で pending_remove.tsv を書く)"] --> LOOP
WAIT -- いいえ --> FIN["finally: 取り込みのスレッドを終える(Office のスレッドは監視と Office を終了)<br>作業領域を空にする<br>書き出し待ちのフォルダを本文インデックスに書き出す(書き出すたびに「出」を記録)<br>取り込み一覧を 1 ファイル 1 行に書き直す<br>ジャーナルに「終」を記録"]
FIN --> GR["中止でなく、フォルダも失われていなければ、<br>gram 索引を本文インデックスの今の状態に合わせる(updateGramIndexesOfRound。<br>作れなくてもログに黄色で出して続ける)"]
GR --> LOST2{"クロール対象フォルダが<br>見つからなくなった?"}
LOST2 -- はい --> E0
LOST2 -- いいえ --> END["『インデックス作成が完了しました/中止しました。(成功: N 件 / 失敗: M 件)』<br>失敗があれば失敗したファイルと原因の一覧を表示<br>終了コード 0(中止は 2)"]
取り込みのスレッドの中では、自分の列から 1 ファイルずつ次を行う(runIngestWorker)。ワーカーはすべて STA で、同じスクリプトを動かす。Excel・Word・PowerPoint のファイルは、そのアプリの持ち主のワーカーが自分の Office を 1 つ持って取り込む。ほかのワーカー(ingestThreads の数)は Office を使わずに読む。どのワーカーも監視を持つ(Office を持たなければ止める相手は無い)。
flowchart TD
A(["ワーカーのスレッドを始める<br>(STA。BelowNormal)"]) --> OW["自分の監視を始める(OfficeWatchdog の Start)"]
OW --> Q{"自分の列にファイルがある?<br>(列が閉じられるまで待つ)"}
Q -- はい --> F["1 ファイルを取り込む(invokeIngestTask。制限時間 10 分)<br>TSV を元のファイル名のフォルダに置く<br>Office が要るファイルなら、Office はこの時点で起動"]
F --> RES["結果(成功・失敗・回し直し・TSV の数・ログ)を結果の列に入れる<br>Office を使わずに読んで、Office が要ると分かったら Reroute"]
RES --> RS{"Office を使ったファイルを 100 件ごと?<br>または制限時間を超えた?"}
RS -- はい --> RS1["このスレッドの Office を終了"] --> Q
RS -- いいえ --> Q
Q -- "いいえ(列が閉じられた)" --> END["監視と Office を終了(諦めたワーカーは Office を終了しない)"]
- 利用者の入力を待つ処理(
pause・Read-Host)は無い。画面は受け渡しの口(newIndexerChannel)から進み具合を読んで表示する(インデックス作成の実行)。 - インデクサは
exitを使わず、終了コードを返す(invokeIndexer。画面のプロセスの中で動くため)。終了コードは受け渡しの口のExitCodeにも最後に入れる。画面はExitCodeが入ったら終わったとみなす。 - Excel・Word・PowerPoint は、それぞれの持ち主のワーカーが必要になったときに 1 つだけ起動し、そのスレッドで使い回す。ワーカーごとに 100 ファイルで、および取り込み失敗時に終了し、次に必要になったときに起動し直す(メモリ肥大化・不安定化の対策)。インデックス作成が起動する Office は、種類ごとに最大 1 つである(Office アプリの管理)。
- 取り込み一覧・進み具合・ログ・本文インデックスと 2-gram の索引は、司令だけが書く。取り込みのスレッドは結果を返すだけである。
- 中止: 画面の[中止]で受け渡しの口の
Stopが立つと、司令は次のファイルを渡さず、取り込み中のファイルも待たずに止める。クロール中なら残りのフォルダを見ずに止め、確認も出さずに終了コード 2 で終わる。止めたフォルダとまだ読んでいないフォルダは、取り込み一覧もインデックスも前回のままにする(createTargetListは止めたフォルダでは無くなったファイルのインデックスを消さない)。ただし、すでに読み終えたフォルダで無くなっていたファイルは、そのときにインデックスと取り込み一覧から外してある(取りやめのときと同じ)。取り込み中のファイルのうち、Office を使って取り込み中のものは、見張り(OfficeWatchdog)が自分が起動した Office アプリを止めて抜ける(利用者が使っているアプリは止めない)。Office を使わずに読んでいるファイルは、そのファイルの読み取りが終わるまで待つ(その 1 ファイルの打ち切りは今のところ無い。まだ取り出されていないファイルは、取り出されたときに中止を見て、読まずに返る)。止めたファイル・列で始めていないファイルは、取り込み途中の TSV を本文インデックスに入れず、取り込み一覧では「未取り込み」のまま残す(失敗に数えない)。取り込めたファイルは「済」のまま残り、検索できる。その後finallyで取り込みのスレッドの終了(アプリの終了)・作業領域の削除・本文インデックスへの書き出し・取り込み一覧の書き直しを行って終了コード 2 で終わる。残りのファイルは取り込み一覧で「未取り込み」のまま残り、次回取り込まれる。 - 強制終了: PC の停止・プロセスの強制終了などで止まった場合は
finallyが実行されないが、取り込み一覧には 1 件ごとに結果を追記しているため、次回は未取り込みのファイルから再開する。強制終了したときに取り込んでいたファイル(ワーカーに渡していたものすべて)は、ジャーナル(work/store/journal/ingest.tsv)に「始」だけが残り、次回は最後に回す(強制終了・時間切れからの再開)。取り込んでいたファイルのインデックスは、TSV をwork/publish/<PID>/w<番号>に集めてからフォルダごと入れ替えるため(publishIndexFiles)、「前回のインデックスがそのまま」か「今回の分がそろった状態」のどちらかになり、一部のシートだけが入ったインデックスは残らない。本文インデックスに書き出していない TSV(ジャーナルに「始」があって「出」が無いフォルダ)は、次回のインデックス作成の始めに書き出す(入れ替えと書き出し「本文インデックスへの書き出し」)。 - 二重に動かさない: 同じ
workのインデックス作成は 1 つだけ動かす(newAppMutex "indexer"。鍵はworkのパスから作る)。ミューテックスは司令のスレッドで取り、同じスレッドで放す。ログはミューテックスを取ってから開く(動いているインデックス作成のログを消さないため)。画面は自分が始めたインデックス作成の間は[すべて更新]を押せないため、ここで止まるのは画面を使わずに起動したものと重なった場合である。既に動いていればほかのインデックス作成が実行中です。インデックス作成が終わってから実行してください。として終了コード 1 で終わる。取り込み一覧・インデックスを 2 つが同時に書き換えると、状態が食い違うため。ミューテックスはプロセスが終われば解放されるため、強制終了しても残らない。 - クラウドにだけあるファイル: 既定ではダウンロードせず、取り込み対象にも一覧にも加えずに残す(前回の行があればそのまま残す)。残した件数は受け渡しの口の
CloudSkippedに入れ、インデックスごとの件数と相対パスをログに書く。ヘッドレスで実行したときは、設定cloudFilesがdownloadのときだけ取り込む。取り込み対象が 0 件で残したファイルだけがあるときは、終わりの案内を「クラウドにだけあるファイルは、ダウンロードせずに残しました」にする。 - 取り込みの直前に元のファイルが無くなった: 取り込み対象を調べてから実際に取り込むまでの間に、元のファイルが移動・削除されることがある。「失敗」として記録すると、利用者が再取り込みを選ぶまで残ってしまうため、取り込まずに TSV を削除して取り込み一覧から除き、本文インデックスからも外す(
元のファイルが無くなったため、取り込まずに一覧から除きます。)。次回のクロールでも見つからないため、結果は同じになる。 - インデックス作成中にクロール対象フォルダごと見えなくなった: ネットワークの切断・USB メモリの取り外しなどでクロール対象フォルダにアクセスできなくなった場合、そのまま続けると残りのファイルがすべて「失敗」になり、次に再取り込みを選ぶまでインデックスに入らない。そのため、次のファイルを渡すのをやめ、取り込み中のファイルが終わるのを待ってから、残りを「未取り込み」のまま中止し、受け渡しの口の
Errorにメッセージを入れて終了コード 1 で終わる(finallyは通るため、アプリの終了・取り込み一覧の書き直しは行う)。フォルダを使えるようにしてからインデックス作成を始めると、続きから取り込む。 - 続けられないエラー: 捕まえていない例外(クロール対象フォルダが無い・チェックの付いたフォルダが無い・設定ファイルを読み込めない・取り込みのスレッドが止まった等)は
invokeIndexerが受け、メッセージをログと受け渡しの口のErrorに入れて終了コード 1 で終わる。画面がこのメッセージを表示する。 - 進み具合の受け渡し: 司令は進み具合(段階・処理済み・残り・失敗・いま行っていること)を受け渡しの口の
Progressに入れ(writeIndexingProgress)、画面は 1 秒ごとに読む(readIndexingProgress。インデックス作成の実行)。書くのは「ファイルを取り込みのスレッドに渡すとき」「結果を受け取ったとき」「段階が変わるとき」である。 - 取り込み一覧(数万行になる)を画面が毎秒読み直すと、1 回に数秒かかって画面が固まる(5 万行で約 5.8 秒)ため、進み具合は取り込み一覧と分けている。
- 段階は
クロール(取り込み対象を探している。件数はまだ分からない)・確認(数え終えて、画面で取り込むかどうかを選ぶのを待っている)・取り込み(ファイルを取り込んでいる)・仕上げ(Office アプリの終了・本文インデックスへの書き出し・取り込み一覧の書き直し)。待ち時間が長くなりがちな「クロール」「仕上げ」でも、何をしているか(どのフォルダをクロールしているか・取り込み一覧を書き直しているか)を書く。 - インデックス作成が終わっても消さない(画面が終わり方(成功・失敗の件数)を読むため)。
- 取り込み前の確認(受け渡しの口の
ConfirmTargets。画面から実行したとき): 取り込み対象を数え終えたところで、インデックスごとの件数を受け渡しの口のPlanに入れ、段階を確認にして画面の返事を待つ(IndexingReporter.WaitForApproval。Answeredで待つ)。画面が取り込む返事(answerIndexingPlan。@{RetryFailed; IncludeCloud})を渡せばインデックス作成を始め(RetryFailedなら、前回失敗したファイルも対象に加える。IncludeCloudなら、クラウドにだけあるファイルも、ダウンロードして対象に加える。クラウドにだけあるファイル)、取りやめ($null)か中止(Stop)なら 1 件も取り込まずに終了コード 2 で終わる。60 分待っても返事が無ければ取りやめる(画面が返事をしないまま待ち続けないため)。待ち終えたらPlanを外す。 - 取りやめたときは、取り込み一覧を前回の内容のまま書き直す(今回の取り込み対象は記録しない)。「未取り込み」で記録すると、画面の表示が[続きから再開]になり、インデックス作成を中断したように見えるため。一方で、元ファイルが無くなった分の削除は済んでいるため、取り込み一覧そのものは書き直す。
- 選んだインデックスだけの更新(受け渡しの口の
OnlyNames。インデックス名の配列。空なら、チェックの付いたものすべて): 対象を「setting.configのtargetFolders[].enabledが true のもの」のうちOnlyNamesに入るものだけに絞る(selectOnlyNames)。名前を指した行にチェックを付けて保存するのは画面で、インデクサは「チェックが付いていてOnlyNamesに入る」だけを見る。 - 設定に無い名前・チェックが付いていない名前は黙って飛ばさず、ログに 1 行ずつ書き、受け渡しの口の
OnlySkipped(@{Name; Reason}の配列)に残す。選んだ名前がどれも更新できなければエラーにして終わる(終了コード 1)。 - 選ばなかったインデックスは、その回では一切触らない。取り込み一覧の行は前回のまま、確認の表(
Plan)に出さず(「チェックなし」とも出さない)、失敗の一覧にも加えない。 - 中止(
Stop)と進み具合(Progress)は回全体のもので、インデックスごとには分けない。 - ログ: 表示内容は
writeIndexerLogでwork/indexing_log.txt(UTF-8 BOM 付き)に書く。二重起動の鍵を取れたあと、前回のindexing_log.txtをindexing_log_previous.txtへ移し(あれば上書き。鍵を取れなかったときは移さない)、新しいログの先頭に版と環境の見出しを書く(getToolDiagnosticHeader。見出しが書けなくても作成は続ける)。移す処理は画面のスレッドでなく、司令のスレッドで走る。取り込みのスレッドは 1 ファイル分のログを貯めて結果と一緒に返し、司令が書く(結果を受け取った順に並ぶ。先頭の[番号/件数]が取り込み対象の中の順である)。画面を使わずに実行したときはコンソールにも出す。問題が起きたときはこのログで確認する。