📣 极限科技诚招搜索运维工程师(Elasticsearch/Easysearch)- 全职/北京 👉 : 立即申请加入

配置项作用 #

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]

可能原因:

  1. 应用未清理滚动上下文
  2. 并发滚动查询过多
  3. 滚动时间过长

解决方案:

  1. 增加限制
PUT /_cluster/settings
{
  "transient": {
    "search.max_open_scroll_context": 1000
  }
}
  1. 清理旧的滚动上下文
# 清理所有滚动上下文
DELETE /_search/scroll/_all

# 清理特定的滚动上下文
DELETE /_search/scroll/c2NhbjsxOzE7...
  1. 使用 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]
}
  1. 优化滚动查询
// 减小每次请求的数据量
GET /my_index/_search?scroll=2m
{
  "size": 100,    // 减小 size
  "query": {
    "range": {
      "timestamp": {
        "gte": "now-1h"    // 缩小时间范围
      }
    }
  }
}

问题 2:滚动上下文泄漏

症状:

  • 滚动上下文数量持续增长
  • 内存使用持续增长
  • 未达到限制但资源紧张

解决方案:

  1. 设置自动清理
# 在 easysearch.yml 中设置
search.keep_alive_interval: 1m
  1. 应用层正确处理
// 伪代码示例
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 });
}
  1. 定期清理
# 定期执行清理任务
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_alive24h最大滚动保持时间
search.default_keep_alive5m默认滚动保持时间
search.keep_alive_interval1m清理间隔
search.max_open_pit_context300最大 PIT 上下文数

监控建议 #

# 持续监控滚动上下文数量
GET /_nodes/stats/indices/search?pretty

# 设置告警
# 当 open_contexts > 80% 限制时告警

健康阈值:

指标健康警告严重
open_contexts / max< 60%60-80%> 80%

注意事项 #

  1. 动态更新:此配置为动态配置,可在线修改
  2. 资源占用:每个滚动上下文占用内存和文件句柄
  3. 及时清理:使用完毕后务必清理滚动上下文
  4. 替代方案:考虑使用 search_after 或 PIT
  5. 监控重要:定期监控打开的上下文数量