作者別: fukui

SolrCloud 環境におけるスキーマの読み込みと保存について

はじめに

Solr 5.4.1 以降においては、スキーマは schema.xml を使うファイルベースの管理ではなく Schema API を使う管理が推奨されています。
Schema API を利用して設定された内容は managed-schema というファイルに書き込まれますが、このファイルを直接編集しないようにと公式ドキュメントには書かれています。

とはいえ、インデックスの初期設定時にはファイルで設定を与えられた方が便利です。
ManagedIndexSchemaFactory が使われる設定においては、 schema.xml が存在すれば自動的に schema.xml の内容を読み込んで schema.xml.bak にリネームしてから managed-schema ファイルが作られることになっています。
それ以降のスキーマ変更は Schema API を利用する訳です。

SolrCloud 環境においても実際に上記のような動作をするのかどうか、簡単な検証をしてみました。

準備

configsets の準備

_default の configsets をコピーして使います。

$ cd server/solr/configsets
$ cp -r _default/ schema_convert
$ rm schema_convert/conf/managed-schema
$ vi schema_convert/conf/schema.xml
$ cat schema_convert/conf/schema.xml

maganed-schema を削除して、_default/solrconfig.xml との組み合わせでエラーにならない最小限の schema.xml を作成。

<schema name="default-config" version="1.6">
    <field name="_version_" type="plong" indexed="false" stored="false"/>
    <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
    <field name="key" type="string" indexed="true" stored="true" required="true" multiValued="false" />
    <field name="val" type="pint" indexed="true" stored="true" required="true" multiValued="false" />
    <uniqueKey>id</uniqueKey>
    
    <fieldType name="string" class="solr.StrField" sortMissingLast="true" docValues="true" />
    <fieldType name="pint" class="solr.IntPointField" docValues="true"/>
    <fieldType name="plong" class="solr.LongPointField" docValues="true"/>

    <fieldType name="booleans" class="solr.BoolField" sortMissingLast="true" multiValued="true"/>
    <fieldType name="plongs" class="solr.LongPointField" docValues="true" multiValued="true"/>
    <fieldType name="pdoubles" class="solr.DoublePointField" docValues="true" multiValued="true"/>
    <fieldType name="pdates" class="solr.DatePointField" docValues="true" multiValued="true"/>

    <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100" multiValued="true">
      <analyzer type="index">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <!-- in this example, we will only use synonyms at query time
        <filter class="solr.SynonymGraphFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
        <filter class="solr.FlattenGraphFilterFactory"/>
        -->
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>
</schema>

configsets アップロード

$ ../../scripts/cloud-scripts/zkcli.sh -zkhost localhost:9983 -cmd upconfig -confdir schema_convert/conf -confname schema_convert

検証

コレクション作成

$ curl 'http://localhost:8983/solr/admin/collections?action=CREATE&name=schema_convert&numShards=1&replicationFactor=1&collection.configName=schema_convert&wt=xml'

コレクション作成時に schema.xml が読み込まれたはずです。確認してみましょう。

zookeeper データツリー確認

schema.xml が schema.xml.bak に変更されて managed-schema が作られたことが分かります。

Schema API による変更が managed-schema に反映されるか

変更前

$ curl -s http://localhost:8983/solr/schema_convert/schema/fields |grep name
      "name":"_version_",
      "name":"id",
      "name":"key",
      "name":"val",

Solr の管理画面で確認した managed-schema の内容(抜粋)

変更

curl -X POST -H 'Content-type:application/json' --data-binary '{
  "add-field":{
     "name":"label",
     "type":"string",
     "indexed":true,
     "stored":true }
}' http://localhost:8983/solr/schema_convert/schema

“label” というフィールドを追加しました。

変更後

$ curl -s http://localhost:8983/solr/schema_convert/schema/fields |grep name
      "name":"_version_",
      "name":"id",
      "name":"key",
      "name":"label",
      "name":"val",

Solr の管理画面で確認した managed-schema の内容(抜粋)

確かに変更内容が managed-schema に反映されました。

まとめ

