どうもコッピーです.
初ブログで言ったとおり,今回の目標はSolrを立てて,Slackの投稿データを全文検索できるようにすることです.
基本的にはこちらの本を読みつつやっています.どうでもいいですが,この本の著者の半分がメルカリで働いているらしいですね.
前提条件
- Solrは公式のDockerイメージを使用してDocker環境に構築する
- 今回,SolrにSlackの投稿データを投げる部分はRubyで書きました
以下,簡単に理由を記載しています.
まず,あまりローカル環境を汚したくなかった(Java系は色々インストールとかめんどくさい)ので,上記の書籍ではローカル環境でやっているところをDockerで動かしています.
また,Rubyを選んだのは自分が書き慣れていたからです.それだけです.とはいえあまりAPI周りをRubyで書いたことはなかったので地味に時間を取られましたが...
環境
- OS:
macOS Mojave 10.14.4
- Docker:
18.09.2
- docker-compose:
1.23.2
- Ruby:
2.5.5p157
- Solr:
8.1.1
各種環境は以上のようになっています.最新環境との違いはあまり無いはずなので,以下環境構築手順は最新環境の構築を前提として記述しています.
各種環境構築手順
以下,上記環境構築手順になります.
- Docker
macの場合,Docker公式から落としてくる方法と,brew
で入れる方法の2種類の方法で入れることが出来ます.
おそらくどちらで入れても問題ないと思います.ここでは,簡単なbrew
を使用する方法で入れたいと思います.
$ brew cask install docker
はい,これだけです.簡単ですね.ちなみにこれでdocker-compose
についても自動で入るはずなので,以下コマンドでバージョン確認をしてみてください.このように出力されていれば問題ないと思います.
$ docker version Client: Docker Engine - Community Version: 18.09.2 API version: 1.39 Go version: go1.10.8 Git commit: 6247962 Built: Sun Feb 10 04:12:39 2019 OS/Arch: darwin/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 18.09.2 API version: 1.39 (minimum version 1.12) Go version: go1.10.6 Git commit: 6247962 Built: Sun Feb 10 04:13:06 2019 OS/Arch: linux/amd64 Experimental: false $ docker-compose -v docker-compose version 1.23.2, build 1110ad01
RubyについてはデフォルトでMacに入っていますが,デフォルトのRubyはクソなので今回はrbenv
を使用して入れていきます.ここで,あくまで僕自身がRubyで書きたかったからRubyを使用しただけなので,各自得意な言語,使いたい言語があればそちらでやっていただければ問題ありません.その場合はここは飛ばしていただいて構いません.
rbenv
自体はgit
かbrew
で入れることになります.ここではbrew
を使用して入れていきます.また,rbenv
は裏でruby-build
を使用するため,ここで一緒に入れてしまいます.
$ brew install rbenv ruby-build
rbenv
が入ったら,パスの追加をします.自分はzshを使っているため,zshrc
に下記のようなコードを追加します.bash
orfish
の方はそれぞれにあった場所に追加してください.
export PATH=/usr/local/bin/bin:$PATH export PATH=$HOME/.rbenv/bin:$PATH eval "$(rbenv init - zsh)"
これが追加できたらsource ~/.zshrc
で読み込んでください.
これでrbenv
でruby
をインストールする準備が整いました.早速入れていきたいと思います.今回は2.6.0
を入れたいと思います.
$ rbenv install --list $ rbenv install 2.6.0 $ rbenv global 2.6.0 $ rbenv rehash
これでインストールが完了したはずなので,最後にrubyのバージョンを確認してあげましょう.
$ ruby -v ruby 2.5.5p157 (2019-03-15 revision 67260) [x86_64-darwin18]
自分の環境では2.5.5
なのでこんな感じですが,何かしらバージョンがこんな感じで出力されていれば問題ありません.
長くなりましたが,アンド結構雑ではありますが,環境構築については以上です.Solrについてはこのあと構築していきます.
Solrのコア用ディレクトリを作成
まず,コアと呼ばれる,Solrの検索集合を用意します.これはRDBでいうスキーマのようなもので,コアごとにスキーマ定義やクエリの設定が出来ます.Solrは1つのインスタンスの中に複数のコアを持つことができ,それぞれにインデックスを保持出来ます.
例えば,複数のサービスで同一プラットフォームの検索システムを使用する場合にサービスごとに検索したいフィールドが異なる・ランキングの算出方法を変えたいということがあると思います.
このとき,それぞれのサービスごとにコアを用意することで,同じ検索システムを使用していても異なる設定を適用することが出来るというわけです.
では,コア用のディレクトリを作成していきます.
$ mkdir data $ sudo chown 8983:8983 data
Solrコンテナの構築
まず,docker-compose.yaml
を用意します.
中身はひとまずこのように書いてください.
version: '2' services: solr: image: solr:latest container_name: 'solr_test' ports: - "8983:8983" volumes: - ./data:/var/solr/data entrypoint: - docker-entrypoint.sh - solr-precreate - slackCore restart: always
これはほとんどSolr公式Docker Imageから持ってきています.
今回はdocker-compose.yaml
の中身については触れることはしませんが,簡単に言うとSolrの最新バージョンを使用し,コンテナ起動時にslackCore
というコアを/var/solr/data
配下に作成しています.
/var/solr/data
はSolrがコアを作成するディレクトリで,いつのバージョンかまでは/opt/solr/server/solr/mycores
に作成されていたようです.
あとはDockerを起動するだけです.
$ docker-compose up -d
これが無事成功したら,ブラウザでlocalhost:8983
にアクセスするとSolrのホームを起動することができます.
このような画面が出れば成功です. まだcoreにはデータは何も入っていないので,検索などは出来ません.
スキーマ定義
では,まずはスキーマ定義をしていきます.
スキーマ定義はSolrにどのようなデータを入れるか,どの項目が検索できるか,表示する項目はどれかなどを定義するもので,Solrはスキーマ定義がなければ原則データを入れることも検索することも出来ません.
原則というのは,Solrにはスキーマレスモードが存在し,スキーマ定義をしなくてもインデクシングされたデータからスキーマを推測する機能があるためです.
しかし,今回はスキーマレスモードについては取り扱わないため,スキーマ定義をしていくことになります.
スキーマを定義する上で,今回はSlackAPIからデータを引いてくるため,その中のどのフィールドを使いたいかを考える必要があります.
自分は今回,
- client_msg_id : 投稿ID(一意)
- type : イベントの種類(投稿,リアクションなど)
- ts : イベントの発生時間
- user : イベントを行ったユーザ
- text : 投稿本文
を定義しました.全部定義するのはさすがにダルいので,少しだけピックアップした形になります.詳細についてはSlackAPIの仕様を見た上で,個々人の使用したいフィールドをピックアップしてみてください.
スキーマ定義の流れ
使用するフィールドを決めたので,いよいよスキーマ定義をしていきます.まずは簡単にスキーマ定義の流れを把握して,それから定義していきましょう.
スキーマ定義に必要なのは フィールド と フィールドタイプ の2つです.フィールドは先程決定したclient_msg_id
やtext
のようなもので,検索対象のデータのタイトルや本文などを格納する,Solrへの入れ物となるものです.それぞれのフィールドに対してどのような型(数値,文字列,日付など)であるかを定義したものがフィールドタイプです.
フィールドタイプについては基本的にデフォルトで用意されているもので十分であるため,今回定義することはしません.
スキーマ定義には大きく2つの方法があり,一つ目はschema.xml
を直接編集する方法です.もう一つはSchema APIを使用して定義する方法で,今回は後者のSchema APIを使用して定義する方法を採用しています.
フィールドの定義
では,フィールドの定義をしていきましょう.先程書いたようにAPIを使用して定義していくため,JSONファイルに記述していきます.
定義の方法は次のようにします.
{ "追加or更新or削除を指定するキー": { "name": "フィールドの名前", "type": "フィールドタイプの名前", "オプション": "オプションの値" } }
フィールドの追加,更新,削除の場合で記述が異なります.ここで指定できるキーは下記の通りです.
キー名 | 説明 |
---|---|
add-field | 追加時 |
replace-field | 更新時 |
delete-field | 削除時 |
基本的には登録時にはadd-field
しか使いませんが,ユニークキーの設定のために一部replace-field
も使用します.これについては後ほど説明します.
次に設定項目についてです.書き方からおそらく分かると思いますが,name
とtype
は必須項目です.オプションも含め,設定項目について簡単にまとめました.
項目名 | 値 | 説明 |
---|---|---|
name(必須) | フィールドの名前 | 半角英数字 |
type(必須) | フィールドタイプの名前 | フィールドで使用する型 |
indexed(オプション) | true/false | 検索可能なフィールドであればtrue.デフォルトはtrue |
stored(オプション) | true/false | 検索結果として表示したい場合にはtrue.デフォルトはtrue |
multiValued | true/false | 複数の値を登録する場合にはtrue.デフォルトはfalse |
これ以外にも設定は可能ですが,今回は使用する範囲のみでまとめさせていただいています.もっと詳しく知りたい方は調べてみてください.
今回は下記のように(schemaDefinition.jsonとして)設定しています.
{ "replace-field": { "name": "id", "type": "string", "indexed": "true", "stored": "true", "multiValued": "false" }, "add-field": { "name": "type", "type": "string", "indexed": "false", "stored": "true", "multiValued": "false" }, "add-field": { "name": "ts", "type": "string", "indexed": "false", "stored": "true", "multiValued": "false" }, "add-field": { "name": "user", "type": "string", "indexed": "false", "stored": "true", "multiValued": "false" }, "add-field": { "name": "text", "type": "text_ja", "indexed": "true", "stored": "true", "multiValued": "false" } }
id
については既存フィールドであるため,replace-field
を使用しています.この項目はユニークキーとなるため,検索・表示ともにtrue
とし,multiValued
はfalse
としています.必ず設定しなければいけないということはありませんが,ユニークキーを設定することで,データを取り込んだときに同一データを抜いて差分のみの取り込みが可能となるため今回は設定しています.
また,今回基本的に検索可能なフィールドは投稿本文のみとしています.検索可能なフィールド(日本語データが保存される場合)ではフィールドタイプにtext_ja
を設定します.このフィールドタイプは裏でいい感じに形態素解析をしてくれます.この辺についてはSolr AdminでAnalysisを使ってみると分かりやすいと思います.
jsonファイルの準備も出来たので,APIを使用してフィールド定義を適用します.
$ curl -X POST -H 'Content-type:application/json' --data-binary @schemaDefinition.json http://localhost:8983/solr/slackCore/schema { "responseHeader":{ "status":0, "QTime":1319}}
上のようなレスポンスが返ってこれば成功です.これでフィールドの登録が出来ました.次はいよいよSlackの投稿データを流し込んでいきます.
SlackAPIのトークン取得
Slackの投稿データを流し込もうにも,SlackAPIを使用できるようにしなければいけません.
まずは
ここでSlackアプリを作成します.アプリを作成したら
Add features and functionality -> Permissions -> Select Permission Scopesからchannels:history
を選択します.
これで保存していただければOAuth Access Tokenからトークンを取得出来ます.
また,ここでついでに投稿を取得したいチャンネルのIDについても取得しておきましょう.こちらは特にAPIを叩く必要もなく,Web上でSlackを開いたときにURLの最後の/
以下の文字列がチャンネルIDです.
これで投稿データを流し込む準備が出来ました.あとはデータを流し込むだけですね!
Slack投稿データの流し込み
Slack投稿データについては先程取得したトークンを使用すれば簡単に取ってこれます.今回はRubyでプログラムを作成したのでプログラムの中で
までやっています.ひとまず下にコードを載せておきますので,こちらを参考にしてください
@Update 2019.08.27 一部プログラムにミスがあったので修正
require "slack" require "json" require "net/http" require "uri" require "dotenv" # .envファイルから環境変数を読み込む Dotenv.load Slack.configure do |config| config.token = ENV["TOKEN"] end # これで指定したチャンネルの投稿を取得できる messages = Slack.channels_history(channel: ENV["CHANNEL_ID"])['messages'] # 必要なフィールドのみで新しい配列を生成 messageArray = messages.map do |message| messageHash = {} if message["client_msg_id"] != nil messageHash["id"] = message["client_msg_id"] messageHash["type"] = message["type"] messageHash["ts"] = message["ts"] messageHash["user"] = message["user"] messageHash["text"] = message["text"] else next end messageHash end # ここ以降でSolrへのデータ注入 uri = URI.parse("http://localhost:8983/solr/slackCore/update?commit=true") http = Net::HTTP.new(uri.host, uri.port) req = Net::HTTP::Post.new(uri.request_uri) req["Content-Type"] = "text/json; charset=utf-8" req.body = messageArray.compact.to_json res = http.request(req) puts res.code, res.msg, res.body
コード自体は難しいものでもないのですが,Solrに流し込む上で注意しなければならない点があるため,そこを説明していきたいと思います.
SolrにAPI経由でデータを流し込むとき,流し込むデータ全体を大カッコで囲う必要があります.そこで,プログラムの中で取得した投稿データのうち必要なフィールドのみをハッシュとして整形した上で配列に格納しています.
分かりやすいように例をあげておくと,Solrに流すときにはデータは下のような形になっています.
[ { "id": "1234", "type": "message", "ts": "123456.789", "user": "1234", "text": "テスト" }, {}... ]
また,Slack上ではclient_msg_id
となっているIDをid
として保存しています.これはフィールド定義のときにユニークキーとして使用するためにid
という名前で定義したためです.
あとはこのコードを実行するだけです.コードをgetSlackMessage.rb
という名前で保存して,実行してみましょう.下のように200 OKが返ってこれば成功です.
$ ruby getSlackMessage.rb 200 OK { "responseHeader":{ "status":0, "QTime":376}}
データの検索
さて,これでやることは全て終わりました.Solr Adminの確認をしてみましょう.
Solr Adminで今回作成したコアを選択し,Queryを選んでください.ここで,何も変更を加えずにExecute Query
を押してみてください.
どうですか?投稿データの検索が出来たでしょうか? 何も変更していなければ全てのフィールドに対して全てのワードで検索をするという動作をしているため,登録したドキュメント全ての検索が出来ているはずです.
q
となっている部分をtext:検索したいワード
として再度Execute Query
を押すと検索したいワードでの検索が出来ます.
まとめ
以上,少し長くなってしまいましたが,Slackの投稿データをSolrを使って検索することが出来ました.
自分自身初めてSolrを触ってここまで出来るとかなり面白く,やりごたえもありました.
これを機に少しでも検索に興味を持ってもらえれば嬉しいです. 次回はこのデータを使ったちょっとした応用をしてみたいと思います.
今回使用したRubyのプログラムは下記GitHubに上げておきます. https://github.com/Kryota/slackSearch