Elasticsearch使用searchAfter+PIT跳过万条记录跳转指定页方案咨询
Elasticsearch 大数据集指定区间拉取优化方案
你当前的性能瓶颈来源于需要逐页遍历到目标页码才能拉取数据,页码越靠后需要发起的请求数越多,耗时越长。可以通过以下几种方案解决:
方案1:低改造成本的跳步查询(最推荐优先落地)
你当前非目标页仅需要获取最后一条的排序值,不需要返回文档内容,完全可以把非目标页的查询size放大到集群可承受的范围(比如10万,只要单请求返回的排序值数组不超过集群负载阈值即可),大幅减少请求次数。
额外优化点:非目标页查询时额外添加.StoredFields(f => f.None())配置,进一步减少非必要返回内容,提升查询速度。
优化后的代码示例:
int numberOfPages = Pagination.GetTotalPages(totalCount, _size); // 非目标页的大步长,可根据集群性能调整,建议10万~50万 const int bigStepSize = 100000; var pitResponse = await _esClient.OpenPointInTimeAsync(content._index, p => p.KeepAlive("2m")); if (pitResponse.IsValid) { IEnumerable<object> lastHit = null; // 计算需要跳转到的偏移量 long targetOffset = requiredPage * _size; long currentOffset = 0; while (currentOffset < targetOffset) { // 剩余偏移大于大步长时用大步长,否则用小步长刚好到目标偏移 int currentSize = (targetOffset - currentOffset) > bigStepSize ? bigStepSize : (int)(targetOffset - currentOffset); var jumpResponse = await _esClient.SearchAsync<ProductionDataItem>(s => s .Index(content._index) .Size(currentSize) .Source(false) .StoredFields(f => f.None()) .Query(query) .PointInTime(pitResponse.Id) .Sort(srt => { if (content.Sort == 1) { srt.Ascending(sortBy); } else { srt.Descending(sortBy); } return srt; }) .SearchAfter(lastHit) ); lastHit = jumpResponse.Hits.Last().Sorts; currentOffset += currentSize; } // 偏移对齐后直接拉取目标页数据 var targetResponse = await _esClient.SearchAsync<ProductionDataItem>(s => s .Index(content._index) .Size(_size) .Source(true) .Query(query) .PointInTime(pitResponse.Id) .Sort(srt => { if (content.Sort == 1) { srt.Ascending(sortBy); } else { srt.Descending(sortBy); } return srt; }) .SearchAfter(lastHit) ); itemsList.AddRange(targetResponse.Documents.ToList()); //Closing PIT await _esClient.ClosePointInTimeAsync(p => p.Id(pitResponse.Id)); }
比如你要拉取10万偏移位置的5000条数据,原方案需要发起10次1万步长的查询,优化后仅需要1次10万步长的跳转查询+1次目标页查询,耗时可降低80%以上。
方案2:预植有序字段实现直接查询(适合长期高频跳页场景)
如果你的业务长期需要随机跳页查询,可在数据写入时新增一个和排序规则完全对齐的全局有序字段(比如自增数值型字段seq_id),查询时直接添加范围条件即可一次拉取目标区间数据:
var response = await _esClient.SearchAsync<ProductionDataItem>(s => s .Index(content._index) .Size(5000) .Query(q => q .Bool(b => b .Must(query) .Filter(f => f.Range(r => r.Field("seq_id").GreaterThanOrEquals(100000).LessThan(105000))) ) ) .Sort(srt => { if (content.Sort == 1) { srt.Ascending(sortBy); } else { srt.Descending(sortBy); } return srt; }) );
该方案性能最高,不需要PIT也不需要逐页跳转,单次请求即可拿到结果,但需要保证新增有序字段的一致性,适合写入逻辑可控的场景。
方案3:调整max_result_window(仅适合极低并发场景,不推荐)
如果你的业务并发量极低,且没有资源做上述优化,可以临时调整索引配置,放大index.max_result_window阈值到你需要的最大值:
PUT /你的索引名/_settings { "index.max_result_window": 200000 }
调整后可以直接用from=100000&size=5000的方式查询,但该方案会让ES在查询时加载from+size条数据到内存再截断,大并发下极易引发集群OOM,仅可作为临时方案使用。
内容的提问来源于stack exchange,提问作者Noa
相关产品推荐
相关产品推荐

