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 のファイルツリー上で実ファイルと全く同じ内容が実行されることを確認できました。

今更ながらLaravelのバージョンを5.1から5.5にアップグレード

最初は5.1から5.5に一気にあげようかと思いましたが、順次あげていったほうが最終的には早く終わりそうだったので、Laravelのアップグレード手順と他の方の記事を参考にしながらアップグレードを行いました。
せっかくなのと覚書を兼用してつまづいた部分をいくつか紹介します。

5.1 > 5.2

composer updateでエラー

Illuminate\Foundation\ComposerScripts::postUpdate
php artisan optimize
PHP Fatal error: Uncaught ReflectionException: Class log does not exist in vendor/laravel/framework/src/Illuminate/Container/Container.php:734
Stack trace:

原因は.envに定義している配列の書き方がまずかった模様

ALLOW_IPS=[“192.168.111.111”, “192.168.111.123”]

こちらスペースを削除したら問題なく動作しました。

5.2 > 5.3

bladeを拡張していた部分でエラー

(‘aaa’, $bbb->ccc)

これまでblade拡張のdirectiveにて上記のようにパラメータが来ていたが、5.3になってからは下記のように外側の括弧がなくなりマッチせずエラーに

‘aaa’, $bbb->ccc

こちらは括弧がない文字列でマッチ条件を変更

5.3 > 5.4

・使用していたSlackのライブラリが使えなくなった > 別のライブラリに変更
・関数名がリネームされていた > forceSchemaをforceSchemeに書き換え

5.4 > 5.5

・Filesystemのfilesメソッドの挙動がSplFileInfoを返すようになったので、実行前にis_dirで確認を追加しました。
・Requestのhas,onlyメソッドの挙動が少し変わっていたので適宜書き換え

動作確認やアップグレード中に不具合修正が入ったり等で少し時間がかかりましたが、基本的にはアップグレード手順書通り行えばそこまで問題はなさそうです。

ギターを買ってPCと接続してみました。オーディオインターフェイスのすすめ

こんにちは。最近、中古の安っすいギターを購入したLQヒロシです。

20代の頃は少しギターをかじっていた私。

諸事情により愛機を手放して以来全く楽器を手にすることはなかったのですが、GWにゲームで知り合ったフレさんとオフ会をして、お酒の席でアツい音楽談義をしたら無性にギターを触りたくなって衝動買いをしてしまいましたwwww

購入したのはこちらのエピフォン Dotというセミアコースティックギターです。
安物なのにこの美しさwwww家にいるといつも眺めております(^o^)

新品なら販売価格4万7000円ぐらいのものが2万7000円で売っていました。

セミアコースティックギターなのでアンプがなくてもそれなりに音はでるのですが、やはり音を歪ませて爆音でザクザクギュンギュン言わせたくなるのが人情?

私がギターを弾いていた当時は、アンプを買ったりエフェクターを買ったりと音を出すまでにコストがかかったものです。しかし最近は、聞くところによると、それがなんとPCでアンプをシュミレートできるというではありませんか!!!


文明の利器よ! PCの発展よ!! ITの力よ、ありがとう(`;ω;´)


というわけで早速購入したギターとPCをつなげる憎いやつがこちら。

Steinberg(スタインバーグ) UR22mkII

オーディオインターフェイスといものらしいです。オーディオインターフェイスとはなんぞや?

オーディオインタフェイスとは、パソコンでオーディオを高音質で再生したり、楽器の録音やコンデンサーマイクの接続に必要となる機器です。パソコンにはオーディオインターフェイスを接続しなくても、もともとオーディオを再生・録音する機能は付いています。しかし、このもともと備わる「オンボード」とも呼ばれるオーディオ機能は、DTMで使用していく上では低品質過ぎるのです。そこで、DTMをパソコンで行っていく上で遅かれ早かれ必要となるのがオーディオインターフェイスです。

オーディオインターフェイスとは?使い方や活用シーンを解説!|moovoo(ムーブー)


とのこと。

こいつが1台あればアンプやエフェクターをシュミレートしながら録音ができたり、ループバックという機能でシュミレートした音をスピーカーから変換して出力することができるのです!!ライブ活動を予定してるわけでもなく、ただ家でシコシコ音を出して遊びたい私にはこれぐらいで十分なのですね。

若干、接続の方法に手間取ったので、現在のPC接続環境を図にしてみましたので、もしどこかにスタインバーグの接続でお困りの方の一助になれば幸いです。

ギターとスタインバーグの接続は通常のシールドで問題なく利用できます

スタインバーグで音をシュミレートするにはDAW(音楽制作)ソフトが必要になりますが、製品を購入するとCUBASE AIという簡易版のライセンスが一緒についてくるのでご心配なく。スタインバーグは2005年にヤマハの子会社になったので、そのあたりでも安心できそうな気がします。

動画を見たらサクッと設定できちゃいます

ソフトの設定がある程度必要ですがそれさえ完了すれば、ソフトを起動してギターをスタインバーグにぶっ刺し、PCの音声主力ラインを切り替えるだけでシュミレートしたギターの音がでます。ゲームなどをしたいときは通常のライン出力に切り替えるだけでおk

やっぱり歪んだギターの音はたまんないぜ(´﹃`)

っということで、最近はゲームしながらゲームのBGMをギターでギュンギュン言わせながらコピーしたりと最強のPC環境を堪能しておりますw

アマゾンなどでお求めやすい値段でお買い求めいただけますよ(^o^)
Steinberg スタインバーグ 2×2 USB2.0 24bit/192kHz オーディオインターフェース UR22mkII

読書、始めましたとおまけ

こんにちわ。
リエです。

最近意識的に紙の本を読むようにしています。
ここ数年は本は電子書籍で読むことが多く、紙の本を買っていませんでした。なので久しぶりに紙の本を購入。紙の本ってやっぱりいいですね。字もずっときれいに書けるようになりたいと思いながらもそのための行動を起こせずにいました。お手紙を書くのが好きなので少しでもきれいな字で書きたいと思いペン字の練習帳を買って練習することにしました。

ということでまずは読んだ本のご紹介。

会話は、とぎれていい ―愛される48のヒント

https://amzn.to/2VajB15
フリーアナウンサーの加藤綾子さんが書かれた本です。
話のプロが書いた本ということで気になって買ってみました。
今までのご経験から導き出したことや心がけていることなど、読みやすいテンポで書かれていて、楽しく学びながら読むことができました。

女性の品格


https://amzn.to/2jhtN4L
最近いただいた本なのですが、2006年に発売されたベストセラー本です。
ついつい使ってしまうあんまりよろしくない言葉。。それは自分の品格を落とす行為なんだと再認識。日頃の心構えなどためになることもたくさん書かれていました。品格ある女性を目指して日々精進します。

ペン字の練習はまずは正しい字をなぞり、自分で書くというものです。子供の頃、こんな感じで字を練習したな〜と思い出しました。
楽しく練習をしたいので、TVを見たり音楽を聞きながらマイペースに練習しています。大体見開き1ページだと15分〜30分で書けるのですが、終わると中々達成感があります( ̄ー ̄)♪

最近は情報はインターネットから収集することも多く、つねにスマートフォンを触っているという感じでした。
でも紙の本を読んだりペン字の練習をするようになり、スマートフォンから少し離れる時間ができたので生活が少し変わったように感じます。
何事もメリハリは必要ですね。
これからもこの習慣は続けたいと思います。

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: パフォーマンス面では不利だが柔軟