【问题标题】:Return all records in one query in Elasticsearch在 Elasticsearch 的一个查询中返回所有记录
【发布时间】:2013-02-13 09:37:41
【问题描述】:

我有一个弹性搜索数据库,想获取我网站页面上的所有记录。我写了一个bean,它连接到弹性搜索节点,搜索记录并返回一些响应。我进行搜索的简单 java 代码是

SearchResponse response = getClient().prepareSearch(indexName)
    .setTypes(typeName)              
    .setQuery(queryString("\*:*"))
    .setExplain(true)
    .execute().actionGet();

但是 Elasticsearch 将默认大小设置为 10,我有 10 次点击作为响应。我的数据库中有 10 多条记录。如果我将大小设置为Integer.MAX_VALUE,我的搜索会变得非常慢,这不是我想要的。

如何在不设置响应大小的情况下在可接受的时间内通过一个操作获取所有记录?

【问题讨论】:

  • 我也有同样的问题。谢谢你的问题
  • 我有 100 个文档。我也在 My Jboss 中将 Integer.MAX_VALUE 设置为 size.Got OutOfMemoryError[Java heap space]。如果我给 1000,那么效果很好。

标签: java api elasticsearch search


【解决方案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;
}

【讨论】:

  • 这可行,但需要将整个结果列表加载到内存中,这对于非常大的结果集是不必要的,甚至是不可能的。更健壮的解决方案是使用迭代器:stackoverflow.com/a/35729505/2091700
【解决方案2】:

当前排名最高的答案有效,但它需要将整个结果列表加载到内存中,这可能会导致大型结果集出现内存问题,并且在任何情况下都是不必要的。

我创建了一个 Java 类,它在 SearchHits 上实现了一个不错的 Iterator,它允许遍历所有结果。在内部,它通过发出包含from: 字段的查询来处理分页,并且它只在内存中保存一页结果

用法:

// 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 客户端不提供类似的东西。

【讨论】:

  • 如果其他人添加或删除了一个文档,我们是否保证我们会遍历所有文档?
  • 我认为使用scrolls 应该更健壮。
  • 这取决于您如何撰写SearchRequestBuilder。例如,如果您按文档 ID 升序对结果进行排序,并且只添加文档 ID 较高的文档,您将遍历所有结果。这个答案是为 ES 1.7 编写的,当时 Scrolls 还不存在。它们确实可能是更好的选择。
  • 使用“from”也限制为 10.000 个结果,如 here 所述。卷轴没有这个问题。
【解决方案3】:

您可以使用滚动 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.

【讨论】:

    【解决方案4】:

    您已经很久没有问这个问题了,我想将我的答案发布给未来的读者。

    如上所述,最好加载具有大小的文档并在索引中有数千个文档时开始。在我的项目中,搜索加载 50 个结果作为默认大小并从零索引开始,如果用户想要加载更多数据,则将加载接下来的 50 个结果。这是我在代码中所做的:

    public List<CourseDto> searchAllCourses(int startDocument) {
    
        final int searchSize = 50;
        final SearchRequest searchRequest = new SearchRequest("course_index");
        final SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    
        if (startDocument != 0) {
            startDocument += searchSize;
        }
    
        searchSourceBuilder.from(startDocument);
        searchSourceBuilder.size(searchSize);
    
        // sort the document
        searchSourceBuilder.sort(new FieldSortBuilder("publishedDate").order(SortOrder.ASC));
        searchRequest.source(searchSourceBuilder);
    
        List<CourseDto> courseList = new ArrayList<>();
    
        try {
            final SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            final SearchHits hits = searchResponse.getHits();
    
            // Do you want to know how many documents (results) are returned? here is you get:
            TotalHits totalHits = hits.getTotalHits();
            long numHits = totalHits.value;
    
            final SearchHit[] searchHits = hits.getHits();
    
            final ObjectMapper mapper = new ObjectMapper();
    
            for (SearchHit hit : searchHits) {
                // convert json object to CourseDto
                courseList.add(mapper.readValue(hit.getSourceAsString(), CourseDto.class));
            }
        } catch (IOException e) {
            logger.error("Cannot search by all mach. " + e);
        }
        return courseList;
    }
    

    信息: - Elasticsearch 版本 7.5.0 - Java High Level REST Client 用作客户端。

    我希望,这对某人有用。

    【讨论】:

      【解决方案5】:

      您必须权衡返回结果的数量与您希望用户等待的时间以及可用服务器内存的数量。如果您已为 1,000,000 个文档编制索引,则没有一种现实的方法可以在一个请求中检索所有这些结果。我假设您的结果是针对一位用户的。您必须考虑系统在负载下的表现。

      【讨论】:

        【解决方案6】:

        要查询全部,您应该构建一个 CountRequestBuilder 以获取记录总数(通过 CountResponse),然后将数字设置回您的搜索请求的大小。

        【讨论】:

          【解决方案7】:

          如果您的主要关注点是导出所有记录,您可能希望寻求不需要任何排序的解决方案,因为排序是一项昂贵的操作。 您可以使用 ElasticsearchCRUD 的扫描和滚动方法,如 here 所述。

          【讨论】:

            【解决方案8】:

            对于 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;
            }
            

            【讨论】:

              【解决方案9】:
              SearchResponse response = restHighLevelClient.search(new SearchRequest("Index_Name"), RequestOptions.DEFAULT);
              SearchHit[] hits = response.getHits().getHits();
              

              【讨论】:

                【解决方案10】:

                1.设置最大尺寸,例如:MAX_INT_VALUE;

                private static final int MAXSIZE=1000000;

                @覆盖 公共列表 getAllSaleCityByCity(int cityId) 抛出异常 {

                    List<EsSaleCity> list=new ArrayList<EsSaleCity>();
                
                    Client client=EsFactory.getClient();
                    SearchResponse response= client.prepareSearch(getIndex(EsSaleCity.class)).setTypes(getType(EsSaleCity.class)).setSize(MAXSIZE)
                            .setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), FilterBuilders.boolFilter()
                                    .must(FilterBuilders.termFilter("cityId", cityId)))).execute().actionGet();
                
                    SearchHits searchHits=response.getHits();
                
                    SearchHit[] hits=searchHits.getHits();
                    for(SearchHit hit:hits){
                        Map<String, Object> resultMap=hit.getSource();
                        EsSaleCity saleCity=setEntity(resultMap, EsSaleCity.class);
                        list.add(saleCity);
                    }
                
                    return list;
                
                }
                

                2.在搜索之前计算ES

                CountResponse countResponse = client.prepareCount(getIndex(EsSaleCity.class)).setTypes(getType(EsSaleCity.class)).setQuery(queryBuilder).execute().actionGet();
                

                int size = (int)countResponse.getCount();//这是你想要的大小;

                那么你就可以了

                SearchResponse response= client.prepareSearch(getIndex(EsSaleCity.class)).setTypes(getType(EsSaleCity.class)).setSize(size);
                

                【讨论】:

                • 这会在验证阶段爆炸,因为MAX_RESULT_WINDOW设置为10000。您需要在prepareSearch中设置滚动值以避免这种情况。也许将 prepareSearchScroll 与响应对象中的滚动 ID 一起使用会是一个更好的解决方案。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2021-10-31
                • 2019-03-29
                • 1970-01-01
                • 2011-09-11
                • 2014-06-19
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多