From Size 方式分页查询 demo

func TestESQueryDemo(client *elastic.Client) {
        // ES SDK 教程:https://www.yisu.com/zixun/694102.html
	query := elastic.NewBoolQuery().
                Must(elastic.NewMatchQuery("lastname", "smith")).
		Filter(elastic.NewTermQuery("age", "20")).
		Filter(elastic.NewRangeQuery("modify_time").Format("yyyy-MM-dd").Gte("2021-07-22").Lt("2021-07-23"))

	ctx := context.Background()
	index := "info"

	result, err := client.Search(index).Query(query).Size(20).From(0).Do(ctx)
	if err != nil {
        logs.Error("err=%v", err)
	}
	for page, hit := range result.Hits.Hits {
		//var t Employee
		//err := json.Unmarshal(*hit.Source, &t) //另一种取出的方法
		//if err != nil {
		//	fmt.Println("failed")
		//}
		//fmt.Printf("employee name %s:%s\n", t.FirstName, t.LastName)
		logs.Info("page=%v, hit=%v", page, hit)
	}

	logs.Info("result=%v, err=%v", *result.Hits, err)
}

其他 api 使用参考:go语言操作es的方法

Scroll 分页查询 demo

Type: illegal_argument_exception, Reason: Result window is too large, from + size must be less than or equal to: [10000] but was [10001]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting.

当所请求的数据总量大于1w时,可用scroll来代替from+size。

scroll 查询 可以用来对 Elasticsearch 有效地执行大批量的文档查询。游标查询会取某个时间点的快照数据。 查询初始化之后索引上的任何变化会被它忽略。 它通过保存旧的数据文件来实现这个特性,结果就像保留初始化时的索引视图一样。

func TestESQueryDemo(client *elastic.Client) {
   defer logs.Flush()
   query := elastic.NewBoolQuery().
        Must(elastic.NewMatchQuery("lastname", "smith")).
        Filter(elastic.NewTermQuery("age", "20")).
        Filter(elastic.NewRangeQuery("modify_time").Format("yyyy-MM-dd").Gte("2021-07-22").Lt("2021-07-23"))
   
   ctx := context.Background()
   index := "info"

   result, err := client.Scroll(index).
      Query(query).
      Scroll("8s"). // 这个时间只需要能查到第一次时间片就够了,不是查询所有文档的时间
      Size(200).
      Do(ctx)
   scrollID := result.ScrollId
   logs.Info("total = %d, result=%v, err=%v", result.TotalHits(), *result.Hits, err)

   for {
      result, err = client.Scroll("2s").ScrollId(scrollID).Do(ctx) // 每次查询都会使用这个时间续期
      logs.Info("total = %d, result=%v, err=%v", result.TotalHits(), *result.Hits, err)
      if len(result.Hits.Hits) <= 0 {
         break
      }
      logs.Info("scrollID = %v", result.ScrollId)
      time.Sleep(1 * time.Second)
   }

   // 清除游标
   _, err = client.ClearScroll().ScrollId(result.ScrollId).Do(ctx)
}

关于 Scroll 查询返回 返回 EOF 错误

使用 Scroll 来分页查询,如果首次查询时发现无有效记录,即首次查询结果为空时,那么会返回 EOF 错误,而不是空的结果列表。此时,需要我们做一个判断,究竟是 EOF 错误则认为是返回结果为空,否则认为是发生了错误

if err == io.EOF {
  fmt.Println("the error equal io.EOF")
}
if err.Error() == "EOF" {
  fmt.Println("the error.Error() equal io.EOF")
}
fmt.Println(err) // 查询错误,而非数据为空

关于 Scroll 时间参数的说明

启用游标查询可以通过在查询的时候设置参数 scroll 的值为我们期望的游标查询的过期时间。

游标查询的过期时间会在每次做查询的时候刷新,所以这个时间只需要足够处理当前批的结果就可以了,而不是处理查询结果的所有文档的所需时间。

这个过期时间的参数很重要,因为保持这个游标查询窗口需要消耗资源,所以我们期望如果不再需要维护这种资源就该早点儿释放掉。

设置这个超时能够让 Elasticsearch 在稍后空闲的时候自动释放这部分资源。

参考:es scroll 时间_游标查询 Scroll | Elasticsearch: 权威指南 | Elastic、Elasticsearch的滚动查询---Scroll,解决ES每次最多查一万笔数据的问题

两种分页查询方式的对比

ES对于from+size的个数是有限制的,二者之和不能超过1w。当所请求的数据总量大于1w时,可用scroll来代替from+size。

ES的搜索是分2个阶段进行的,即Query阶段和Fetch阶段。 Query阶段比较轻量级,通过查询倒排索引,获取满足查询结果的文档ID列表。 而Fetch阶段比较重,需要将每个shard的结果取回,在协调结点进行全局排序。 通过From+size这种方式分批获取数据的时候,随着from加大,需要全局排序并丢弃的结果数量随之上升,性能越来越差。

而Scroll查询,先做轻量级的Query阶段以后,免去了繁重的全局排序过程。 它只是将查询结果集,也就是doc id列表保留在一个上下文里, 之后每次分批取回的时候,只需根据设置的size,在每个shard内部按照一定顺序(默认doc_id续), 取回这个size数量的文档即可。

由此也可以看出scroll不适合支持那种实时的和用户交互的前端分页工作,其主要用途用于从ES集群分批拉取大量结果集的情况,一般都是offline的应用场景。 比如需要将非常大的结果集拉取出来,存放到其他系统处理,或者需要做大索引的reindex等等。 不要把 scroll 用于实时请求,它主要用于大数据量的场景。例如:将一个索引的内容索引到另一个不同配置的新索引中。