SolrとMySQLクエリのパフォーマンスの比較

13605 ワード

テストデータ量:10407608
Num Docs: 10407608
プロジェクトの中で1つの最もよく使うクエリーで、ある時間内のデータをクエリーして、SQLクエリーはデータを取得して、30 sぐらい
SELECT * FROM `tf_hotspotdata_copy_test` WHERE collectTime BETWEEN '2014-12-06 00:00:00' AND '2014-12-10 21:31:55';

collectTimeにインデックスを作成すると、同じクエリー、2 s、ずいぶん速くなりました.
Solrインデックス
<!--Index Field for HotSpot-->
<field name="CollectTime" type="tdate" indexed="true" stored="true"/>
<field name="IMSI" type="string" indexed="true" stored="true"/>
<field name="IMEI" type="string" indexed="true" stored="true"/>
<field name="DeviceID" type="string" indexed="true" stored="true"/>

Solrクエリ、同じ条件、72 ms
"status": 0,
    "QTime": 72,
    "params": {
      "indent": "true",
      "q": "CollectTime:[2014-12-06T00:00:00.000Z TO 2014-12-10T21:31:55.000Z]",
      "_": "1434617215202",
      "wt": "json"
    }
クエリーのパフォーマンスが向上したのはわずかではありません.Solrjコードで試してみてください.
SolrQuery params = new SolrQuery();
params.set("q", timeQueryString);
params.set("fq", queryString);
params.set("start", 0); 
params.set("rows", Integer.MAX_VALUE);
params.setFields(retKeys);
QueryResponse response = server.query(params);

Solrjは結果セットをクエリーして取得し、結果セットのサイズは220296で、5つのフィールドを返し、時間は12 s程度です.
どうしてこんなに時間がかかるのですか.上記のQTimeはインデックスクエリの時間に基づいているだけで、solrサービス側からクエリの結果セットを取得する場合は、solrはstoredのフィールド(ディスクIO)を読み取り、Httpを介してローカル(ネットワークIO)に転送する必要があります.この2つは、特にディスクIOに時間がかかります.
時間の比較:
クエリー条件
時間
MySQL(インデックスなし)
30s
MySQL(インデックス付き)
2s
Solrj(selectクエリ)
12s
最適化方法IDの取得に要する時間を見てみましょう.
SQLクエリはidだけを返して、collectTimeに対してインデックスを建てていないで、10 sぐらい
SELECT id FROM `tf_hotspotdata_copy_test` WHERE collectTime BETWEEN '2014-12-06 00:00:00' AND '2014-12-10 21:31:55';

SQLクエリーはidのみを返し、同じクエリー条件でcollectTimeにインデックスを作成し、0.337 s、すぐに.
Solrjクエリはidだけを返して、7 sぐらいで、少し速くなりました.
    id Size: 220296
    Time: 7340
時間の比較:
問合せ条件(IDのみ取得)
時間
MySQL(インデックスなし)
10s
MySQL(インデックス付き)
0.337s
Solrj(selectクエリ)
7s
最適化を続行します.
Solrjが大量の結果セットを取得する速度が遅いという類似の問題について:
http://stackoverflow.com/questions/28181821/solr-performance#
http://grokbase.com/t/lucene/solr-user/11aysnde25/query-time-help
http://lucene.472066.n3.nabble.com/Solrj-performance-bottleneck-td2682797.html
この問題は良い解決方法がなく、基本的な提案はすべてページを分けることですが、私たちは大量のデータを手に入れていくつかの比較分析をしなければなりません.ページを分けるのは意味がありません.
solrのデフォルトのクエリは「/select」request handlerを使用しています.solrの説明を参照してください.solrのデフォルトのクエリは「/select」request handlerを使用しています.
It's possible to export fully sorted result sets using a special rank query parser and response writer  specifically designed to work together to handle scenarios that involve sorting and exporting millions of records. This uses a stream sorting techniquethat begins to send records within milliseconds and continues to stream results until the entire result set has been sorted and exported.
SolrではこのrequestHandlerが定義されています.
<requestHandler name="/export" class="solr.SearchHandler">
  <lst name="invariants">
    <str name="rq">{!xport}</str>
    <str name="wt">xsort</str>
    <str name="distrib">false</str>
  </lst>
  <arr name="components">
    <str>query</str>
  </arr>
</requestHandler>

使用/export需要フィールドdocValueを使用してインデックスを作成するには、次の手順に従います.
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" docValues="true"/>
<field name="CollectTime" type="tdate" indexed="true" stored="true" docValues="true"/>
<field name="IMSI" type="string" indexed="true" stored="true" docValues="true"/>
<field name="IMEI" type="string" indexed="true" stored="true" docValues="true"/>
<field name="DeviceID" type="string" indexed="true" stored="true" docValues="true"/>

docValueを使用するには、Sort用のフィールドが必要です.次のタイプのみサポートされています.
Sort fields must be one of the following types: int,float,long,double,string
docValueでサポートされている戻りフィールド:
Export fields must either be one of the following types: int,float,long,double,string
Solrjを使用してデータをクエリーして取得するには、次の手順に従います.
        SolrQuery params = new SolrQuery();
        params.set("q", timeQueryString);
        params.set("fq", queryString);
        params.set("start", 0);
        params.set("rows", Integer.MAX_VALUE);
        params.set("sort", "id asc");
        params.setHighlight(false);
        params.set("qt", "/export");
        params.setFields(retKeys);
        QueryResponse response = server.query(params);

