配置项作用 #
search.max_open_scroll_context 配置项限制节点上允许同时打开的最大滚动上下文(Scroll Context)数量。滚动上下文用于维持滚动查询的状态,每个滚动查询都会占用内存和文件句柄等资源。
配置项类型 #
该配置项为动态配置,可以在运行时通过集群设置 API 进行修改。
默认值 #
500
是否必需 #
可选配置项(有默认值)
取值范围 #
0 ~ Integer.MAX_VALUE
工作原理 #
滚动查询通过维护上下文来支持大量数据分页:
┌─────────────────────────────────────────────────────────┐
│ 初始化滚动查询 │
│ GET /my_index/_search?scroll=5m │
└─────────────────────────────────────────────────────────┘
│
▼
创建滚动上下文
(占用内存和资源)
│
▼
┌───────────────┴───────────────┐
│ │
未达限制 达到限制
│ │
▼ ▼
返回 scroll_id 拒绝创建新上下文
│ EsRejectedExecutionException
▼
后续请求使用 scroll_id
继续获取数据
资源占用:
每个滚动上下文占用:
- 内存:存储查询状态和结果集
- 文件句柄:保持索引 reader 打开
- CPU:维护上下文需要额外开销
配置格式 #
# 默认配置
search.max_open_scroll_context: 500
# 增加限制(大批量导出场景)
search.max_open_scroll_context: 1000
# 减少限制(资源有限)
search.max_open_scroll_context: 200
# 禁用滚动查询
search.max_open_scroll_context: 0
使用示例 #
基本滚动查询:
# 1. 初始化滚动查询
GET /my_index/_search?scroll=5m
{
"size": 1000,
"query": {
"match_all": {}
}
}
# 返回:
# {
# "_scroll_id": "c2NhbjsxOzE7dG90YWxfaGl0czo1MDt...",
# "hits": { ... }
# }
# 2. 使用 scroll_id 获取下一批数据
GET /_search/scroll
{
"scroll": "5m",
"scroll_id": "c2NhbjsxOzE7dG90YWxfaGl0czo1MDt..."
}
# 3. 完成后清理滚动上下文
DELETE /_search/scroll/c2NhbjsxOzE7dG90YWxfaGl0czo1MDt...
推荐设置建议 #
生产环境建议:根据使用场景和资源设置
| 场景 | 推荐值 | 说明 |
|---|---|---|
| 默认 | 500 | 通用场景 |
| 数据导出 | 1000-2000 | 需要大量并发滚动查询 |
| 实时搜索 | 100-300 | 滚动查询较少 |
| 资源受限 | 100-200 | 内存或文件句柄有限 |
资源消耗估算 #
每个滚动上下文约占:
| 资源类型 | 占用量 | 说明 |
|---|---|---|
| JVM 堆内存 | 1-10MB | 取决于查询复杂度 |
| 文件句柄 | 1-3 个 | 保持 segment reader |
| CPU | 低 | 主要是维护开销 |
示例计算:
每个上下文平均占用: 5MB
max_open_scroll_context: 500
总内存占用: 500 × 5MB = 2.5GB
如果 JVM 堆: 16GB
滚动上下文占用: 2.5GB / 16GB = 15.6%
常见问题 #
问题 1:超出限制被拒绝
EsRejectedExecutionException[trying to create too many scroll contexts.
maximum: 500]
可能原因:
- 应用未清理滚动上下文
- 并发滚动查询过多
- 滚动时间过长
解决方案:
- 增加限制
PUT /_cluster/settings
{
"transient": {
"search.max_open_scroll_context": 1000
}
}
- 清理旧的滚动上下文
# 清理所有滚动上下文
DELETE /_search/scroll/_all
# 清理特定的滚动上下文
DELETE /_search/scroll/c2NhbjsxOzE7...
- 使用 search_after 替代滚动
// 使用 search_after 进行深分页
GET /my_index/_search
{
"size": 100,
"query": {"match_all": {}},
"sort": [
{"_id": "asc"},
{"_score": "desc"}
],
"search_after": ["last_doc_id", 0.5]
}
- 优化滚动查询
// 减小每次请求的数据量
GET /my_index/_search?scroll=2m
{
"size": 100, // 减小 size
"query": {
"range": {
"timestamp": {
"gte": "now-1h" // 缩小时间范围
}
}
}
}
问题 2:滚动上下文泄漏
症状:
- 滚动上下文数量持续增长
- 内存使用持续增长
- 未达到限制但资源紧张
解决方案:
- 设置自动清理
# 在 easysearch.yml 中设置
search.keep_alive_interval: 1m
- 应用层正确处理
// 伪代码示例
try {
do {
result = client.scroll({
scroll: '5m',
scroll_id: scrollId
});
// 处理数据...
// 检查是否完成
if (result.hits.hits.length === 0) {
break;
}
} while (true);
} finally {
// 务必清理滚动上下文
client.clearScroll({ scroll_id: scrollId });
}
- 定期清理
# 定期执行清理任务
DELETE /_search/scroll/_all
动态修改 #
# 临时修改
PUT /_cluster/settings
{
"transient": {
"search.max_open_scroll_context": 1000
}
}
# 持久修改
PUT /_cluster/settings
{
"persistent": {
"search.max_open_scroll_context": 1000
}
}
查看当前状态 #
# 查看当前设置
GET /_cluster/settings?flat_settings=true
# 查看节点统计信息
GET /_nodes/stats/indices/search?pretty
# 查看当前打开的滚动上下文数量
GET /_nodes/stats/indices/search?filter_path=**.open_contexts
输出字段说明:
| 字段 | 说明 |
|---|---|
| open_contexts | 当前打开的滚动上下文数 |
| scroll_current | 当前活跃的滚动查询数 |
| scroll_total | 总滚动查询数 |
替代方案 #
1. 使用 search_after
// search_after 适用于实时深分页
GET /my_index/_search
{
"size": 100,
"query": {"match_all": {}},
"sort": [
{"timestamp": "asc"},
{"_id": "asc"}
]
}
// 获取下一页,使用上一页最后的 sort 值
GET /my_index/_search
{
"size": 100,
"query": {"match_all": {}},
"sort": [
{"timestamp": "asc"},
{"_id": "asc"}
],
"search_after": [1609459200000, "doc_id_123"]
}
2. 使用 PIT(Point In Time)
# 创建 PIT
POST /my_index/_pit
{
"keep_alive": "10m"
}
# 使用 PIT 进行搜索
GET /_search
{
"size": 100,
"pit": {
"id": "pit_id_value",
"keep_alive": "10m"
},
"query": {"match_all": {}},
"sort": [{"_shard_doc": "asc"}]
}
# 关闭 PIT
DELETE /_pit
{
"pit_id": "pit_id_value"
}
性能优化建议 #
1. 合理设置滚动时间
# 根据处理速度设置
# 快速处理:1-2 分钟
GET /my_index/_search?scroll=1m
# 慢速处理:5-10 分钟
GET /my_index/_search?scroll=5m
# 避免设置过长
GET /my_index/_search?scroll=1h # 不推荐
2. 控制每次返回数量
// 根据网络和内存情况调整 size
GET /my_index/_search?scroll=5m
{
"size": 1000 // 100-1000 之间
}
3. 使用字段过滤
// 只获取需要的字段,减少内存占用
GET /my_index/_search?scroll=5m
{
"size": 1000,
"_source": ["field1", "field2", "field3"],
"query": {"match_all": {}}
}
相关配置项 #
| 配置项 | 默认值 | 说明 |
|---|---|---|
search.max_keep_alive | 24h | 最大滚动保持时间 |
search.default_keep_alive | 5m | 默认滚动保持时间 |
search.keep_alive_interval | 1m | 清理间隔 |
search.max_open_pit_context | 300 | 最大 PIT 上下文数 |
监控建议 #
# 持续监控滚动上下文数量
GET /_nodes/stats/indices/search?pretty
# 设置告警
# 当 open_contexts > 80% 限制时告警
健康阈值:
| 指标 | 健康 | 警告 | 严重 |
|---|---|---|---|
| open_contexts / max | < 60% | 60-80% | > 80% |
注意事项 #
- 动态更新:此配置为动态配置,可在线修改
- 资源占用:每个滚动上下文占用内存和文件句柄
- 及时清理:使用完毕后务必清理滚动上下文
- 替代方案:考虑使用 search_after 或 PIT
- 监控重要:定期监控打开的上下文数量





