Elastic Searchにデータベースがあり、自分のWebサイトページのすべてのレコードを取得したいと考えています。 Elastic Searchノードに接続し、レコードを検索して応答を返すBeanを作成しました。私の単純なJavaコードは、検索を行いますが、
SearchResponse response = getClient().prepareSearch(indexName)
.setTypes(typeName)
.setQuery(queryString("\*:*"))
.setExplain(true)
.execute().actionGet();
しかし、Elasticsearchはデフォルトサイズを10に設定し、それに応じて10ヒットしました。私のデータベースには10を超えるレコードがあります。サイズをInteger.MAX_VALUE
に設定すると、検索が非常に遅くなり、これは私が望むものではありません。
応答のサイズを設定せずに、許容可能な時間内に1つのアクションですべてのレコードを取得するにはどうすればよいですか?
public List<Map<String, Object>> getAllDocs(){
int scrollSize = 1000;
List<Map<String,Object>> esData = new ArrayList<Map<String,Object>>();
SearchResponse response = null;
int i = 0;
while( response == null || response.getHits().hits().length != 0){
response = client.prepareSearch(indexName)
.setTypes(typeName)
.setQuery(QueryBuilders.matchAllQuery())
.setSize(scrollSize)
.setFrom(i * scrollSize)
.execute()
.actionGet();
for(SearchHit hit : response.getHits()){
esData.add(hit.getSource());
}
i++;
}
return esData;
}
現在の最高ランクの回答は機能しますが、結果のリスト全体をメモリにロードする必要があります。これにより、大きな結果セットでメモリの問題が発生する可能性があり、いずれの場合も不要です。
Iterator
s上にNiceSearchHit
を実装するJavaクラスを作成しました。これにより、すべての結果を反復処理できます。内部的には、次のようなクエリを発行することでページネーションを処理します。 _from:
_フィールドであり、メモリにのみ保持されます1ページの結果。
使用法:
_// build your query here -- no need for setFrom(int)
SearchRequestBuilder requestBuilder = client.prepareSearch(indexName)
.setTypes(typeName)
.setQuery(QueryBuilders.matchAllQuery())
SearchHitIterator hitIterator = new SearchHitIterator(requestBuilder);
while (hitIterator.hasNext()) {
SearchHit hit = hitIterator.next();
// process your hit
}
_
SearchRequestBuilder
を作成するときは、setFrom(int)
を呼び出す必要がないことに注意してください。これは、SearchHitIterator
によって内部的に行われるためです。ページのサイズ(つまり、ページあたりの検索ヒット数)を指定する場合は、setSize(int)
を呼び出すことができます。それ以外の場合は、ElasticSearchのデフォルト値が使用されます。
SearchHitIterator:
_import Java.util.Iterator;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
public class SearchHitIterator implements Iterator<SearchHit> {
private final SearchRequestBuilder initialRequest;
private int searchHitCounter;
private SearchHit[] currentPageResults;
private int currentResultIndex;
public SearchHitIterator(SearchRequestBuilder initialRequest) {
this.initialRequest = initialRequest;
this.searchHitCounter = 0;
this.currentResultIndex = -1;
}
@Override
public boolean hasNext() {
if (currentPageResults == null || currentResultIndex + 1 >= currentPageResults.length) {
SearchRequestBuilder paginatedRequestBuilder = initialRequest.setFrom(searchHitCounter);
SearchResponse response = paginatedRequestBuilder.execute().actionGet();
currentPageResults = response.getHits().getHits();
if (currentPageResults.length < 1) return false;
currentResultIndex = -1;
}
return true;
}
@Override
public SearchHit next() {
if (!hasNext()) return null;
currentResultIndex++;
searchHitCounter++;
return currentPageResults[currentResultIndex];
}
}
_
実際、そのようなクラスを持つことがどれほど便利であるかを理解すると、ElasticSearchのJavaクライアントが同様のものを提供しないのはなぜかと思います。
スクロールAPIを使用できます。 searchhitイテレータを使用する他の提案もうまく機能しますが、それらのヒットを更新したくない場合に限ります。
import static org.elasticsearch.index.query.QueryBuilders.*;
QueryBuilder qb = termQuery("multi", "test");
SearchResponse scrollResp = client.prepareSearch(test)
.addSort(FieldSortBuilder.DOC_FIELD_NAME, SortOrder.ASC)
.setScroll(new TimeValue(60000))
.setQuery(qb)
.setSize(100).execute().actionGet(); //max of 100 hits will be returned for each scroll
//Scroll until no hits are returned
do {
for (SearchHit hit : scrollResp.getHits().getHits()) {
//Handle the hit...
}
scrollResp = client.prepareSearchScroll(scrollResp.getScrollId()).setScroll(new TimeValue(60000)).execute().actionGet();
} while(scrollResp.getHits().getHits().length != 0); // Zero hits mark the end of the scroll and the while loop.
返される結果の数と、ユーザーが待機する時間および使用可能なサーバーメモリの量とのトレードオフが必要になります。 1,000,000のドキュメントにインデックスを付けた場合、1つのリクエストでそれらすべての結果を取得する現実的な方法はありません。結果は1人のユーザーのものだと思います。負荷がかかった状態でシステムがどのように動作するかを考慮する必要があります。
バージョン6.3.2の場合、以下が機能しました。
public List<Map<String, Object>> getAllDocs(String indexName, String searchType) throws FileNotFoundException, UnsupportedEncodingException{
int scrollSize = 1000;
List<Map<String,Object>> esData = new ArrayList<>();
SearchResponse response = null;
int i=0;
response = client.prepareSearch(indexName)
.setScroll(new TimeValue(60000))
.setTypes(searchType) // The document types to execute the search against. Defaults to be executed against all types.
.setQuery(QueryBuilders.matchAllQuery())
.setSize(scrollSize).get(); //max of 100 hits will be returned for each scroll
//Scroll until no hits are returned
do {
for (SearchHit hit : response.getHits().getHits()) {
++i;
System.out.println (i + " " + hit.getId());
writer.println(i + " " + hit.getId());
}
System.out.println(i);
response = client.prepareSearchScroll(response.getScrollId()).setScroll(new TimeValue(60000)).execute().actionGet();
} while(response.getHits().getHits().length != 0); // Zero hits mark the end of the scroll and the while loop.
return esData;
}
すべてのレコードのエクスポートに主な焦点が当てられている場合、並べ替えはコストのかかる操作であるため、並べ替えを必要としないソリューションを選択することをお勧めします。説明されているように、ElasticsearchCRUDでスキャンとスクロールのアプローチを使用できます ここ 。
すべてをクエリするには、CountRequestBuilderを作成して(CountResponseによって)レコードの総数を取得し、その数を検索リクエストのサイズに戻す必要があります。