通常の Solr core の環境では Schema API による設定変更は managed-schema の実ファイルに保存されます。

一方、SolrCloud の環境では各種設定ファイルは zookeeper 上にあるものを各ノードで共有する形になっています。schema.xml の読み込みや Schema API による変更の保存などは zookeeper のファイルツリー上で実ファイルと全く同じ内容が実行されることを確認できました。


Solrで入れ子構造の文書をインデックスする

入れ子構造の文書

入れ子になった文書をそのままインデックスできると便利なことがあります。たとえば

  • 親文書:ブログ本文、子文書:コメント
  • 親文書:製品の基本情報、子文書:サイズ違い、色違いなどのバリエーション
  • 親文書:音楽プレイリスト、子文書:曲

などです。

Solrで入れ子構造を扱う

Solrは入れ子になった文書を扱うことができますが、そのためにはいくつかの条件と制限があります。

  • 親-子の2階層まで
  • indexされるがstoreされない root フィールドを持つ。同一の文書に含まれるすべての親要素、子要素は自動的に root フィールドに同じ値を与えられる
  • 親階層の文書であることを示すフィールドを持つ。検索時の条件として使う。
  • いわゆるスキーマレスの設定が必要。構造の異なる(場合が多い)親と子を同じコア(コレクション)内で扱う必要があるため

例: プレイリスト

以下のようなプレイリスト情報を Solr に与えます。
(_default の configset で core を作っていればスキーマレスでインデックス作成できます)

{
    "id":"list_1",
    "contentType":"playlist",
    "title":"list1",
    "songs":[
	{
	    "id":"song_1",
	    "contentType":"song",
	    "title":"title1",
	    "artist":"artist1",
	    "trackNum":1
	},
	{
	    "id":"song_2",
	    "contentType":"song",
	    "title":"title2",
	    "artist":"artist2",
	    "trackNum":2
	}
    ]
},
{
    "id":"list_2",
    "contentType":"playlist",
    "title":"list2",
    "songs":[
	{
	    "id":"song_3",
	    "contentType":"song",
	    "title":"title3",
	    "artist":"artist3",
	    "trackNum":1
	},
	{
	    "id":"song_1",
	    "contentType":"song",
	    "title":"title1",
	    "artist":"artist1",
	    "trackNum":2
	}
    ]
}

こういう入れ子構造を検索するのに使える Block Join Query Parser が用意されています。

親文書すべて

q={!parent which="contentType:playlist"}

"response":{"numFound":2,"start":0,"docs":[
      {
        "id":"list_1",
        "contentType":["playlist"],
        "title":["list1"],
        "_version_":1630800916686831616},
      {
        "id":"list_2",
        "contentType":["playlist"],
        "title":["list2"],
        "_version_":1630800916689977344}]
  }

子文書の情報もまとめて取得

ChildDocTransformerを利用します。

q={!parent which="contentType:playlist"}
fl=id, title, [child parentFilter="contentType:playlist" childFilter="contentType:song" fl=id,trackNum,title,artist]

"response":{"numFound":2,"start":0,"docs":[
      {
        "id":"list_1",
        "title":["list1"],
        "_childDocuments_":[
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[1]},
        {
          "id":"song_2",
          "title":["title2"],
          "artist":["artist2"],
          "trackNum":[2]}]},
      {
        "id":"list_2",
        "title":["list2"],
        "_childDocuments_":[
        {
          "id":"song_3",
          "title":["title3"],
          "artist":["artist3"],
          "trackNum":[1]},
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[2]}]}]
  }

「”title2″を含むプレイリスト」

q={!parent which="contentType:playlist"} title:title2
fl=id, title, [child parentFilter="contentType:playlist" childFilter="contentType:song" fl=id,trackNum,title,artist]

