タグ: Solr

[Solr] Circuit Breaker の新機能

Solr 9.4 で Circuit Breaker にいろいろと変更が加えられました。以前の記事で取り上げたときとは変わったところも多いです。

  • CPUCircuitBreakerが名前の通りCPU使用率をチェックするようになりました。以前はCPUと言いつつ実際にはロードアベレージをチェックしていました
  • ロードアベレージをチェックする Circuit Breaker は LoadAverageCircuitBreaker になりました
  • 検索リクエストだけでなく更新リクエストにも効くようになりました

これらの変更に伴い設定方法が変更になっています。

CPUCircuitBreaker

クラス名と閾値を指定します。たとえば以下のように指定すると、CPU使用率75%を超えたら発動します。

<circuitBreaker class="org.apache.solr.util.circuitbreaker.CPUCircuitBreaker">
 <double  name="threshold">75</double>
</circuitBreake>

LoadAverageCircuitBreaker

クラス名と閾値を指定します。たとえば以下のように指定すると、ロードアベレージ8.0を超えたら発動します。

<circuitBreaker class="org.apache.solr.util.circuitbreaker.LoadAverageCircuitBreaker">
 <double  name="threshold">8.0</double>
</circuitBreaker>

MemoryCircuitBreaker

クラス名と閾値を指定します。たとえば以下のように指定するとメモリ使用率75%を超えたら発動します。

<circuitBreaker class="org.apache.solr.util.circuitbreaker.MemoryCircuitBreaker">
 <double name="threshold">75</double>
</circuitBreaker>

リクエストタイプの指定

リクエストタイプとして search, update を指定できます。上記の例のようにリクエストタイプをしない場合はデフォルトとして search が指定されたことになります。search, update それぞれに別の閾値を設定することもできます。

以下の例の場合、CPU使用率が80%を超えたら更新リクエストの受付を停止し、95%を超えたら検索リクエストを停止するという設定になります。

<config>
  <circuitBreaker class="solr.CPUCircuitBreaker">
   <double  name="threshold">80</double>
   <arr name="requestTypes">
     <str>update</str>
   </arr>
  </circuitBreaker>

  <circuitBreaker class="solr.CPUCircuitBreaker">
   <double  name="threshold">95</double>
   <arr name="requestTypes">
     <str>query</str>
   </arr>
  </circuitBreaker>
</config>

[Solr] 検索結果をCBORで出力する

Solr 9.3 から Response Writer で CBOR がサポートされました。
CBOR は MessagePack の後継とも言われるオブジェクト・シリアライズ・フォーマットです。データモデルは JSON がベースになっており、シンプルかつコンパクトにデータのやり取りができます。

Solr の検索結果を CBOR で出力するには、パラメータで wt=cbor を指定するだけです。
Solr のチュートリアル用の techproducts データを対象に、同じ検索結果を JSON で出力した場合とでファイルサイズの違いを比べてみます。

$ curl -s 'http://localhost:8983/solr/techproducts/select?indent=true&q.op=OR&q=*%3A*&wt=cbor' --output result_cbor
$ curl -s 'http://localhost:8983/solr/techproducts/select?indent=true&q.op=OR&q=*%3A*&wt=cbor' --output result_cbor
$ ls -l result*
-rw-r--r-- 1 splout splout 2772 10月 28 20:13 result_cbor
-rw-r--r-- 1 splout splout 5166 10月 28 20:13 result_json

CBOR は JSON に比べると半分近くのサイズになっています。

cbor2 モジュールを使って CBOR のバイナリファイルを python で読み込んでみます。

$ python3
>>> import cbor2
>>> f = open('result_cbor', 'rb')
>>> dic = cbor2.loads(f.read())
>>> dic['responseHeader']
{'zkConnected': True, 'status': 0, 'QTime': 12, 'params': {'q': '*:*', 'indent': 'true', 'q.op': 'OR', 'wt': 'cbor'}}
>>> dic['response']['numFound']
52
>>> dic['response']['docs'][0]
{'id': '0812521390', 'cat': ['book'], 'name': 'The Black Company', 'price': 6.989999771118164, 'price_c': '6.99,USD', 'inStock': False, 'author': 'Glen Cook', 'author_s': 'Glen Cook', 'series_t': 'The Chronicles of The Black Company', 'sequence_i': 1, 'genre_s': 'fantasy', '_version_': 1780827206570737664, 'price_c____l_ns': 699, 'name_exact': 'The Black Company'}

cbor2.loads で dictionary として読み込まれ、JSON のとき同様に検索結果の情報を取り出せることが分かります。


Solr のスリムなバイナリ配布

Solr 9.3 から従来と同じ solr-9.3.0.tgz の他に solr-9.3.0-slim.tgz という配布形式が追加されました。追加のモジュール類が不要な利用者向けに、各種のライブラリを除外した配布となっています。

