Elasticsearch 查询、容量与重建:8.19.23 单节点使用观察
前一篇讲清了 ES 的源码机制:倒排索引、执行与恢复固定 8.15.1/9.11.1 源码讲 mapping/分词/并发条件/可见性/主副本/分片检索/PIT。本页是 U54(#91)的 C08 使用观察页:在 Elasticsearch 8.19.23 单节点(DBngin,127.0.0.1:9200)上实跑 7 组查询与容量实验,补真实服务数字。
证据等级:全部观察来自真实服务(labs/es-usage/run.py,索引 u54-<run-id> 结束删除,原始观察见运行证据),但都是单次观察;单节点 0 副本意味着分片/副本行为只有文档层验证。机制问题回查源码页。
一句话主线:mapping 决定能回答什么问题;分页/重建的成本边界是设计期决策,不是运行期调参。
term vs match:mapping 契约,不是 analyzer 魔法
| 查询 | 命中 |
|---|---|
term title:"Red Apple"(text 字段) | 0 |
match title:"red apple" | 2 |
term title_keyword:"Red Apple"(keyword 字段) | 1 |
三条判读:text 字段的 term 查询输入不分词,通常找不到完整词项;match 把输入分词后命中 red+apple 的交集(2 条);keyword 字段存原文,term 精确匹配成功(U54 termVsMatch=PASS)。"term 不能查 text"不是规则,是 mapping 决定的结果——用 _analyze 核查再下结论。
nested vs object:同元素 vs 跨元素
object mapping 把数组对象扁平为多值字段;nested mapping 把每个对象作为独立隐藏 Lucene 文档。真实对照(同一组 attrs 数据写入两个索引):
- object 索引:
key=color AND value=red命中 2 条(doc 1 的 attrs[0] 和 doc 2 的 attrs[1] 分别贡献条件)。 - nested 索引:同样的 bool must 放在同一个 nested 查询内,命中 1 条(仅 doc 1,同元素同时满足)。
这就是"同一个属性元素的 key 为 color 且 value 为 red"的语义差异。只把 mapping 改成 nested、却把条件放到两个独立 nested 查询,仍可能分别匹配不同元素——block join 的边界是一个 nested query 一个元素。
refresh 与 GET:搜索可见 ≠ 实时 GET
新文档 POST 后未 refresh:term title_keyword search 得 0 命中;POST _refresh 后得 1(U54 refreshVisibility=PASS beforeRefresh=0 afterRefresh=1)。但 realtime GET(GET /index/_doc/id)在 refresh 前也能取到——GET 从 translog 读,search 从 Lucene 读。这是"写入成功但搜不到"的根因。
深分页:max_result_window 拒绝
from=20000 触发 400 错误,响应提到 max_result_window(默认 10000)。(U54 deepPaginationReject=PASS status=400。)调大这个值只是允许请求,候选规模的成本不消失(机制页 12 片 × 100020 的算例仍适用)。替代方案:search_after + PIT。
默认值的来源(固定 v8.15.1 源码,IndexSettings.java 122–136 行)——它是一个动态、索引级设置,注释也直说调高要自己评估集群:
/**
* Index setting describing the maximum value of from + size on a query.
* The Default maximum value of from + size on a query is 10,000. This was chosen as
* a conservative default as it is sure to not cause trouble. Users can
* certainly profile their cluster and decide to set it to 100,000
* safely. 1,000,000 is probably way to high for any cluster to set
* safely.
*/
public static final Setting<Integer> MAX_RESULT_WINDOW_SETTING = Setting.intSetting(
"index.max_result_window",
10000,
1,
Property.Dynamic,
Property.IndexScope
);这一点在 8.19.23 上实测同样成立(本页 400 观察即 from+size 超过默认窗口)。
search_after + PIT:逐页遍历无重叠无遗漏
PIT 打开(POST _pit,不是 PUT)→ page1 size=2 取 [3,1],带回最后 hit 的完整 sort 数组 → page2 search_after 该数组 → 取 [2, Fresh](自动生成 _id)。4 条无重叠无遗漏(U54 searchAfterPit=PASS page1=[3,1] page2=[2,...] noOverlapNoMiss=true)。PIT 用 POST 关闭(也是 POST,不是 DELETE)。PIT 不是事务:它固定读取视图,不给跨 PIT 一致性。
聚合与重建
- terms 聚合:fruit=3、test=1,bucket 数与 mapping 的 keyword 字段一致。
- _reindex + _alias 切换:4 条文档 reindex 到 v2(映射增加
new_field),PUT /{new_index}/_alias/{alias}后通过 alias GET count=4。mapping 不兼容变更的正确路径是新建→reindex→别名切换,不是原地改。
routing 与副本
routing 可将租户查询缩到一片,但单分片实验无法观察路由分布(诚实边界)。副本增加可用性与读能力,写入仍需复制——不能按副本数线性估算写扩容。
未运行与受阻
| 项目 | 状态 |
|---|---|
| 多节点分片/副本故障 | 未运行(单节点 0 副本) |
| routing 分布观察 | 未运行(单分片) |
| 吞吐/延迟基准 | 明确不纳入 |
| reindex 并发限流与中断恢复 | 未运行 |
| 8.19.23 与 8.15.1 行为差异逐项核对 | 未运行 |
实验读法
python3 labs/es-usage/run.pytermVsMatch:mapping 契约三查询对照。nestedScope:object 跨元素 vs nested 同元素(双索引对比)。refreshVisibility:refresh 前/后搜索可见性。deepPaginationReject:max_result_window 拒绝。searchAfterPit:PIT 游标遍历。aggregation:terms 聚合。reindexAlias:_reindex + _alias 切换。
闭卷验收
本页 13 道闭卷题收进了下方自测面板:先回忆,再展开参考答案与评分点,最后给三档自评。