"response":{"numFound":1,"start":0,"docs":[
      {
        "id":"list_1",
        "title":["list1"],
        "_childDocuments_":[
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[1]},
        {
          "id":"song_2",
          "title":["title2"],
          "artist":["artist2"],
          "trackNum":[2]}
}

部分的に更新するとどうなるか

更新するときは親子関係にある文書をすべてひとまとめにすること、という制限を破るとどうなるか試してみました。

{
        "id":"list_1",
        "title":["list1"],
        "_childDocuments_":[
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[1]},
        {
          "id":"song_2",
          "title":["title2"],
          "artist":["artist2"],
          "trackNum":[2]}]
}

というプレイリストの一部分だけを更新してみます。

{
    "id":"song_2",
    "contentType":"song",
    "title":"title22",
    "artist":"artist2",
    "trackNum":2
}

上のようなデータを与えてupdateしても特にエラーにはなりません。

q=title:title2

{
        "id":"song_2",
        "contentType":["song"],
        "title":["title2"],
        "artist":["artist22"],
        "trackNum":[2],
        "_version_":1630801429121728512}]
}

title:title2 という条件で検索してみると id:song_2 単体として見れば更新されていることが分かりますが、実は親子関係は破壊されてしまってます。

q={!parent which="contentType:playlist"}
fl=id, title, [child parentFilter="contentType:playlist" childFilter="contentType:song" fl=id,trackNum,title,artist]

"response":{"numFound":2,"start":0,"docs":[
      {
        "id":"list_1",
        "title":["list1"],
        "_childDocuments_":[
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[1]}]},
      {
        "id":"list_2",
        "title":["list2"],
        "_childDocuments_":[
        {
          "id":"song_3",
          "title":["title3"],
          "artist":["artist3"],
          "trackNum":[1]},
        {
          "id":"song_1",
          "title":["title1"],
          "artist":["artist1"],
          "trackNum":[2]}]}]
}

検索実行時にJOINする方法との比較

この記事でご紹介したのは入れ子構造のままインデックスする方法です。それとは別に、親文書と子文書を別々のコア(コレクション)に分けて検索時にJOINする方法もあり、パフォーマンスと扱いやすさとのトレードオフが存在します。

  • 入れ子のまま扱う: 親子関係の情報自体をインデックスするのでパフォーマンス面で有利だがその分制限強め
  • 検索時JOIN: パフォーマンス面では不利だが柔軟

Solr の Kuromoji を extended で使うとインデックスサイズが予想以上に小さくなる問題について

前回得られた、Kuromoji の mode を extended にした場合にインデックスサイズが最も小さくなるという不思議な結果を調査しました。

mode=search と mode=extended の違いは未知語の扱いです。 なので、未知語を含むクエリがどのように扱われるのかを確認しました。

【mode=search の場合】

"rawquerystring":"title:西沙諸島 AND text:パラセル諸島"
"parsedquery":"+title:西沙諸島 +(text:パラセル text:諸島)"

【mode=extended の場合】

"rawquerystring":"title:西沙諸島 AND text:パラセル諸島"
"parsedquery":"+title:西沙諸島 +text:諸島"

未知語が1-gramに分割されてインデックスサイズが多少大きくなることを予想していたのに、実際は逆に未知語が落とされてしまっています。

そこで、mode=search と mode=extended とで「パラセル諸島」を形態素解析した結果を比べてみました。

mode=search で「パラセル諸島」を形態素解析した結果
mode=extendedで「パラセル諸島」を形態素解析した結果

mode=search では「パラセル」が名詞-一般となっており、mode=extended では「パラセル」が1文字ずつに分解されてそれぞれが記号-一般となっています。分解のされ方はどちらも期待通りです。形態素解析に問題が無さそうなので、怪しいのは形態素解析後のフィルタということになります。おそらくストップワードに引っ掛かっているのでしょう。

設定ファイルの lang/stoptags_ja.txt を確認すると、記号-一般がストップワードとして定義されていました。

#  symbol-misc: A general symbol not in one of the categories below.
# e.g. [○◎@$〒→+]
記号-一般

そこで、この行をコメントアウトした configset (それ以外は全く同じ)を作り、wikipedia-ja のインデックスを作成しました。

4.2G    example/cloud/node1/solr/wikipedia-ja-extended2_shard1_replica_n1
2.1G example/cloud/node1/solr/wikipedia-ja-extended_shard1_replica_n1
3.1G example/cloud/node1/solr/wikipedia-ja-normal_shard1_replica_n1
3.1G example/cloud/node1/solr/wikipedia-ja-search_shard1_replica_n1

