カテゴリー: テクノロジー

Solrのパッケージ管理機能

はじめに

Solr 8.4 からパッケージ管理機能が追加されました。リファレンスによると、ここでいうパッケージは1つまたは複数のプラグインを1つにまとめたものという意味のようです。
Solr におけるパッケージ管理について調べました。

パッケージ管理機能

従来、プラグインを利用するためには
・jar ファイルを特定の場所に置く
・solrconfig.xml の lib ディレクティブで jar ファイルの場所を指定する
という作業が必要でした。

SolrCloud環境では、複数のノードでこの作業を行う必要があります。 jar ファイルのアップデートの際に異なるノードでのバージョン違いが発生するリスクもあります。

パッケージ管理機能により、複数ノードへのプラグインのインストールやアップデートを一括で操作できるようになります。

パッケージ管理を有効にする

デフォルトではパッケージ管理機能は無効になっているので、起動時のオプションを指定して有効にします。

bin/solr -c -Denable.packages=true

リポジトリを追加する

パブリックなSolrパッケージリポジトリというものは存在していないようなので、Solr のソースに含まれていた、以下のサンプルプラグインをローカルのウェブサーバでホストして試してみました。

$ ls solr-8.4.1-src/solr/core/src/test-files/solr/question-answer-repository/
publickey.der  question-answer-request-handler-1.1.jar.tmp
question-answer-request-handler-1.0.jar.tmp  repository.json
$ cp solr-8.4.1-src/solr/core/src/test-files/solr/question-answer-repository/* $DOC_ROOT/solr/repo/

ウェブサーバ上の特定のディレクトリがパッケージリポジトリであるとSolrに認識されるためには repository.json ファイルが必要であるようです。

$ cat solr-8.4.1-src/solr/core/src/test-files/solr/question-answer-repository/repository.json
[
  {
    "name": "question-answer",
    "description": "A natural language question answering plugin",
    "versions": [
      {
        "version": "1.0.0",
        "date": "2019-01-01",
        "artifacts": [
          {
            "url": "question-answer-request-handler-1.0.jar.tmp",
            "sig": "TTzgh5/usbyWg7oZ7lRwz4eQfh1FeXWvv4U85tsVthVz0MRDz9t7SmonDkegZ7OyqeoiQ4I207pifpVW+DRd9Q=="
          }
        ],
        "manifest": {
          "version-constraint": "8 - 9",
          "plugins": [
            {
              "name": "request-handler",
              "setup-command": {
                "path": "/api/collections/${collection}/config",
                "payload": {"add-requesthandler": {"name": "${RH-HANDLER-PATH}", "class": "question-answer:fullstory.QA\
RequestHandler"}},
                "method": "POST"
              },
              "uninstall-command": {
                "path": "/api/collections/${collection}/config",
                "payload": {"delete-requesthandler": "${RH-HANDLER-PATH}"},
                "method": "POST"
              },
              "verify-command": {
                "path": "/api/collections/${collection}/config/requestHandler?componentName=${RH-HANDLER-PATH}&meta=tru\
e",
                "method": "GET",
                "condition": "$['config'].['requestHandler'].['${RH-HANDLER-PATH}'].['_packageinfo_'].['version']",
                "expected": "${package-version}"
              }
            }
          ],
          "parameter-defaults": {
            "RH-HANDLER-PATH": "/mypath"
          }
        }
      },
      {
        "version": "1.1.0",
        "date": "2019-01-01",
        "artifacts": [
          {
            "url": "question-answer-request-handler-1.1.jar.tmp",
            "sig": "LypqlmbJ76AWa5jx0XhjKxO4lrcQAvpSuYddfzcE6TnX0VDPFhrlQHSSX6cZLtvNbQ+74xUMKgsoX1IUnEnKYw=="
          }
        ]
      }
    ]
  }
]

同じパッケージのバージョン違いがrepository.json で定義されています。

Solr稼働中に以下のコマンドでリポジトリを追加します。

$ bin/solr package add-repo question-answer http://localhost/solr/repo/

利用できるパッケージのリスト

インストール対象となるパッケージのリストを表示します。

$ bin/solr package list-available
Available packages:
-----
question-answer                 A natural language question answering plugin
        Version: 1.0.0
        Version: 1.1.0

インストール

$ bin/solr package install question-answer:1.0.0
Posting manifest...
Posting artifacts...
Executing Package API to register this package...
Response: {"responseHeader":{
    "status":0,
    "QTime":42}}
question-answer installed.

インストール済みパッケージの確認

$ bin/solr package list-installed
Installed packages:
-----
{
  "name":"question-answer",
  "version":"1.0.0"}

デプロイ

コレクションを指定してデプロイします。

$ bin/solr package deploy question-answer -collections test
Executing {"add-requesthandler":{"name":"/mypath","class":"question-answer:fullstory.QARequestHandler"}} for path:/api/\
collections/test/config
Execute this command (y/n):
y
Executing http://localhost:8983/api/collections/test/config/requestHandler?componentName=/mypath&meta=true for collecti\
on:test
{
  "responseHeader":{
    "status":0,
    "QTime":0},
  "config":{"requestHandler":{"/mypath":{
        "name":"/mypath",
        "class":"question-answer:fullstory.QARequestHandler",
        "_packageinfo_":{
          "package":"question-answer",
          "version":"1.0.0",
          "files":["/package/question-answer/1.0.0/question-answer-request-handler-1.0.jar.tmp"],
          "manifest":"/package/question-answer/1.0.0/manifest.json",
          "manifestSHA512":"a91ab5a2c5abd53f0f72c256592c2be8b667cecb8226ac054aeed4d28aac9d743311442f2d58539bb83663a19bd\
1efb310aaadfd77bea458f3d475161721a114"}}}}}

Actual: 1.0.0, expected: 1.0.0
Deployed on [test] and verified package: question-answer, version: 1.0.0
Deployment successful

repository.json の記述にしたがって /mypath で QARequestHandler が動くように Config API が呼ばれたことが分かります。
/mypath をリクエストしてみます。

$ curl http://localhost:8983/solr/test/mypath
{
  "responseHeader":{
    "status":0,
    "QTime":0},
  "responseHeader":[
    "version","1.0"]}

残念ながら QARequestHandler のソースを見つけることができず、具体的にどんなパラメータでどんな機能が使えるのかが不明ですが、デプロイできていることは確認できました。

パッケージのアップデート

$ bin/solr package install question-answer:1.1.0
Posting manifest...
Posting artifacts...
Executing Package API to register this package...
Response: {"responseHeader":{
    "status":0,
    "QTime":4}}
question-answer installed.
$ bin/solr package list-installed
Installed packages:
-----
{
  "name":"question-answer",
  "version":"1.0.0"}
{
  "name":"question-answer",
  "version":"1.1.0"}

同じパッケージの2つのバージョンがインストールされたことが分かります。

新しい方のバージョンをデプロイします。

$ bin/solr package deploy question-answer:1.1.0 -collections test
Package {
  "name":"question-answer",
  "version":"1.0.0"} already deployed on test. To update to {
  "name":"question-answer",
  "version":"1.1.0"}, pass --update parameter.
Already Deployed on [test], package: question-answer, version: 1.1.0
Deployment failed

失敗しました。
リファレンスを確認すると、上書きデプロイのときは –update オプションが必要とのことです。

$ bin/solr package deploy question-answer:1.1.0 -collections test --update
Executing http://localhost:8983/api/collections/test/config/requestHandler?componentName=/mypath&meta=true for collecti\
on:test
{
  "responseHeader":{
    "status":0,
    "QTime":1},
  "config":{"requestHandler":{"/mypath":{
        "name":"/mypath",
        "class":"question-answer:fullstory.QARequestHandler",
        "_packageinfo_":{
          "package":"question-answer",
          "version":"1.1.0",
          "files":["/package/question-answer/1.1.0/question-answer-request-handler-1.1.jar.tmp"],
          "manifest":"/package/question-answer/1.1.0/manifest.json",
          "manifestSHA512":"be22126df3fbb84284cdb94469fa0f83753961c066451f0b9107312d076f3f016e60fd279951656ee7ff4b17bc2\
57fbd2ce8950533c7d394b1080122f6461606"}}}}}

Actual: 1.1.0, expected: 1.1.0
Deployed on [test] and verified package: question-answer, version: 1.1.0
Deployment successful

今度は成功しました。
/mypath にリクエストを投げて、バージョン1.1にアップデートされたことを確認します。

$ curl http://localhost:8983/solr/test/mypath
{
  "responseHeader":{
    "status":0,
    "QTime":0},
  "responseHeader":[
    "version","1.1"]}

デプロイ中のパッケージをリストするコマンドもあります。

$ bin/solr package list-deployed -c test
Packages deployed on test:
{
  "name":"question-answer",
  "version":"1.1.0"}

おわりに

Solrのパッケージ管理機能の使い方を見てきました。次回はリポジトリを設定して自分で作ったパッケージを扱えるようにしてみます。


【Solr】Date Mathの仕様をソースコードで確認する

Solr のリファレンスガイドでは Date Math においてどの時間単位がどういう表記で許されているかの仕様はあまり詳しくは説明されておらず、いくつかの具体例が挙げられているのみです。 例に挙がっていない時間の単位が使えるのかはどうかは試してみないと分かりません。

そこで Solr 8.4.1 のソースコードで仕様を確認してみました。

Date Math を扱うクラスを探して org.apache.solr.util.DateMathParser というクラスに行き当たりました。そのソースコードでは、扱える単位は以下のように定義されていました。

    Map units = new HashMap<>(13);
    units.put("YEAR",        ChronoUnit.YEARS);
    units.put("YEARS",       ChronoUnit.YEARS);
    units.put("MONTH",       ChronoUnit.MONTHS);
    units.put("MONTHS",      ChronoUnit.MONTHS);
    units.put("DAY",         ChronoUnit.DAYS);
    units.put("DAYS",        ChronoUnit.DAYS);
    units.put("DATE",        ChronoUnit.DAYS);
    units.put("HOUR",        ChronoUnit.HOURS);
    units.put("HOURS",       ChronoUnit.HOURS);
    units.put("MINUTE",      ChronoUnit.MINUTES);
    units.put("MINUTES",     ChronoUnit.MINUTES);
    units.put("SECOND",      ChronoUnit.SECONDS);
    units.put("SECONDS",     ChronoUnit.SECONDS);
    units.put("MILLI",       ChronoUnit.MILLIS);
    units.put("MILLIS",      ChronoUnit.MILLIS);
    units.put("MILLISECOND", ChronoUnit.MILLIS);
    units.put("MILLISECONDS",ChronoUnit.MILLIS);

まとめると以下のようになります。ミリ秒の異表記が妙に充実していますね。

  • 年 : YEAR,YEARS
  • 月 : MONTH,MONTHS
  • 日 : DAY,DAYS,DATE
  • 時 : HOUR,HOURS
  • 分 : MINUTE,MINUTES
  • 秒 : SECOND,SECONDS
  • ミリ秒 : MILLI,MILLIS,MILLISECOND,MILLISECONDS

週(WEEK)が見当たりませんが、近くに書かれているコメントによると、週については
/WEEK と書いたときの丸め処理(たとえば NOW/DAY と書くとその日の0時0分0秒になる)がややこしいので今のところ採用していないとのことです。


OpenCVでのオーバーヘッドの少ない静止画像撮影方法

はじめに

Amazon Rekognition を使った顔認識システムを検討しています。

  • Raspberry Pi 3 とカメラモジュールと OpenCV を使う
  • USBで接続したキーボードのEnterキーを押したら撮影
  • ASWへのリクエストで1秒程度必要になるので、撮影に要する時間はなるべく短く

シャッターボタン(Enterキー)を押してから画像データを取得するまでの遅延をなるべく小さくするためには、OpenCVでの処理にある程度のコツが必要なことが分かったのでこの記事にまとめてみました。

Raspberry Pi の準備

  1. Raspbeian Buster with desktop をインストール
  2. raspi-config を起動して Interfacing Options → Camera で カメラインタフェースを有効化
  3. apt-get install python-opencv

ダメダメな例

最初にうっかり書いてしまったバージョンです。
raw_input で入力待ちをして、入力があれば VedeoCapture オブジェクトを生成して read で画像を1枚読み込む、という処理を無限ループします。 一応動きますが、毎回 VideoCapture を作っているので遅い(キー入力から画像取得まで1秒弱)です。
さらに、後述の通り2回目以降正しく動作しません。

ダメな例

ループの外で VideoCapture を生成して使い回すようにしました。VideoCapture生成処理の分(0.4秒程度)早くなりました。
ただし、上のダメダメな例でもそうでしたが、2回目以降のシャッターは正しく動作しません。

  1. 顔をカメラに向けた状態で起動
  2. キー入力する
  3. 顔が写った画像が記録される
  4. 顔をフレームから外す
  5. キー入力する
  6. 顔が写った画像が記録される

といった具合です。2回目以降はキー入力された時点の映像がキャプチャされません。

正しいキャプチャの方法

VideoCapture を使うサンプルコードは世に溢れてますが、そのほとんどは動画のすべてのフレームを漏れなく処理するには、のようなパターンで、処理をブロックしておいて何らかのトリガで1枚だけキャプチャをするという今回のような使い方はあまりしないようです。
改めて VideoCapture の仕様を調べてみると、フレームレートに応じた頻度で read を呼び出して VideoCapture 内部のフレームを進めないと、過去のフレームを読み込んでしまうことになるようです。要するに、ダメな例のコードでは read の回数が少なすぎた訳です。

したがって、以下のような動作が必要になります。

  1. 適切なウェイト(50msec程度)を入れつつreadを繰り返し実行
  2. キー入力が発生したら最後に読み込んだ画像をメインの処理に渡す

こういう場合の定石は cv2.imshow して cv2.waitKey でタイムアウト有りのキー入力待ちをすることです。タイムアウトを50msecに設定すれば、キー入力が無ければ50msec毎に cv2.read を実行できます。

もっと動作を軽く

上の定番のやり方の場合、imshowするのでそれなりにCPU負荷が掛かります。カメラに映っている映像を表示しなくても良い用途のためにもっと軽い方法も欲しくなります。

select を使ってタイムアウト付きキー入力関数を自前で用意しました。

おわりに

imshowとwaitKeyを使う版だとほとんど何もしていないときでもCPU使用率が20%強になりますが、動画表示しない版だと3%程度まで抑えることができました。また、プログラムの構成上キー入力の前に画像読み込みが終わっているので、処理の遅延をなるべく小さくという意味でも非常に良いものになっています。


誰でもできるプログラミング

こんにちは。開発担当のマットです。

以前も言ったことがありますが、開発はとても楽しい仕事です。
PCに文字を打ち込むだけで、コンピュータがその指示を従って、面白いものを生み出せます。

でも、一度もプログラムを書いたことがない人は、どこから始まればいいかわからないでしょう。私も長いこと、プログラムの書き方を一切わからなくて、不思議な魔法のようでした。

それで、誰でもすぐに作れるプログラムを紹介したいと思います。

必要な準備

多くのプログラミング・プロジェクトに特殊な言語やコンパイラーのインストールが必要です。今回は、めっちゃ簡単に、ブラウザーとテキスト・エディター(メモ帳など)さえあれば、動くJavaScriptのプログラムを作ろうと思っています。

つまり、特に必要な準備はありません。

始めましょう〜

まずは、テキストエディターを開いて、「Hello」を書きましょう!
終わったら、program.html として保存しましょう!
保存先はどこでもいいです。デスクトップでも問題ありません。

HTMLファイルなので、ダブルクリックすると規定のブラウザーで開かれるでしょう。私の場合、Chromeで「Hello」が表示されます。

これはプログラム?

いや、今やったことは(とても簡単な)ウェブページの作成です。
動的な内容を出力する何かを開発しないと、「プログラム」と呼べないでしょう。

では、それを作りましょう!
現代のブラウザーは JavaScript という言語を解釈する能力を持っています。
Helloの後ろに、Javascriptのコードを書く部分を定義しましょう!これはHTMLタグというものでできます。

書き方は簡単です。
Javascriptのコード部分の開始は <script> で定義する。
Javascriptのコード部分の終了は </script> で定義する。

なお、コード部分にアラートを出す処理を入れましょう!
Javascriptでは、 alert(‘ホゲホゲ’); でできます。 

先程のファイルに以下を書きましょう〜

Hello
<script>
    alert('こんにちは!');
</script>

保存して、ブラウザーをリロードすると、アラートが表示されます!

やった!なんか動き出しましたよ!

基礎の準備ができ、初めてのスクリプトを書きましたので、面白いものを作りましょう!

スプラウティモン

せっかく、スプラウト株式会社のブログなので、スプラウティモンという、モンスター・ゲームを作りましょう!
150匹のモンスターがいて、全部集めることが目標!

150匹のモンスターの名前を決めなければならないことが面倒ので、プログラムで自動にやりましょう。こうやってします。

  1. スプラウティモンの名前のパーツを手動で決める。
  2. 名前のパーツを適当に組み合わせて、名前を作り出す。
  3. その名前をページに出力
  4. それを150回繰り返します。

よっし!目標を設定しました。まずは名前のパーツを手動で決めましょう。
プログラミングで、何かのコレクションを「配列」といいます。
名前のパーツのコレクションが必要ので、まずはpartsという配列を作りましょう。
言語によって、やり方が違いますが、Javascriptの場合は以下です。

Hello
<script>
	var parts = ['ほげ', 'ホゲ'];
</script>

「ほげ」と「ホゲ」で、あまりいい名前作れないので、「ファイヤ」とか「アイス」とかを勝手に決めて、面白さそうなリストを作りましょう。

Hello
<script>
	var parts = ['ファイヤ','アイス','ウィング','ライオン','イシ','デビル','ウルトラ','スーパー','デンキ','パワー','ブリザード','フラワー','ドラゴン','ロック','メガ','ワニ','ドロ','クマ','ヘビ','サメ'];
</script>

これでパーツを定義しましたが、プログラムで3つをランダムに組み合わせて、スプラウティモンの名前を決めましょう。

では、ランダムなパーツを選択する方法はちょっとややこしいですが、以下となります。

parts[Math.floor(Math.random() * parts.length)];

 これを完全に理解しなくてもいいですが、「parts配列の中から、最初から、最後までの中、どれかランダムなものを返す」という意味です。

alertで出してみましょう!

Hello
<script>
	var parts = ['ファイヤ','アイス','ウィング','ライオン','イシ','デビル','ウルトラ','スーパー','デンキ','パワー','ブリザード','フラワー','ドラゴン','ロック','メガ','ワニ','ドロ','クマ','ヘビ','サメ'];
	alert(parts[Math.floor(Math.random() * parts.length)]);
</script>

僕の場合、「メガ」が出ました。リフレッシュする度に、違う名前が出てきます。
ところで、アラートじゃなくて、直接ページに書き出す場合、「document.write」を使えます。それ使いましょう。なお、3つを組み合わせたいので、その行を3回書きましょう。

Hello
<script>
	var parts = ['ファイヤ','アイス','ウィング','ライオン','イシ','デビル','ウルトラ','スーパー','デンキ','パワー','ブリザード','フラワー','ドラゴン','ロック','メガ','ワニ','ドロ','クマ','ヘビ','サメ'];
	document.write(parts[Math.floor(Math.random() * parts.length)]);
	document.write(parts[Math.floor(Math.random() * parts.length)]);
	document.write(parts[Math.floor(Math.random() * parts.length)]);
</script>

すると、

なんか面白いですね。
7才の息子に聞いたら、100点満点で「100点」の名前だそうです!(笑)

どうやって150回?

プログラミング言語でループを書く事ができます。
ループとは、何かの作業を繰り返す時に使うものです。
Javascript では、以下のように書きます。

for (i = 0; i < 150; i++) {
    //150回実行したいコード
}

「Hello」をもっと適切な言葉に変えて、
各スプラウティモンの名前の前に改行(HTMLで「<br />」と書く)も入れましょう!

名前のパーツを150回も定義する必要はないので、ループの外で問題ありません。

すプラウティモンのリスト!
<script>
	var parts = ['ファイヤ','アイス','ウィング','ライオン','イシ','デビル','ウルトラ','スーパー','デンキ','パワー','ブリザード','フラワー','ドラゴン','ロック','メガ','ワニ','ドロ','クマ','ヘビ','サメ'];
	for (i = 0; i < 150; i++) {
		document.write('<br />');
		document.write(parts[Math.floor(Math.random() * parts.length)]);
		document.write(parts[Math.floor(Math.random() * parts.length)]);
		document.write(parts[Math.floor(Math.random() * parts.length)]);
	}
</script>

こうすると・・・・

できました!おめでとうございます!
プログラムを書くことに成功しました。

まとめ

今回、とても簡単なプログラムを紹介しました。
よく考えたら、重複の名前がでてくる可能性もあり、「ドロドロドロ」のような変わった名前も出力されるかもしれません(確率が低くても)。
プログラマはこういうことを考慮して、プログラムを柔軟に作らなければなりません。

とても楽しい仕事で、今後も多くのプログラムを作るチャンスを楽しみにしています。


【Solr】Date Mathによる日付表現

はじめに

Solr では、pdate 等の時刻を扱うフィールドに対するクエリで使用できる Date Math という表現が用意されています。「今日からN日前」のような表現を使いたいときに便利です。 この記事ではDate Mathについてまとめました。

通常の日付表現

Solr では日付のフィールドは以下の形式で表現します。

2020-03-22T14:55:29Z

ミリ秒までの精度があるので、小数点表記も有効です。

2020-03-22T14:55:29.123Z

時刻は秒まできちんと表記しなければエラーになります。

ミリ秒よりも小さい桁が書いてあっても受付けますが、無視されます。

エラーになる例

2020-03-22
2020-03-22T14:55Z

Date Math

Date Mathは、特定の時点からの相対的な時刻を指定しやすくするための表記法です。
起点として良く使用される「今現在」を指定するために NOW という Date Math が用意されています。

Date Mathではまず起点となる日付を書き、それプラスN日という形で表記します。
「2ヶ月後」

NOW+2MONTH

「10日前」

NOW-10DAY

「1年後」

NOW-1YEAR

プラス/マイナスの部分は連続して書くことができます。

NOW+1YEAR+3MONTH+5DAY

起点として通常の日付を指定することもできます。

2020-03-22T10:15:18Z+7DAY

丸め表記

スラッシュ(‘/’)を併用することで、「〜の最初」を表現できます。
たとえば現在時刻が2020年3月22日11時22分33秒だとします。

NOW/HOUR

→2020年3月22日11時00分00秒

NOW/DAY

→2020年3月22日00時00分00秒

Date Math と組み合わせて使えるリクエストパラメータ

NOW

NOW パラメータにより、Date Math で記述した “NOW” を特定の日付に設定することができます。 NOW パラメータには、いわゆる UNIX TIME の Long 値を指定します。
NOW パラメータを指定することで、複数のノードにまたがる分散検索での NOW の値を揃えることができます。テストのときにも役に立ちそうです。

TZ

TZ パラメータを指定することで、デフォルト UTC である Date Math の日付計算を別のタイムゾーンとして扱うことができます。