深入 Elasticsearch(08):从 match 到 bool 的查询体系
上一篇解决了分数怎么算出来的——BM25 的三个因子决定了文档的相关性排序。这一篇进入 Query DSL 的查询类型体系和组合逻辑。 ES 的查询体系不是一个扁平的 API 列表。全文查询、精确查询、模糊查询、地理查询、复合查询分别对应不同的底层数据结构和匹配逻辑。理解这个分类,才能在具体场景中选对查询类型。 本文抓两个问题:ES 的查询类型怎样分类,以及 bool 查询怎样把不同类型的查询组合起来。 查询体系全景 123456789101112131415161718192021222324252627282930313233ES 查询体系├── 全文查询(走 analyzer,参与打分)│ ├── match 分词后多词项匹配│ ├── match_phrase 分词后要求位置相邻│ ├── multi_match 跨多个字段的 match│ └── match_phrase_prefix 短语前缀(输入补全)│├── 精确查询(不走 analyzer,通常用于 filter context)│ ...
深入 Elasticsearch(07):BM25 与打分机制
上一篇解决了搜索的执行流程——Query-Then-Fetch 两阶段在集群中怎样运转。这一篇进入每条结果的分数是怎么算出来的。 ES 的搜索结果按 _score 降序排列。_score 是 BM25 算法算出的相关性分数。理解 BM25,才能理解为什么有些文档排在前面、有些排在后面,也才能有针对性地调整搜索相关性。 本文只抓一个问题:BM25 的三个因子分别衡量什么,以及它们怎样组合成最终分数。 BM25 的直觉 BM25 的名字来自"Best Matching 25"(第 25 号最佳匹配公式)。它取代了 ES 5.x 之前使用的 TF-IDF 模型,成为 Lucene 和 ES 的默认相似度算法。 BM25 的核心思路可以用一句话概括:一个词项越稀有(IDF 高)、在文档中出现越多次(TF 高)、文档越短(字段长度归一化),包含这个词项的文档就越相关。 分数由三个因子相乘构成: 1score(q, d) = IDF(q) × TF_saturation(q, d) × length_norm(d) flowchart TB IDF["...
深入 Elasticsearch(06):Query-Then-Fetch 的两阶段流程
上一篇解决了文档怎样变成可搜索的——写入路径经过 buffer、translog、refresh 三步后文档才对搜索可见。这一篇进入一次搜索请求在集群内部怎样执行。 ES 是分布式搜索引擎,一个 index 的数据分布在多个 shard 上。一次搜索需要在所有相关 shard 上执行,然后合并结果。这个过程分成两个阶段:Query 阶段和 Fetch 阶段。 本文只抓一个问题:Query-Then-Fetch 的两阶段搜索是怎么工作的,以及这个设计为什么是必要的。 为什么需要两个阶段 如果 ES 只有一个 shard,搜索很简单:在 shard 上执行查询,取 top-N 结果,返回。但数据分布在多个 shard 上时,直接从每个 shard 取 top-N 的完整文档(包含 _source),再在 coordinating node 合并,会传输大量不需要的数据——大部分文档在全局排序后会被丢弃。 两阶段设计的核心思路:先用轻量数据(docId + score)做全局排序,再只取需要的文档内容。 sequenceDiagram participant C as Cli...
深入 Elasticsearch(05):近实时、Translog 与 Refresh/Flush
上一篇解决了文本怎样变成词项——analysis pipeline 把原始文本标准化为可索引的 term。这一篇进入文档写入后怎样变成可搜索的。 一条文档经过 analysis 生成词项后,并不会立即对搜索可见。ES 的写入路径涉及内存缓冲、translog 持久化、refresh 生成新 segment、flush 提交到磁盘四个步骤。这套机制让 ES 在写入吞吐和搜索延迟之间取得平衡,也是"近实时搜索"(near-real-time, NRT)这个名字的由来。 本文只抓一个问题:ES 的近实时搜索是怎么实现的,refresh 和 flush 的区别在哪里。 写入路径的完整流程 一条文档从客户端发出到最终持久化到磁盘,经过以下步骤: 123456789101112131415Document → Coordinating Node(路由到目标 shard 的 primary) → Primary Shard: 1. 写入 in-memory buffer(indexing buffer) 2. 追加到 translog(顺序写磁盘,...
深入 Elasticsearch(04):从原始文本到可搜索词项
上一篇解决了文档的结构定义——mapping 把 JSON 字段翻译成 Lucene 数据结构。这一篇进入 text 类型字段怎样从原始字符串变成可搜索的词项。 mapping 决定一个字段"要不要做全文索引",analysis 决定"怎样做"。一个 text 字段在写入时经过 analysis pipeline,产生一组标准化的词项(terms),这些词项被写入倒排索引。搜索时,查询文本也经过 analysis,产生同样标准化的词项,然后在倒排索引中匹配。 本文只抓一个问题:analysis pipeline 的三个阶段怎样工作,以及索引时分析和搜索时分析为什么可能用不同的 analyzer。 Analysis 三阶段 一个 analyzer 由三个组件按顺序组成: 12345原始文本 → Character Filter(s) 字符级预处理 → Tokenizer 切分为 token → Token Filter(s) token 级后处理 → [term1, term2, ...] 写入倒...
深入 Elasticsearch(03):Mapping 与字段类型
上一篇解决了 Lucene 内部的段和索引结构。这一篇进入 ES 如何把 JSON 文档映射到 Lucene 字段。 一个 JSON 文档写入 ES 后,每个字段会变成 Lucene 内部的某种数据结构——倒排索引、BKD-tree、Doc Values 或 Stored Fields。这个从"JSON 字段"到"Lucene 数据结构"的翻译规则,就是 mapping。 本文只抓一个问题:mapping 在 ES 中扮演什么角色,以及 dynamic mapping 和 explicit mapping 的差异会怎样影响搜索行为。 Mapping 是什么 Mapping 定义了一个 index 中文档的字段名、字段类型和索引方式。可以类比为关系型数据库的 DDL(CREATE TABLE),但有几个关键区别: Mapping 允许动态推断。写入一个未知字段时,ES 可以自动猜测类型并添加到 mapping 中。 Mapping 一旦创建,字段类型不可修改。只能添加新字段,不能把一个 text 字段改成 keyword。要改变已有字段的...
深入 Elasticsearch(02):Segment、倒排索引与 Doc Values
上一篇解决了 ES 集群级架构——节点角色、集群状态和数据路径。这一篇进入单个 shard 内部的 Lucene 存储结构。 ES 的每个 shard 就是一个 Lucene index。Lucene index 不是一整块文件,而是由多个不可变的段(segment)组成。理解 segment 的内部结构,才能理解后续文章中 refresh、flush、merge、搜索延迟等机制为什么是那样设计的。 本文只抓一个问题:一个 Lucene segment 内部有哪些数据结构,它们各自承担什么角色。 Segment:不可变的存储单元 一个 Lucene index 由零个或多个 segment 组成。每个 segment 是一批文档的完整索引——包含这批文档的倒排索引、正排数据、字段信息等所有内容。 12345Lucene Index (= ES Shard)├── Segment 0 (committed, immutable)├── Segment 1 (committed, immutable)├── Segment 2 (committed, immutable)└─...
深入 Elasticsearch(01):Node、Cluster 与集群状态
上一篇解决了 ES 的核心数据结构是什么——倒排索引。这一篇进入 ES 的运行时架构。 一个 Elasticsearch 集群不是若干台机器简单地连在一起。集群内部有明确的角色分工、中心化的状态管理和分布式的数据路径。理解这些,才能在后续文章中准确地定位每个机制发生在哪一层。 本文只抓一个问题:一个 ES 集群由哪些角色的节点组成,它们之间通过什么机制协调。 节点角色 一个 Elasticsearch 进程启动后就是一个节点(node)。多个节点通过相同的 cluster.name 组成一个集群(cluster)。每个节点可以承担一个或多个角色,角色通过配置文件或启动参数指定。 ES 8.x 中的主要节点角色: 角色 配置值 职责 Master-eligible master 参与 master 选举,管理集群状态 Data data 存储数据分片,执行搜索和聚合 Data Content data_content 存储非时序数据 Data Hot/Warm/Cold/Frozen data_hot 等 分层存储(ILM 相关) Ingest in...
深入 Elasticsearch(00):为什么 Elasticsearch 的核心是一张倒排索引
Elasticsearch 容易被归类成"分布式搜索引擎"或"日志分析平台"。这些标签描述了它的用途,但没有说清楚它的内部结构。把 Elasticsearch 拆到最底层,剩下的核心数据结构只有一个:倒排索引(inverted index)。mapping、analysis、shard、replica、aggregation 五个机制围绕这个数据结构逐层展开。 Elasticsearch 是一个以倒排索引为核心数据结构的分布式搜索与分析引擎。 本篇是系列导读。核心任务只有一个:说清楚倒排索引是什么,以及它和关系型数据库里的 B+Tree 索引有什么本质区别。 倒排索引的基本结构 关系型数据库的 B+Tree 索引从行出发。给定一个索引键值,B+Tree 能定位到存储这行数据的磁盘页。查找路径是: 1索引键值 → B+Tree 内部节点 → 叶子节点 → 行指针 → 数据页 倒排索引从词项(term)出发。给定一个词项,倒排索引能定位到包含这个词项的所有文档。查找路径是: 1词项 → Term Dictionary → Posting ...
上下文管理全景:Agentic Coding 工具操纵 Messages 数组的六种策略
一个常见的直觉是:agentic coding 工具会"偷偷"管理对话历史。MCP 工具在第一轮取回了一大段数据库查询结果,模型分析完毕,过了十轮对话之后,工具应该已经悄悄把那段原始数据替换成了一句"此前查询了一段数据"。输入 token 因此减少,成本随之下降。 实际情况并非如此。不开 compact 或 prune,messages 数组就原封不动地增长。那段数据库查询结果会从第一轮一直带到第一百轮,每次 API 调用都完整发送,按全价计费。 这引出了一个工程问题:面对一个只增不减的数组,各家 agentic coding 工具到底有哪些操纵手段?这些手段如何与 prompt cache 机制交互?不同选择会导致怎样的成本和信息保真度差异? 本文是对 Claude Code 源码深度解析 和 Agentic Coding 深度解析 的差异化补位。前两篇分别覆盖了 Claude Code 的五级压缩流水线和 messages 数组的五维操纵模型(注入 / 定位 / 保护 / 清理 / 重复)。本文专注于跨工具的策略全景,以 prompt ...