1つのバグ:
org.apache.solr.client.solrj.impl.HttpSolrClient$RemoteSolrException: Error from server at http://192.8.125.30:8985/solr/hotspot: Expected mime type application/octet-stream but got application/json. 
Solrjは結果セットを正確に解析することができず、下のソースコードを見た.なぜなら、Solr serverが返すContentTypeとSolrj解析時にチェックが一致しないため、SolrjのBinaryResponseParserというCONTEN_TYPEは死ぬ:
public class BinaryResponseParser extends ResponseParser {
    public static final String BINARY_CONTENT_TYPE = "application/octet-stream";

このバグをどのように解決するかはしばらく分からないので、自分でHttpリクエストを書いて結果を取得しましょう.HttpClientで簡単なクライアントリクエストを書いてjson取得データを解析して、テスト速度:
    String url = "http://192.8.125.30:8985/solr/hotspot/export?q=CollectTime%3A[2014-12-06T00%3A00%3A00.000Z+TO+2014-12-10T21%3A31%3A55.000Z]&sort=id+asc&fl=id&wt=json&indent=true";
    long s = System.currentTimeMillis();
    SolrHttpJsonClient client = new SolrHttpJsonClient();
    SolrQueryResult result = client.getQueryResultByGet(url);
    System.out.println("Size: "+result.getResponse().getNumFound());
    long e = System.currentTimeMillis();
    System.out.println("Time: "+(e-s));

同じクエリー条件で220296個の結果セットを取得し、時間は2 s程度であり、このようなクエリー取得データの効率とMySQLがインデックスを確立した後の効果は大きくなく、一時的に受け入れることができる.
Export fieldsはint,float,long,double,stringのいくつかのタイプしかサポートしていないため、クエリーの結果にこれらのタイプのフィールドしか含まれていない場合は、このようにしてデータをクエリーして取得すると、速度が速くなります.
以下に、Solrが「/select」と「/export」を使用する速度の比較を示します.
時間の比較:
クエリー条件
時間
MySQL(インデックスなし)
30s
MySQL(インデックス付き)
2s
Solrj(selectクエリ)
12s
Solrj(exportクエリー)
2s
プロジェクトの中でもしページを分けて検索するならば、select方式を使って、もし一度に大量のクエリーのデータを取得するならばexport方式を使って、ここはMySQLを採用してクエリーのフィールドに対してインデックスを建てていないで、データ量が毎日まだ増加しているため、億級のデータ量に達する時、インデックスもよく問題を解決することができなくて、その上プロジェクトの中でその他のクエリーの需要があります.
各デバイス(deviceID)上のデータの分布を統計すると仮定する別のクエリー要件を見てみましょう.
SQLを使用するには、33 sが必要です.
SELECT deviceID,Count(*) FROM `tf_hotspotdata_copy_test` GROUP BY deviceID;

同じクエリーでは、CollectTimeにインデックスを作成した後、14 sしかかかりません.
SolrのFacetクエリーを見てみると、540 msで、速いのは少しではありません.
SolrQuery query = new SolrQuery();
query.set("q", "*:*");
query.setFacet(true);
query.addFacetField("DeviceID");
QueryResponse response = server.query(query);
FacetField idFacetField = response.getFacetField("DeviceID");
List<Count> idCounts = idFacetField.getValues();
for (Count count : idCounts) {
    System.out.println(count.getName()+": "+count.getCount());
}

時間の比較:
クエリー条件(統計)
時間
MySQL(インデックスなし)
33s
MySQL(インデックス付き)
14s
Solrj(Facetクエリ)
0.54s
あるデバイスがある期間に「時」、「週」、「月」、「年」でデータ統計を行う場合、Solrも便利です.たとえば、次の日でデバイス番号1013のデータを統計します.
    String startTime = "2014-12-06 00:00:00";
    String endTime = "2014-12-16 21:31:55";   
    SolrQuery query = new SolrQuery();
    query.set("q", "DeviceID:1013");
    query.setFacet(true);
    Date start = DateFormatHelper.ToSolrSearchDate(DateFormatHelper.StringToDate(startTime));
    Date end = DateFormatHelper.ToSolrSearchDate(DateFormatHelper.StringToDate(endTime));
    query.addDateRangeFacet("CollectTime", start, end, "+1DAY");
    QueryResponse response = server.query(query);

    List<RangeFacet> dateFacetFields = response.getFacetRanges();
    for (RangeFacet facetField : dateFacetFields{
        List<org.apache.solr.client.solrj.response.RangeFacet.Count> dateCounts= facetField.getCounts();
        for (org.apache.solr.client.solrj.response.RangeFacet.Count count : dateCounts) {
            System.out.println(count.getValue()+": "+count.getCount());
        }
    }

2014-12-06T00:00:00Z: 58
2014-12-07T00:00:00Z: 0
2014-12-08T00:00:00Z: 0
2014-12-09T00:00:00Z: 0
2014-12-10T00:00:00Z: 3707
2014-12-11T00:00:00Z: 8384
2014-12-12T00:00:00Z: 7803
2014-12-13T00:00:00Z: 2469
2014-12-14T00:00:00Z: 142
2014-12-15T00:00:00Z: 34
2014-12-16T00:00:00Z: 0
Time: 662
 
水平分割テーブル:
本システムが収集した大量のデータは「時間」と大きく関係しているため、一部の業務需要は「時間」に基づいて照会することも多く、「時間」フィールドによって表を分割することができ、例えば毎月1枚の表によって分割することができるが、このようにアプリケーション層コードを行うにはより多くのことをしなければならず、表をまたぐクエリーもより多くの仕事を必要とする.テーブルの分割とSolrを用いたインデックスクエリのワークロードを総合的に考慮した後,Solrを採用した.
 
まとめ:MySQLに加えて、Lucene、Solr、ElasticSearchなどの検索エンジンと連携して、全文検索、分類統計などの検索性能を向上させることができます.
 
参照先:
http://wiki.apache.org/solr/