取り込みの並列化¶
扱うこと: 取り込みのワーカーと、司令・取り込みスレッド・監視の役割分担、Office の起動し直しと制限時間、中止のときの片づけ、実測。扱わないこと: メインフローそのもの(インデックス作成のメインフロー)、スレッド・プロセス全般の一覧(プロセスとスレッド)。先に読むページ: インデックス作成のメインフロー。
インデクサの司令が取り込み対象を並べ、取り込みのワーカーに 1 ファイルずつ渡す。ワーカーは 1 種類で、どれも同じスクリプトを動かし、どの種類のファイルも受けられる(STA)。ワーカーごとに列と取り込みスレッドを持つ。取り込みスレッドは結果(成功・失敗・TSV の場所)を返し、書き出しは司令がまとめて行う。
Office のアプリ(Excel・Word・PowerPoint)は、そのアプリのファイルを最初に受けたワーカーが「持ち主」になる。Office の COM を呼ぶのは持ち主のスレッドだけで、アプリごとに起動する Office は最大 1 つになる。
| 仕事 | 対象(拡張子) | 渡し先のワーカー | Office |
|---|---|---|---|
| Excel | .xls*(すべての Excel のファイル。セルの表示値を読むため Excel が要る) |
Excel の持ち主 | Excel を 1 つ |
| Word | .doc |
Word の持ち主 | Word を 1 つ |
| PowerPoint | .ppt |
PowerPoint の持ち主 | PowerPoint を 1 つ |
| Office を使わずに読む | .docx・.docm・.pptx・.pptm・テキスト・PDF など |
持ち主でない、いちばん空いているワーカー(無ければいちばん空いているワーカー) | 使わない |
ワーカーの数は、Office を使わずに読む仕事の数(設定 ingestThreads。1〜4。0 はコア数 − 1 で 1〜4。取り込むファイルの数より多くはしない)に、この回に要る Office のアプリの数(最大 3)を足した数である。0 のときは、取り込みのスレッドを使わず、司令のスレッドで取り込む。
flowchart LR
C["インデクサの司令<br>(MTA)<br>selectIngestWorker で渡し先を決める"] --> Q1[("ワーカー 1 の列")]
C --> Q2[("ワーカー 2 の列")]
C --> Qn[("ワーカー n の列")]
Q1 --> W1["ワーカー 1<br>(STA・Office の持ち主は<br>自分の Office を 1 つ・自分の監視)"]
Q2 --> W2["ワーカー 2<br>(STA)"]
Qn --> Wn["ワーカー n<br>(STA)"]
W1 & W2 & Wn -- 結果 --> R[("結果の列")]
R --> C
C -- "Office が要る(回し直し)<br>持ち主の列へ" --> Q1
C -- フォルダが揃ったら --> P["本文インデックス"]
sequenceDiagram
participant C as 司令
participant W as ワーカーのスレッド
participant O as Office(要る場合)
C->>W: IngestDispatcher がファイルを渡す(Task.Office で Office が要るかも伝える)
W->>O: コピーを開いて抽出(COM/office_reader)
alt Office が要ると分かった(Office を使わずに読む仕事)
O-->>W: Reroute(officeRequiredMessage)
W-->>C: Reroute を返す
C->>W: Office の持ち主のワーカーに渡し直す
else 取り込めた
O-->>W: TSV の場所
W-->>C: 結果(成功・TSV)
else 制限時間超過
Note over W: 監視が Office を強制終了
W-->>C: 結果(失敗)
else 制限時間超過・利用者が使っている PowerPoint
Note over W: 監視は止めずにワーカーを諦める
C->>C: そのワーカーの取り込み中・列のファイル、PowerPoint の続きを取り込まず、失敗として理由を見せる
end
C->>C: completeIngestJob で取り込み一覧・進み具合を更新し、PendingPublish の数が 0 になったら本文インデックスを書き出す
- 渡し先は判断層が決める(
selectIngestWorker・getIngestApp・getRerouteApp。判断層のindexer_decide.ps1)。Excel のファイルはセルの表示値を読むため、すべて Excel の仕事にする。Word・PowerPoint は、旧形式(.doc・.ppt)だけ Office の仕事にし、新形式は Office を使わずに読む仕事にする。テキストの拡張子(.txt等)や PDF は、どの分岐にも当たらないため Office を使わずに読む仕事になる(テキストファイルの読み取り)。 - Office は種類ごとに 1 つだけ起動する。 あるアプリのファイルは、そのアプリの持ち主のワーカーにだけ渡す。持ち主がまだいなければ、持ち主のいないワーカーのうちいちばん空いているものを持ち主にする(持ち主のいないワーカーが 1 つも無ければ、いちばん空いているワーカーが 2 つ目以降のアプリも持つ)。インデックス作成が起動する Office は、Excel・Word・PowerPoint がそれぞれ最大 1 つになる。PowerPoint も、持ち主が一度起動したら使い回す(変換のたびに起動し直さない)。同じ種類の Office を複数のスレッドが同時に起動しないため、起動の前後のプロセスの一覧の差で PID を取り違えない。起動した Office の優先度は変えない(スレッドの一覧)。起動した Office の PID は受け渡しの口(
OfficePids)に記録する。 - ワーカーのスレッドは、そのワーカーにファイルを初めて渡すときに始める(
IngestDispatcherのSubmit)。渡すファイルが無いワーカーは、スレッドも Office も作らない。列はIngestDispatcherのStartがワーカーごとに作る(BlockingCollection)。 - ファイルごとに、Office を使うかを決める。 渡すファイルの
Task.Officeが偽のとき、ワーカーは Office を使わずにoffice_readerで ZIP(XML)の形式を直接読む。真のときは Office の COM で読む。ワーカーを動かすスクリプトは 1 種類だけで、これで取り込み方を切り替える。スレッドを使わないとき(司令のスレッドで取り込むとき)はTask.Officeを付けず、今までどおり動く。 - Office が要ると分かったファイルは、Office の持ち主のワーカーに回し直す。 新形式の拡張子でも、中身が ZIP でない(パスワード付き・中身が旧形式)ことがある。
extractDocumentが${officeRequiredMessage}(このファイルの取り込みには Word・PowerPoint が要ります。)の例外を投げ、invokeIngestTaskがRerouteを返す。司令は同じファイルを Word または PowerPoint の持ち主(getRerouteApp)に渡し直す。回し直しは、渡してある数の上限を超えても渡す(持ち主が取り込み中でも列に足す)。回し直す間も取り込み中のまま扱う。ZIP でも旧形式でもない PowerPoint のファイル(壊れている)は、回し直さずに失敗にする。パスワード付き(新形式)・IRM・秘密度ラベルの暗号化(暗号化されたファイルの判定)も、回し直さずにその場で失敗にする(Officeが要らないため)。「形式の分からないバイナリ」は、アプリごとの予備が有効なときだけ今の旧形式と同じく回し直す。 - 判定でOfficeにまだ触れていない失敗は、
stopAppを呼ばない。 パスワード付き・IRM・「形式の分からないバイナリ」(予備が無効)の失敗は、例外のDataに印(throwProtectionFailure)を付け、invokeIngestTaskがそれを見て、そのワーカーのOfficeを終了し直さずに次のファイルへ進む。IRM・パスワード付きのファイルが並んでも、そのたびにOfficeを起動し直して遅くならないため。 - 並びの先を見て、空いているワーカーに渡す。 司令は、まだ渡していない最初のファイルから
${ingestLookAhead}(200 件)先までを見て、ワーカーに空きがあるファイルを渡す。ワーカー 1 つに渡しておける数は 2(取り込み中の 1 件と次の 1 件)である。Excel のファイルが続いても、Office を使わずに読むワーカーを遊ばせない。取り込む順は取り込み一覧の順と変わることがあるが、結果(取り込み一覧・本文インデックス)は同じになる。 - 監視はワーカーごとに持つ。 どのワーカーも、始めるときに自分の監視のスレッドを作る(
OfficeWatchdogのStart)。監視は、そのスレッドの制限時間だけを見て、そのスレッドが起動した Office だけを PID で止める。制限時間と中止の見張りが働くのは、Office を使うファイル(task.Office)を取り込んでいる間だけで、Office を使わずに読むファイルの間はDeadlineを入れず、見張りは何もしない(Office を持つワーカーが PDF などを読んでいても、遊んでいる Office を止めず、そのファイルは読み終わるまで待つ)。ただし、取り込み中に利用者が窓を開いた PowerPoint は、制限時間を過ぎても止めない(窓を一度でも見た印がある間。Office アプリの管理)。止めない代わりにそのワーカーを諦める。諦めたワーカーの取り込み中・列のファイルと、持ち主が諦めたワーカーのアプリのファイルは、取り込まず、失敗として理由を見せる(状態は未取り込みのまま)。ほかのワーカーは取り込みを続ける。 - Office の起動し直しと制限時間は、ワーカーごとに数える。 ワーカーごとに 100 ファイルで起動し直す(Office を使ったファイルだけを数え、取り込んだあとに起動し直す)。1 ファイルの制限時間は 10 分。
- 一時フォルダはスレッドごとに分ける。 作業領域の
<PID>\w<番号>(newWorkerTmpDir)と、publishのw<番号>を使う。 - 本文インデックスはフォルダが揃ってから書く。 司令は、はじめにフォルダごとのファイルの数を
PendingPublish.AddPendingで数えておき、ファイルを渡すたびにDispatch、結果を受け取るたびにComplete(または渡さずに済ませるときSkip)を呼ぶ。結果を受け取るたびに、取り込み中の数・まだ渡していない数がどちらも 0 になったフォルダの本文インデックスを書き出す(indexer_run.ps1のflushPendingPublishがPendingPublish.TakeFlushableを呼ぶ)。 - 取り込み一覧と進み具合は司令だけが書く。 取り込みスレッドは結果を結果の列(
BlockingCollection)に入れるだけで、取り込み一覧(addStatusRow)・進み具合は司令が書く。結果の受け方はcompleteIngestJob(indexer_run.ps1)にまとめてあり、成功・失敗・後回し・中止のどれも同じ関数が反映する。ログも、取り込みスレッドが 1 ファイル分を貯めて結果と一緒に返し、司令が書く。 - ワーカーに渡した仕事は、すべてジャーナルに「始」で記録する(
work/store/journal/ingest.tsv。書くのは司令だけ)。強制終了の後は、結果の行が取り込み一覧に無い仕事すべてを「前回止まった仕事」として扱う(ジャーナルの形)。 - 中止。 司令は振り分けをやめ、Office を使っているファイル(回し直したものを含む)の取り込みは待たずに止める(Office を使わずに読んでいるファイルは、下の表のとおりそのファイルの読み取りが終わるまで待つ)。Office を持つワーカーは、見張りが
Channel.Stopを見て自分が起動したアプリを止め(Cancelled。TimedOutではない)、invokeIngestTaskがStoppedを返す。列に残っていたファイルもingestFileを呼ばずにStoppedで返る。Stoppedのファイルは、一覧の行を作らず、一時の TSV も本文インデックスに入れない(「未取り込み」のまま次回取り込む)。そのあとに書き出しと Office の片づけを行う。
中止が待つものは次のとおり。
| 場面 | 中止が待つもの |
|---|---|
| クロール(一覧化) | フォルダごとと、100 ファイルごとの確認(それ以上は待たない) |
| 列で始めていないファイル | 待たない(ingestFile を呼ばず Stopped) |
| Office を使って取り込み中 | 見張りの周期(500 ミリ秒)と、アプリの終了 |
| Office を使わずに読んで取り込み中 | そのファイルが終わるまで(打ち切りは範囲外) |
各ワーカーのスレッドは、列が閉じられたら自分の Office を終了して終わる(IngestDispatcher の Stop)。
- 取り込みスレッドが止まったら。 取り込みスレッドが途中で止まった(読み込めない等)ときは、インデックス作成を続けられないエラー 取り込みのスレッドが止まりました:<理由> にする。
setting.config の ingestThreads で変えられるのは、Office を使わずに読む仕事の数だけである。Office のアプリ(Excel・Word・PowerPoint)の分のワーカーは、いつも 1 つずつ・Office 1 つにする。
取り込みのスレッドを使うときは、はじめにログに N 件のファイルを取り込みます。(Office を使わずに読むファイルは R 個のスレッド、Excel・Word・PowerPoint のファイルはそれぞれ 1 つのスレッド(Office も 1 つ)で並べて取り込みます。…) と出す。
テストでは受け渡しの口の Workers を 0 にし、すべてのファイルの取り込みを司令のスレッドで行う(途中に割り込むため)。テストデータ(300 ファイル)で、スレッド 1 つと 4 つの結果(本文インデックスと取り込み一覧)が同じになることを確かめた。
速さは、空いている CPU の量で決まる。1 ファイルの取り込みには、Excel と PowerShell で約 1 コアを使う。スレッドと Office の両方の優先度を下げると、ほかのアプリが約 2 コアを使っている PC で、両方 Normal より 1〜2 割遅くなる(性能データ 1,080 ファイル。取り込みのスレッド 1 つ 518 秒、3 つ 456 秒。両方 Normal では 398 秒)。PowerPoint を変換のたびに起動し直すと旧形式 1 件に約 7 秒かかり、使い回すと約 0.25 秒である。