solr-9.3.0.tgz と solr-9.3.0-slim.tgz をそれぞれ展開して含まれているファイルを比較したところ、以下の2つのディレクトリが有るか無いかの違いであることが分かりました。

  • modules
  • prometheus-exporter

prometheus-exporter は Solr と Prometheus を連携させるためのツールです。
modules の下には以下のモジュールが含まれています。

  • analysis-extras
  • analytics
  • clustering
  • extraction
  • gcs-repository
  • hadoop-auth
  • hdfs
  • jaegertracer-configurator
  • jwt-auth
  • langid
  • ltr
  • opentelemetry
  • s3-repository
  • scripting
  • sql

それぞれのアーカイブファイルのサイズと展開後のサイズを比較すると、slim の方はかなり小さくなっていることが分かります。

$ ls -lh solr-9.3.0.tgz solr-9.3.0-slim.tgz 
-rw-r--r-- 1 splout splout  60M  9月 23 12:42 solr-9.3.0-slim.tgz
-rw-r--r-- 1 splout splout 265M  9月 23 12:42 solr-9.3.0.tgz
$ du -sh solr-9.3.0-slim solr-9.3.0
78M	solr-9.3.0-slim
304M	solr-9.3.0

調べていて気が付いたのですが、Solr 9 から Zip ファイルでの配布は無くなったのですね。


[Solr]パラメータセット

はじめに

equest Parameters API により、solrconfig.xmlで定義されたパラメータを上書きしたり、その代わりに使用したりできるパラメータセットを作成できます。

パラメータセットは以下のような場面で使用できます。

  • リクエストパラメータを頻繁に変更したいときに、毎回 solrconfig.xml を編集するのを避ける
  • 様々なリクエストハンドラでパラメータを再利用する
  • リクエスト時にパラメータセットを組み合わせて使用する
  • パラメータを少し変更するためだけにコレクションを再読み込みするのを避ける

パラメータセットを作成する

パラメータセットの作成は以下のように実行します。

curl http://localhost:8983/solr/test/config/params -H 'Content-type:application/json'  -d '{
  "set":{
    "myQueries":{
      "fl":"id,type,area,name",
      "rows":"3",
      "wt":"csv"}}
}'

この例では、検索結果として id, type, area, name の各フィールドをCSV形式で3件出力するためのパラメータを設定して”myQueries”と名付けています。

パラメータセットを使用する

検索リクエストの際にパラメータセットを指定する場合には、useParams でパラメータセットの名前を指定します。

curl 'http://localhost:8983/solr/test/select?useParams=myQueries&q=name%3A%E5%A4%A7%E9%98%AA'
id,type,area,name
19,官公庁,港区,大阪税関
1408,公園・スポーツ,港区,大阪プール
3150,文化・観光,北区,キッズプラザ大阪

useParams を使わずに同じことをするなら以下のようになります。

'http://localhost:8983/solr/test/select?fl=id%2Ctype%2Carea%2Cname&q=name%3A%E5%A4%A7%E9%98%AA&rows=3&wt=csv'

パラメータセットの側を更新すれば、同じリクエストで検索結果を変えることができます。

curl http://localhost:8983/solr/test/config/params -H 'Content-type:application/json'  -d '{
  "set":{
    "myQueries":{
      "fl":"id,type,area,name",
      "rows":"5",
      "wt":"json"}}
}'
curl 'http://localhost:8983/solr/test/select?useParams=myQueries&q=name%3A%E5%A4%A7%E9%98%AA'
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":11,
    "params":{
      "q":"name:大阪",
      "useParams":"myQueries"}},
  "response":{"numFound":1076,"start":0,"maxScore":1.2328514,"numFoundExact":true,"docs":[
      {
        "id":"19",
        "type":"官公庁",
        "area":"港区",
        "name":"大阪税関"},
      {
        "id":"1408",
        "type":"公園・スポーツ",
        "area":"港区",
        "name":"大阪プール"},
      {
        "id":"3150",
        "type":"文化・観光",
        "area":"北区",
        "name":"キッズプラザ大阪"},
      {
        "id":"4185",
        "type":"医療・福祉",
        "area":"東成区",
        "name":"トーヨーケアセンター大阪"},
      {
        "id":"4653",
        "type":"名所・旧跡",
        "area":"北区",
        "name":"大阪天満宮"}]
  }}

パラメータセットの内容を確認する

$ curl http://localhost:8983/solr/test/config/params
{
  "responseHeader":{
    "status":0,
    "QTime":2},
  "response":{
    "znodeVersion":4,
    "params":{"myQueries":{
        "fl":"id,type,area,name",
        "rows":"5",
        "wt":"json",
        "":{"v":2}}}}}

