こんにちは、馬場です。
Solrとelasticsearchを比較するシリーズ、最終回は類似文書検索機能について比較します。
Solrとelasticsearch、両方のベースとなっているLuceneにはMoreLikeThisという類似文書検索の機能が実装されています。両者とも当然LuceneのMoreLikeThisを利用して類似文書検索機能を提供していますが、API の形式などはかなり異なります。
Solrの場合
Solrでは、類似文書検索のプログラムは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import org.apache.solr.client.solrj.impl.HttpSolrServer import org.apache.solr.client.solrj.SolrQuery import org.apache.solr.common.util.NamedList import org.apache.solr.common.SolrDocument object MoreLikeThis { val url = "http://localhost:8983/solr" val count = 6 /** * id で指定されたドキュメントと似たドキュメントをcountの数だけ取得する * @param id * @return 似た記事のIDのList */ def getRelatedDocs(id: Int, count: Int = count): List[String] = { val server = new HttpSolrServer(url) val query = new SolrQuery("id:" + id) query.set("mlt", true) query.set("mlt.fl", "title,body") query.set("mlt.midf", 1) query.set("mlt.mintf", 1) query.set("mlt.count", count) query.set("fl", "id,score,title,body") val response = server.query(query) val doc = response.getResults.get(0) response.getResponse().get("moreLikeThis").asInstanceOf[NamedList[Object]] .getVal(0).asInstanceOf[java.util.List[SolrDocument]] .toList.map(_.get("id").toString)) } } |
elasticsearchの場合
elasticsearchでは類似文書検索のプログラムは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import scala.collection.JavaConversions._ import org.elasticsearch.client.transport.TransportClient import org.elasticsearch.common.transport.InetSocketTransportAddress import org.elasticsearch.common.settings.ImmutableSettings object MoreLikeThis { val count = 6 /** * id で指定されたドキュメントと似たドキュメントをcountの数だけ取得する * @param id * @return 似た記事のIDのList */ def getRelatedDocs(id: String, count: Int = count): List[String] = { val settings = ImmutableSettings.settingsBuilder() .put("client.transport.sniff", false).build() val client = new TransportClient(settings) .addTransportAddress(new InetSocketTransportAddress("localhost", 9300)) val response=client.prepareMoreLikeThis("test", "docs", id.toInt) .setField("title", "body") .setSearchTypes("docs").execute().actionGet() response.getHits.getHits.take(count).map (_.id().toString).toList } } |
Solrと同じようにmidf/mintf の設定を1にすることはできるのですが、その場合結果が返ってきません。
1 2 3 4 5 6 |
// この設定だと検索結果は0件になる val response=client.prepareMoreLikeThis("test", "docs", id.toInt) .setMinDocFreq(1) .setMinTermFreq(1) .setField("title", "body") .setSearchTypes("docs").execute().actionGet() |
類似文書検索は、文書のIDを指定する方法だけではなく、文書のタイトルや本文そのものを指定する方法もあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import scala.collection.JavaConversions._ import org.elasticsearch.client.transport.TransportClient import org.elasticsearch.common.transport.InetSocketTransportAddress import org.elasticsearch.common.settings.ImmutableSettings import org.elasticsearch.index.query.{FilterBuilders, QueryBuilders} object MoreLikeThis { val count = 6 def getRelatedDocs(id: String, title:String, body:String, count: Int = count): List[String] = { val settings = ImmutableSettings.settingsBuilder() .put("client.transport.sniff", false).build() val client = new TransportClient(settings) .addTransportAddress(new InetSocketTransportAddress("localhost", 9300)) val query = QueryBuilders.moreLikeThisQuery("title", "body") .likeText(title + "\n" + body) val response = client.prepareSearch("test").setQuery(query) .setFilter(FilterBuilders.notFilter(FilterBuilders.idsFilter("docs").addIds(id))) //検索結果からidのドキュメントを除外する .setFrom(0).setSize(count).setExplain(true).execute().actionGet response.getHits.getHits.take(count).map (_.id().toString).toList } } |
下記のプログラムの結果の方が、Solrの検索結果と似ています(が、完全に一致しません...)。内部的に何か違うのかもしれません。
まとめ
検索API は、Solr が Map を構築して渡すのに対して、elasticsearchはメソッドをつなげて行く形なので書いていて心地よいです。ただ、ここでもelasticsearch のドキュメントのIDを指定して類似文書検索をする方法はマニュアルには書いてなく、APIドキュメントから「発見」したものですので、elasticsearchはまだまだ発展中だと感じました。
3回にわたって、Solr と elasticsearchの比較を行いました。実際にどちらを採用するか決定する場合、運用やパフォーマンスも要因となると思いますが、この記事がすこしでもみなさんの参考になればと思います。