ようへいの日々精進XP

よかろうもん

Apache Solr をちょっと使ったメモ

前回の記事で

Lucene をちょっと触る機会を得たので、Lucene を生で触るよりも Solr を試してみることにしたのでメモ。

inokara.hateblo.jp

ホントにメモ。そして「Solr とは」とかには触れない。

メモ

環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G17023

$ java -version
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

$ ./bin/solr -version
5.5.5

Solr を起動

こちら から zip ファイルを取得して適当なディレクトリに展開して以下のように Solr を起動する。

./bin/solr start

以下のように出力される。

$ ./bin/solr start
Waiting up to 30 seconds to see Solr running on port 8983 [|]
Started Solr server on port 8983 (pid=51921). Happy searching!

ブラウザで http://localhost:8983 にアクセスすると下図のように表示される。

f:id:inokara:20171105233645p:plain

Core の作成

Solr では Core と呼ばれる RDB 言うところのスキーマを作成する必要がある。Core 毎にスキーマ定義やクエリ設定を行うことが出来る。

Core は以下のように実行して作成する。

bin/solr create -c collection1

以下のように出力される。

$ bin/solr create -c collection1

Copying configuration to new core instance directory:
/Path/To/solr-5.5.5/server/solr/collection1

Creating new core 'collection1' using command:
http://localhost:8983/solr/admin/cores?action=CREATE&name=collection1&instanceDir=collection1

{
  "responseHeader":{
    "status":0,
    "QTime":2088},
  "core":"collection1"}

ドキュメントの登録

Core にドキュメントを登録する場合、XMLJSON そして CSV で登録することが出来るが、今回は以下のように JSON で登録する。

curl -X POST -H 'Content-Type: application/json' \
'http://localhost:8983/solr/collection1/update?wt=json&indent=on' --data-binary '
[
  {
    "id": "1",
    "title": "Doc 1"
  },
  {
    "id": "2",
    "title": "Doc 2"
  },
  {
    "id": "3",
    "title": "Doc 3"
  },
  {
    "id": "4",
    "title": "Doc 4"
  },
  {
    "id": "5",
    "title": "Doc 5"
  },
  {
    "id": "6",
    "title": "Doc 6"
  },
  {
    "id": "7",
    "title": "Doc 7"
  },
  {
    "id": "8",
    "title": "Doc 8"
  },
  {
    "id": "9",
    "title": "Doc 9"
  },
  {
    "id": "10",
    "title": "Doc 10"
  },
  {
    "id": "11",
    "title": "Doc 11"
  }
]'

以下のように出力される。