予想通り、4.2GBとmode=searchよりもインデックスサイズが大きくなりました。形態素解析の結果は以下の通りです。

"rawquerystring":"title:西沙諸島 AND text:パラセル諸島"
"parsedquery":"+title:西沙諸島 +(text:パ text:ラ text:セ text:ル text:諸島)"

インデックスサイズが大きくなったのは、単に未知語が1-gramに分割されただけではなく、他の記号類もストップワードから外れてインデックスに入ってしまったからだと思います。 1-gram に分割したときの品詞を変更できるようになっていればいいのですが、ざっと調べた感じではそうはなっていないようです。 その弊害を考えると、mode=extended はちょっと使い勝手が悪いと言えるかもしれません。


Solrで日本語版Wikipediaのインデックスを作った場合のサイズをTokenizerの設定毎に調べる

はじめに

Solrで日本語の文書を扱う場合に考えなくてはならないことの一つに、Tokenizerをどれにするか、があります。ざっくり言うと、n-gram(だいたいは2-gram)にするか、形態素解析にするか、です。
それぞれの長所、短所はこんな感じです。

n-gram

長所

  • 検索漏れが少ない
  • 未知語に強い

短所

  • nより短いキーワードを扱えない
  • インデックスサイズが大きくなる
  • 検索結果のノイズが多い

形態素解析

長所

  • 検索結果のノイズが少ない
  • インデックスサイズを小さくする余地が大きい

短所

  • 未知語に弱い
  • 入力キーワードに対する完全一致検索が難しい

双方の長所・短所に出てきたインデックスサイズがどれくらいの差になるのかを日本語版Wikipediaを対象に調べてみました。

SolrにWikipediaを投入する

Wikipediaのダンプデータをダウンロード

https://dumps.wikimedia.org/jawiki/
の latest から jawiki-latest-pages-articles.xml.bz2 をダウンロードして展開しておきます。

configset準備

_default をベースに設定ファイルを用意します。

cp -r server/solr/configsets/_default server/solr/configsets/wikipedia 

wikipedia/conf/solrconfig.xml に以下を追加

<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-dataimporthandler-.*\.jar" />

<requestHandler name="/dataimport" class="solr.DataImportHandler">
  <lst name="defaults">
    <str name="config">solr-data-config.xml</str>
  </lst>
</requestHandler>

Wikppediaインポート用の DataImportHander の設定 solr-data-config.xml を
https://wiki.apache.org/solr/DataImportHandler#Example:_Indexing_wikipedia
を参考にして作成

<dataConfig>
  <dataSource type="FileDataSource" encoding="UTF-8" />
  <document>
    <entity name="page"
                processor="XPathEntityProcessor"
                stream="true"
                forEach="/mediawiki/page/"
                url="data/jawiki-latest-pages-articles.xml"
                transformer="RegexTransformer,DateFormatTransformer">
      <field column="id"        xpath="/mediawiki/page/id" />
      <field column="title"     xpath="/mediawiki/page/title" />
      <field column="revision"  xpath="/mediawiki/page/revision/id" />
      <field column="user"      xpath="/mediawiki/page/revision/contributor/username" />
      <field column="userId"    xpath="/mediawiki/page/revision/contributor/id" />
      <field column="text"      xpath="/mediawiki/page/revision/text" />
      <field column="timestamp" xpath="/mediawiki/page/revision/timestamp" dateTimeFormat="yyyy-MM-dd'T'hh:mm:ss'Z'" />
      <field column="$skipDoc"  regex="^#REDIRECT .*" replaceWith="true" sourceColName="text"/>
    </entity>
  </document>
</dataConfig>

configsetをアップロード

(cd server/solr/configsets/wikipedia/conf && zip -r - *) |curl -X POST --header "Content-Type:application/octet-stream" --data-binary @- "http://localhost:8983/solr/admin/configs?action=UPLOAD&name=wikipedia"

コレクション”wikipedia”を作成