$ curl http://localhost:8983/solr/test/config/params/myQueries
{
  "responseHeader":{
    "status":0,
    "QTime":0},
  "response":{
    "znodeVersion":4,
    "params":{"myQueries":{
        "fl":"id,type,area,name",
        "rows":"5",
        "wt":"json",
        "":{"v":2}}}}}

パラメータセットを削除する

$ curl http://localhost:8983/solr/test/config/params -H 'Content-type:application/json'  -d '{"delete" :[ "myQueries" ]}'
{
  "responseHeader":{
    "status":0,
    "QTime":136},
  "WARNING":"This response format is experimental.  It is likely to change in the future."}

$ curl http://localhost:8983/solr/test/config/params
{
  "responseHeader":{
    "status":0,
    "QTime":0},
  "response":{
    "znodeVersion":5,
    "params":{}}}

[Solr]Jaegarと連携させて分散検索処理を追跡する

はじめに

Solr を Jaeger と連携させて分散検索処理を追跡することができます。
https://solr.apache.org/guide/solr/latest/deployment-guide/distributed-tracing.html

Jaeger は分散トレーシングのツールです。
SolrCloud で複数のノードによる分散インデックス・分散検索を利用するときに、どのノードからどのノードへどんなリクエストが送られたか、どのノードでどの処理にどのくらい時間が掛かったかを追跡でき、これらの情報をパフォーマンスの改善などに利用できます。

Jaegerの起動

Jaegerのバイナリをダウンロードサイトからダウンロードして展開し、以下のコマンドを実行します。

./jaeger-all-in-one --collector.zipkin.host-port=:9411

JaegerTracerConfiguratorの設定

solr.xml に以下を追加します。

  <tracerConfig name="tracerConfig" class="org.apache.solr.jaeger.JaegerTracerConfigurator"/>

それ以外のパラメータは Solr 起動時に環境変数で渡します。

bin/solr start -cloud -p 8983 -Denable.packages=true -Dsolr.modules=jaegertracer-configurator -DJAEGER_SAMPLER_TYPE=const -DJAEGER_SAMPLER_PARAM=1

プロダクション環境で全クエリを追跡したくはないときは JAEGER_SAMPLER_TYPE に probabilistic などを指定します。今回は動作確認なので const を指定して全部を対象としています。
JAEGER_SAMPLER_PARAM=1を指定しないとサンプリングが実行されません。

クエリの実行と追跡結果の表示

この実験のために2レプリカずつの2シャードのコレクションを作成しました。準備が整ったら Solr Admin UI で何かクエリを実行してから Jaeger UI ( http://localhost:16686/ )にアクセスします。
以下は追跡結果のタイムライン表示の例です。

2023-03-19 12:37:01.732 INFO  (qtp487416600-120) [c:test s:shard2 r:core_node7 x:test_shard2_replica_n4] o.a.s.c.S.Request webapp=/solr path=/select params={df=_text_&distrib=false&fl=id&fl=score&shards.purpose=16388&start=0&fsv=true&q.op=OR&shard.url=http://localhost:8983/solr/test_shard2_replica_n4/|http://localhost:8983/solr/test_shard2_replica_n5/&rows=10&rid=localhost-6&version=2&q=*:*&omitHeader=false&NOW=1679229421713&isShard=true&wt=javabin&useParams=&_=1678885506788} hits=4562 status=0 QTime=11
2023-03-19 12:37:01.733 INFO  (qtp487416600-15) [c:test s:shard1 r:core_node6 x:test_shard1_replica_n2] o.a.s.c.S.Request webapp=/solr path=/select params={df=_text_&distrib=false&fl=id&fl=score&shards.purpose=16388&start=0&fsv=true&q.op=OR&shard.url=http://localhost:8983/solr/test_shard1_replica_n2/|http://localhost:8983/solr/test_shard1_replica_n1/&rows=10&rid=localhost-6&version=2&q=*:*&omitHeader=false&NOW=1679229421713&isShard=true&wt=javabin&useParams=&_=1678885506788} hits=4676 status=0 QTime=8
2023-03-19 12:37:01.753 INFO  (qtp487416600-120) [c:test s:shard1 r:core_node6 x:test_shard1_replica_n2] o.a.s.c.S.Request webapp=/solr path=/select params={df=_text_&distrib=false&shards.purpose=64&q.op=OR&shard.url=http://localhost:8983/solr/test_shard1_replica_n2/|http://localhost:8983/solr/test_shard1_replica_n1/&rows=10&rid=localhost-6&version=2&q=*:*&omitHeader=false&NOW=1679229421713&ids=22,11,12,24,13,14,15,16,20,10&isShard=true&wt=javabin&useParams=&_=1678885506788} status=0 QTime=1

それぞれのリクエストは上記のログに対応しています。
shard1 と shard2 に id だけを取得するリクエストを並行して投げて、その後、得られた id のリストを統合して結果作成用のリクエストを投げていることが分かります。