$ curl -X POST -H 'Content-Type: application/json' \
'http://localhost:8983/solr/collection1/update?wt=json&indent=on' --data-binary '
[
  {
    "id": "1",
    "title": "Doc 1"
  },

... (略) ...

{
  "responseHeader":{
    "status":0,
    "QTime":222}}

一旦、検索

curl -X GET 'http://localhost:8983/solr/collection1/select?q=*:*&wt=json&indent=on'

以下のように出力されるだけ。

$ curl -X GET 'http://localhost:8983/solr/collection1/select?q=*:*&wt=json&indent=on'
{
  "responseHeader":{
    "status":0,
    "QTime":19,
    "params":{
      "q":"*:*",
      "indent":"on",
      "wt":"json"}},
  "response":{"numFound":0,"start":0,"docs":[]
  }}

Solr では登録したドキュメントを検索出来るようにするには commit 処理が必要になる。

Commit

ということで Commit する。

curl -X GET 'http://localhost:8983/solr/collection1/update?commit=true&wt=json&indent=on'

以下のように出力される。

$ curl -X GET 'http://localhost:8983/solr/collection1/update?commit=true&wt=json&indent=on'
{
  "responseHeader":{
    "status":0,
    "QTime":5}}

改めて検索

curl -X GET 'http://localhost:8983/solr/collection1/select?q=*:*&wt=json&indent=on'

以下のように出力される。

$ curl -X GET 'http://localhost:8983/solr/collection1/select?q=*:*&wt=json&indent=on'
{
  "responseHeader":{
    "status":0,
    "QTime":3,
    "params":{
      "q":"*:*",
      "indent":"on",
      "wt":"json"}},
  "response":{"numFound":11,"start":0,"docs":[
      {
        "id":"1",
        "title":["Doc 1"],
        "_version_":1583237681930829824},
 
... (略) ...

      {
        "id":"10",
        "title":["Doc 10"],
        "_version_":1583237682111184896}]
  }}

削除

以下のように ID を指定してドキュメントを削除する。

curl -X POST -H 'Content-Type: application/json' \
'http://localhost:8983/solr/collection1/update' --data-binary '
{
  "delete": "10"
}'

以下のように出力さる。

$ curl -X POST -H 'Content-Type: application/json' \
> 'http://localhost:8983/solr/collection1/update' --data-binary '
> {
>   "delete": "10"
}'> }'
{"responseHeader":{"status":0,"QTime":6}}

削除の場合にも Commit を行う。

Commit

curl -X GET 'http://localhost:8983/solr/collection1/update?commit=true&wt=json&indent=on'

以下のように出力される。

$ curl -X GET 'http://localhost:8983/solr/collection1/update?commit=true&wt=json&indent=on'
{
  "responseHeader":{
    "status":0,
    "QTime":4}}

改めて検索

curl -X GET 'http://localhost:8983/solr/collection1/select?q=*:*&wt=json&indent=on'

以下のように ID: 10 のドキュメントは削除され、検索結果に出力されない。

$ curl -X GET 'http://localhost:8983/solr/collection1/select?q=*:*&wt=json&indent=on'
{
  "responseHeader":{
    "status":0,
    "QTime":6,
    "params":{
      "q":"*:*",
      "indent":"on",
      "wt":"json"}},
  "response":{"numFound":10,"start":0,"docs":[
      {
        "id":"1",
        "title":["Doc 1"],
        "_version_":1583237681930829824},
      {

... () ...

      {
        "id":"9",
        "title":["Doc 9"],
        "_version_":1583237682110136320},
      {
        "id":"11",
        "title":["Doc 11"],
        "_version_":1583237682111184897}]
  }}

但し、ここでの「削除」はマージ処理が行われていないので、完全な削除にはなっていないので、Solr ダッシュボード → Core → Dashboard では以下のように出力されている。

f:id:inokara:20171106073640p:plain

Deleted Document は 1 にカウントアップされていて、Optimized が実施可能な状態になっている。

最適化(マージ処理)

ということで、最適化を行う。

curl -X GET 'http://localhost:8983/solr/collection1/update?optimize=true&wt=json&indent=on'

以下のように出力される。

$ curl -X GET 'http://localhost:8983/solr/collection1/update?optimize=true&wt=json&indent=on'
{
  "responseHeader":{
    "status":0,
    "QTime":106}}

以上

メモでした。

Elasticsearch の Deleted Documents についてメモ

はじめに

Elasticsearch と言うか、Elasticsearch や Solr の基盤となっている Lucene の話になると思います。

上記のブログ記事を自分なりに整理してみましたが、内容には誤りや古い情報が含まれている可能性があるので、誤りがあればご指摘頂けると嬉しいです。

Lucene

Lucene とは

  • Java で実装された全文検索エンジン
  • Lucene検索エンジンライブラリとして提供されていて、検索アプリケーションとして Solr が提供されているという理解(Elasticsearch も同様に Lucene を利用した検索アプリケーションという感じなのかな)

Elasticsearch と Lucene

Elasticsearch インデックスと Lucene インデックスについて、こちらの記事の下図が解り易かったので転載させて頂きました。

f:id:inokara:20171105121928p:plain

  • Elasticsearch Index はシャードと呼ばれる物理的な概念で各ノードに分散配置される
  • シャードが Lucene Index となる
  • Lucene Index には最大で 2147483519 個のドキュメントを含めることが出来る
  • Lucene Index はセグメントと呼ばれるファイルに分割されている

Lucene のデモ

Lucene を取得

cd /tmp/
wget http://ftp.jaist.ac.jp/pub/apache/lucene/java/7.1.0/lucene-7.1.0.zip

展開して CLASSPATH を指定

unzip lucene-7.1.0.zip
export CLASSPATH=lucene-7.1.0/core/lucene-core-7.1.0.jar:lucene-7.1.0/queryparser/lucene-queryparser-7.1.0.jar:lucene-7.1.0/analysis/common/lucene-analyzers-common-7.1.0.jar:lucene-7.1.0/demo/lucene-demo-7.1.0.jar

インデックスの作成

java org.apache.lucene.demo.IndexFiles -docs lucene-7.1.0/docs

インデックスの作成が終わると、カレントディレクトリに index というディレクトリが作成されます。

$ tree index
index
├── _0.cfe
├── _0.cfs
├── _0.si
├── _1.cfe
├── _1.cfs
├── _1.si
├── segments_1
└── write.lock

0 directories, 8 files

各ファイルの概要については、以下のドキュメントに記載されています。

ほうほう、これが Lucene のインデックスなんですな...位のレベルの理解に留めておきます。(とても奥深そうなので)

検索してみる

以下のように lucene というキーワードで検索してみると以下のように出力されました。

$ java org.apache.lucene.demo.SearchFiles
Enter query:
lucene
Searching for: lucene
6221 total matching documents
1. lucene-7.1.0/docs/core/allclasses-noframe.html
2. lucene-7.1.0/docs/test-framework/allclasses-noframe.html
3. lucene-7.1.0/docs/analyzers-common/allclasses-noframe.html
4. lucene-7.1.0/docs/core/allclasses-frame.html
5. lucene-7.1.0/docs/analyzers-common/allclasses-frame.html
6. lucene-7.1.0/docs/spatial3d/overview-tree.html
7. lucene-7.1.0/docs/test-framework/allclasses-frame.html
8. lucene-7.1.0/docs/queryparser/allclasses-noframe.html
9. lucene-7.1.0/docs/changes/Changes.html
10. lucene-7.1.0/docs/benchmark/allclasses-noframe.html
Press (n)ext page, (q)uit or enter number to jump to a page.

Lucene のドキュメントから lucene というキーワードが含まれているドキュメントの HTML 一覧が出力されるようです。

再掲・Deleted Documents

なぜ Deleted Documents なのか

以下、こちらのブログについて、個人的な解釈をまとめていきます。

あくまでも個人的な解釈の為、誤り等が散見されると思いますが、その際はコメント等でご指摘頂けると幸いです。

Lucene は削除したドキュメントをどのように処理しているのか

  • 削除又は更新時はセグメントにビットをマークしてドキュメントが削除されたことを記録する(論理削除)
  • 論理削除されたドキュメントは検索時にスキップされる
  • 論理削除したドキュメントの領域はセグメントがマージされるまでは再利用されない
  • 削除されたドキュメントでのみ存在する用語(?)はマージするまでは削除されない(削除されたドキュメントはマージするまでディスク領域を消費する)

Deleted Documents のマージ処理について

  • Lucene のマージポリシーは TieredMergePolicy と呼ばれるポリシー
  • TieredMergePolicy は削除されたドキュメントを再利用を優先する
  • 時間の経過と共により多くの削除を持つセグメントがマージの対象となる
  • index.merge.policy.reclaim_deletes_weight はマージを制御する設定項目
    • どのくらい削除対象をマージするかを指定
    • あまり大きくしすぎると危険らしい

Deleted Documents の検索への影響について

  • Deleted Documents はあくまでも論理削除されただけで、検索時にはこれらをスキップするだけ
  • Deleted Documents が多くなれば検索パフォーマンスに影響を及ぼす
  • ブログ記事によると...
    • 100MB の Lucene インデックスとその Lucene インデックスを 50% 削除したインデックスで QPS を比較
    • 20% 〜 50% 弱のパフォーマンス劣化が見られた

Expunge Deletes について

  • Elasticsearch の Optimize API(Elasticsearch 2.1 以降は Merge API)の only_expunge_deletesLuceneIndexWriter.expungeDeletes メソッドを呼び出す
  • only_expunge_deletes が付与された場合にはセグメント全体の 10 % を超える削除があるセグメントのマージが行われる(という理解)
  • Elasticsearch のドキュメントでは only_expunge_deletes フラグの説明は以下の通り記載されている
    • only_expunge_deletes フラグが立っていると、削除されたセグメントのみマージ処理が行われる
    • デフォルトは false になっている

Expunge Deletes の挙動について、以下の記事がとても参考になりました。

qiita.com

有難うございます。

Time-Based Indices(時間ベースのインデックス?)

  • Elasticsearch では追加したドキュメント毎に生存時間を指定出来る
  • 指定した時間を過ぎるとドキュメントは自動削除されるが、時間の経過と共に削除処理が重くなる
  • セグメント内の全てのドキュメントが削除された場合(全てのドキュメントに削除フラグが立つと)、それらのドキュメントのマージを待つことなく削除されるので、マージ処理と比較すると効果的

以上

まとめ

  • Deleted Documents は Lucene インデックスのセグメントで削除フラグが立っているが、マージ処理が行われていないドキュメントのこと
  • Deleted Documents は 検索時にスキップされるだけで、検索性能に影響を与える(20 〜 50% 程度の検索パフォーマンスの劣化が見られる)
  • Lucene のデフォルトマージポリシーは TieredMergePolicy と呼ばれるマージポリシーが適用される
  • Elasticsearch の Marge API での only_expunge_deletes フラグが立っていると、削除されたセグメントのみマージ処理が行われる

すいません

Google 翻訳しながら読んだので半分も理解出来ていない可能性がありますが、Lucene のレベルまで切り込んで Elasticsearch について考えたことが無かったのでとても良い経験になりました。

再掲・Deleted Document

  • 基本的には Elasticsearch が自動で Merge 処理をしてくれるので、それに任せた方が良いと考えています

今更だけど Thunderbolt ケーブルを使って iMac を MacBook Pro の外付けモニタにしてみた

こんな感じになりました

トリプルディスプレイ

ターゲットディスプレイモード

support.apple.com

必要(だった)なもの

既に MacBook ProiMac が手元で稼働していることが前提。

俺の環境

基本的には以下の 2 台を Thunderbolt ケーブルで繋ぐだけ。

ハマったこと嬉しかったこと

iMac がターゲットディスプレイモードに切り替わらない

原因

対策

以下の記事を参考に Karabiner をインストールして Brightness Adjust を F2 に割り当てた。

qiita.com

有難うございました。

ターゲットディスプレイモードの iMac 側で

Spotify で音楽流すことが出来たりするのが嬉しい。

音量の操作は

接続している MacBook Pro 側で操作出来たりする。凄いなあ。

f:id:inokara:20171104232608p:plain

外付けモニタも iMac もフル HD で 21 インチくらいだから

パッと見で違和感無し。

以上

Apple の純正品だと何にも苦労しないのは素敵だなと思った次第。高いけど。

2017 年 11 月 04 日(土)

ジョギング

  • 香椎浜 x 2 周
  • 左太腿に違和感あり

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

ケケ

奥さんオススメする博多のマッサージ屋さんで全身マッサージを受ける。施術してくれたのは台湾出身のケケさん。ケケさん、無駄な営業トークも無く淡々と台湾仕込みのマッサージでかなり癒やされた。有難う、ケケさん。

Thunderbolt ケーブル

博多のヨドバシで Thunderbolt ケーブルを購入。iMac を外付けモニタで利用する予定。ポイント使ってちょっとだけ安く買うことが出来た。

ついでに iPhone X をちょろっと触ってみたけど、従来の iPhone との差がイマイチ判らなかった。

夕飯

ちょっと早い誕生日を奥さんに祝ってもらった。

焼肉パーリー

42 歳でこんな感じでござる。

2017 年 11 月 03 日(金)

ジョギング

  • 香椎浜 x 2 周
  • ロング浜ダッシュ x 3

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

正三おじさんと釣り

夜釣りでセイゴを上げた。

夜釣りで「セイゴ」(スズキの進化前の名前)を上げた。

ウキを使わない仕掛けを使って、竿先に意識を集中して手に伝わってくる感覚だけで釣るので、仕事の事とか忘れてとにかく釣りに没頭することが出来た。

本当に楽しかった。

誘ってくださった正三おじさん、本当に有難うございました。

2017 年 11 月 02 日(木)

ジョギング

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

夕飯

  • 奥さんが外食ということで、自分も外食
  • 香椎の串かつ屋さん(串かつよりもサイドメニューが旨い)

2017 年 11 月 01 日(水)

ジョギング

  • 香椎浜 x 2 周
  • だいぶん寒くなってきて、走りやすくなってきた感

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

夕飯

  • パスタを作る
  • 量がいつもより少なくなってしまった
  • トマトも少なかった気がする

2017 年 10 月 31 日(火)

ジョギング

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

iMac を外付けモニタに

support.apple.com

今更感が強いが... iMac を他の Mac の外付けモニタとして利用出来る「ターゲットディスプレイモード」という機能があるらしい。Thunderbolt ケーブルで iMac と他の Mac を繋げるだけで OK 牧場とのことなので、早速試してみようと思ったら...以下のような懸念点があった。

  • 既に Thunderbolt ポートは外付け SSD で利用している(この SSDiMac を起動している)
  • Thunderbolt ケーブルが異常に高い

ちなみに、利用中の iMac は mid 2011 なので要件は満たしている。

うおおー

  • もう 10 月が終わってしまった

2017 年 10 月 30 日(月)

ジョギング

  • 休み

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

早朝業務

  • 朝 3 時過ぎから稼働
  • ちょいちょい休憩しつつ、最初の頃は元気だったけど、夜になったら Stop Instance
  • 毎回思うけど、もうイレギュラーな時間の仕事は無理な体になってきているんだよなー

夕飯

  • 何だかんだ言って、今宵も鍋
  • 寒くなってきているので鍋が良い

2017 年 10 月 29 日(日)

ジョギング

  • 香椎浜 x 2 周
  • 砂浜ダッシュ(100m)x 5

日課

  • (腕立て x 30 + 腹筋 x 30) x 3
  • PHP 5 技術者認定[初級]試験問題集
    • 第 4 章 配列の操作

MNP

お家 de 焼肉

  • イオンで国産ステーキ肉が安かったのでお家 de 焼肉
  • お肉はサッと焼いてから千切り玉ねぎの上でじっくり火を通すと柔らかく焼けることを発見
  • 美味しかった