curl 'http://localhost:8983/solr/admin/collections?action=CREATE&name=wikipedia&numShards=1&replicationFactor=1&collection.configName=wikipedia&wt=xml'

インポート開始

管理画面からインポート開始

各Tokenizerの設定

以下の4種類の設定で試してみます。
今回は手っ取り早く上記の手順の「configsetの準備」のところで managed-schema ファイルを編集しています。

CJK bigram

managed-schema の変更箇所

(略)
<pre>
    <fieldType name="text_cjk" class="solr.TextField" positionIncrementGap="100">
      <analyzer>
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <!-- normalize width before bigram, as e.g. half-width dakuten combine  -->
        <filter class="solr.CJKWidthFilterFactory"/>
        <!-- for any non-CJK -->
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.CJKBigramFilterFactory"/>
      </analyzer>
    </fieldType>
</pre>
(略)
<field name="title"     type="string"  indexed="true" stored="false"/>
<field name="revision"  type="pint"    indexed="true" stored="true"/>
<field name="user"      type="string"  indexed="true" stored="true"/>
<field name="userId"    type="pint"     indexed="true" stored="true"/>
<field name="text"      type="text_cjk"    indexed="true" stored="false"/>
<field name="timestamp" type="pdate"    indexed="true" stored="true"/>
(略)

Kuromoji (mode=normal)

辞書通りの分割
(例) 株式会社→「株式会社」

managed-schema の変更箇所

(略)
<pre>
    <fieldType name="text_ja" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="false">
      <analyzer>
        <tokenizer class="solr.JapaneseTokenizerFactory" mode="normal"/>
        <filter class="solr.JapaneseBaseFormFilterFactory"/>
        <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" />
        <filter class="solr.CJKWidthFilterFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" />
        <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>
</pre>
(略)
<field name="title"     type="string"  indexed="true" stored="false"/>
<field name="revision"  type="pint"    indexed="true" stored="true"/>
<field name="user"      type="string"  indexed="true" stored="true"/>
<field name="userId"    type="pint"     indexed="true" stored="true"/>
<field name="text"      type="text_ja"    indexed="true" stored="false"/>
<field name="timestamp" type="pdate"    indexed="true" stored="true"/>
(略)

Kuromoji (mode=search)

複合語を細かく分割
株式会社→「株式」「会社」

managed-schema の変更箇所

        <tokenizer class="solr.JapaneseTokenizerFactory" mode="search"/>

それ以外は mode=normal と同じ。

Kuromoji (mode=extended)

mode=search + 未知語を 1-gram に分割

managed-schema の変更箇所

        <tokenizer class="solr.JapaneseTokenizerFactory" mode="extended"/>

それ以外は mode=normal と同じ。

結果

インデックスサイズ生成時間
CJK5.9GB35分
Kuromoji(normal)3.1GB76分
Kuromoji(search)3.1GB83分
Kuromoji(extended)2.1GB79分

ちなみに、日本語版Wikipedia全記事のテキスト部分のサイズをカウントしてみたところ、約1.4GBでした。

まとめ

Solrで日本語版Wikipedia全記事のインデックスを作成しました。2-gramのインデックスサイズは形態素解析インデックスの約2倍になりました。

Kuromoji(extended)は未知語を1-gramに分割する分他のモードよりもインデックスサイズが大きくなると予想していたのですが、逆に30%強も小さくなりました。ここはもうちょっと調べてみる必要がありそうです。


Apache Solr を Eclipse でリモートでデバッグ

はじめに

先日、久しぶりに全文検索エンジン Apache Solr に触れる機会がありました。
現時点の最新版は 7.5 です(この記事を書いている間に 7.6 がリリースされました)。以前に扱ったことのある 5.5 からはかなり色々なところが変わっているようです。

Solr は OSS なので変更点の詳細を追いかけたければソースを読めばいいのですが、Solr くらいの規模のソフトウェアとなるとソースを読むだけでは取っ掛かりが掴みにくいことも有ります。たとえば、この factory オブジェクトが生成したのはどの具象クラスなんだ、とか、この if 文の分岐はどっちが使われるんだ、とか。そういう場合にはデバッガが役に立ちます。
今回は以下のような構成でデバッガを動かすための手順をまとめました。

  • Solr 7.5 バイナリパッケージ(稼働用)
  • Solr 7.5 ソースパッケージ(デバッガ参照用)
  • Eclipse IDE for Java Developers Version 2018-09

Solr のインストール

  1. 公式サイトからsolr-7.5.0.tgzをダウンロード。
  2. 展開
  3. $ tar zxf solr-7.5.0.tgz
    $ cd solr-7.5.0
    
  4. サンプル設定で起動
    $ bin/solr -e cloud
    (略)
    To begin, how many Solr nodes would you like to run in your local cluster? (specify 1-4 nodes) [2]: 1
    (略)
    Please enter the port for node1 [8983]:
    (略)
    Please provide a name for your new collection: [gettingstarted] 
    test
    (略)
    How many shards would you like to split test into? [2]
    1
    (略)
    How many replicas per shard would you like to create? [2] 
    1
    (略)
    Please choose a configuration for the test collection, available options are:
    _default or sample_techproducts_configs [_default] 
    (略)
    
  5. Solr を一旦停止しておく
    $ bin/solr stop -all
    

Solr のソースを Eclipse にインポート

  1. 公式サイトからsolr-7.5.0-src.tgzをダウンロード。
  2. 展開
    $ mkdir solr-src
    $ cd solr-src
    $ tar zxf solr-7.5.0-src.tgz
    $ mv solr-7.5.0 solr-7.5.0-src
    $ cd solr-7.5.0-src
    
  3. Eclipse のプロジェクトとして読み込めるようにビルド
    $ ant eclipse
    
  4. インポート
    1. 「ファイル」→「インポート」→「既存プロジェクトをワークスペースへ」→「次へ」
    2. 「ルート・ディレクトリの選択」で solr-src/solr-7.5.0-src を指定→「完了」

デバッグ開始

  1. Solr スタート
  2. リモートプロセスのデバッグなので java コマンドのオプションを指定して JDWP を利用します。

    $ bin/solr start -c -p 8983 -s example/cloud/node1/solr -a "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=6900"
    

    suspend=y を指定するとデバッガが接続するまで実行を中断してくれます。ただし180以内に起動が完了しないと Solr が起動失敗と判断して自らプロセス終了してしまうので、それまでに以下の手順でデバッガを接続しなければなりません。

  3. デバッガ起動
    1. 「実行」→「デバッグの構成」
    2. プロジェクト solr-7.5.0 指定→ポート 6900 指定→「デバッグ」

動作確認

今回は Solr 6 で追加された ExtractingRequestHandler を試してみます。
このハンドラは PDF などのバイナリファイルからテキストを抽出してインデックスを作成するためのものです。solrconfig.xml では以下のように定義されています。


  <requestHandler name="/update/extract"
                  startup="lazy"
                  class="solr.extraction.ExtractingRequestHandler" >
    <lst name="defaults">
      <str name="lowernames">true</str>
      <str name="fmap.meta">ignored_</str>
      <str name="fmap.content">_text_</str>
    </lst>
  </requestHandler>

RequestHandler のメインの処理は handleRequestBody ですが、これは ExtractingRequestHandler の親クラスである ContentStreamHandlerBase クラスで定義されているので、そちらにブレイクポイントを設定しておきます。

そして PDF ファイルを post コマンドで送信します。

$ bin/post -c test -params "extractOnly=false&wt=json&indent=true" -out yes example/exampledocs/solr-word.pdf

指定しておいた場所でブレイクされます。

あとは普通にデバッガを使っていくだけです。

たとえば ExtractingDocumentLoader のこの行で変数の内容を確認すると、
実行時パラメータとして extractOnly=false だけを指定した状態では parser として AutoDetectParser、parsingHandler として SolrContentHandler が使われることが分かりました。
metadata としてどんな情報が抽出されるのかも良く分かります。

最後に

Solrのリモートデバッグは簡単です。Solr 内部の理解を深めるのに役立てたいと思います。