2. 搜索引擎基础回顾:倒排索引原理、字段类型及其核心差异
在构建搜索索引之前,必须理解搜索引擎的底层数据组织方式。倒排索引是全文搜索引擎(如 Elasticsearch、OpenSearch、Solr)的核心数据结构,而字段类型决定了数据如何被索引、存储和检索。本章将系统回顾这两大基础,为后续 NestJS 驱动索引字段类型演进与兼容策略奠定理论基石。
2.1 倒排索引原理
倒排索引(Inverted Index)是一种以“词项 → 文档”为映射关系的索引结构,与传统的“文档 → 词项”正向索引相反。它通过预先建立词项到文档列表的映射,实现毫秒级全文搜索。
核心概念:倒排索引由 词项字典(Term Dictionary) 和 倒排列表(Posting List) 两部分组成。词项字典存储所有唯一词项,倒排列表记录每个词项出现在哪些文档中,以及出现位置、频率等信息。
2.2 核心字段类型详解
搜索引擎中的字段类型决定了数据如何被索引、存储和检索。不同字段类型在分词、排序、聚合、过滤等行为上存在本质差异。以下是生产环境中最常见的六种字段类型。
| 字段类型 | 索引方式 | 分词行为 | 典型用途 | 排序/聚合 |
|---|---|---|---|---|
| keyword | 不分词,整体索引 | 不进行分词 | 精确匹配、过滤、聚合、排序 | 支持 |
| text | 分词后索引 | 按分析器分词 | 全文搜索、模糊匹配 | 默认不支持 |
| integer | 数值索引(BKD树) | 不适用 | 数值范围查询、排序、聚合 | 支持 |
| date | 数值索引(时间戳) | 不适用 | 时间范围查询、排序、日期直方图 | 支持 |
| nested | 独立子文档索引 | 子字段各自决定 | 对象数组独立查询与关联 | 有限支持 |
| object | 扁平化索引 | 子字段各自决定 | 简单嵌套对象(非数组) | 有限支持 |
关键差异:keyword 与 text 是搜索工程师最常混淆的一对类型。keyword 适合精确匹配(如订单状态、用户ID),text 适合全文搜索(如商品描述、文章正文)。在索引映射设计时,常使用 multi-fields 策略,将同一字段同时映射为 text 和 keyword,兼顾搜索与聚合。
2.3 字段类型核心差异对比
2.3.1 keyword vs text:分词与检索行为
keyword 和 text 是字符串字段的两大分支,其核心差异在于是否经过分析器(Analyzer)处理。
- keyword:将整个字符串作为一个词项存入倒排索引。搜索时必须完全匹配(或使用通配符/正则)。适用于枚举值、ID、标签、状态码。
- text:经过分析器(分词器+过滤器)处理后,生成多个词项。支持部分匹配、模糊查询、同义词扩展。适用于自然语言内容。
常见陷阱:对 text 字段执行 terms 聚合或排序操作时,搜索引擎会报错或返回意外结果,因为 text 字段默认不启用 fielddata。解决方案:使用该字段的 keyword 子字段(multi-field)进行聚合。
2.3.2 integer 与 date:数值与时间类型
integer 和 date 在底层都使用 BKD 树(Block K-D Tree)进行索引,支持高效的数值范围查询和排序。date 字段在内部被转换为毫秒级时间戳(long 类型)存储。
| 特性 | integer | date |
|---|---|---|
| 底层存储 | BKD 树 | BKD 树(时间戳) |
| 范围查询 | 支持(gte/lte) | 支持(gte/lte + 日期数学) |
| 聚合类型 | stats, percentiles, histogram | date_histogram, date_range |
| 排序性能 | 极高 | 极高 |
| 时区处理 | 不适用 | 支持时区转换 |
2.3.3 object vs nested:嵌套对象处理
object 和 nested 都用于处理 JSON 嵌套结构,但它们在索引方式上存在根本差异。
- object:将嵌套对象的字段扁平化到父文档中。对于对象数组,会丢失数组内字段之间的关联性,导致跨对象交叉匹配。
- nested:将数组中的每个对象作为独立的隐藏子文档进行索引。查询时保证同一子文档内的字段关联性,但会带来额外的存储和查询开销。
2.4 字段类型选择决策流程
在实际索引映射设计时,可参考以下决策流程快速确定字段类型:
- 数据类型判断:字符串 → 进入步骤2;数值 → integer/long/float/double;时间 → date;布尔 → boolean;嵌套 → object/nested。
- 字符串用途判断:全文搜索 → text;精确匹配/聚合/排序 → keyword;两者都需要 → 使用 multi-fields(text + keyword)。
- 嵌套结构判断:是否为对象数组且需要独立查询 → nested;简单嵌套或无需跨对象关联 → object。
- 性能与存储权衡:nested 查询性能低于 object,且占用更多存储空间。仅在确实需要子文档关联性时使用 nested。
生产实践:在 NestJS 中使用 @nestjs/elasticsearch 或 @opensearch-project/opensearch 定义索引映射时,建议将字段类型决策前置到实体设计阶段。通过 TypeScript 类型与索引映射的强绑定,避免运行时类型不匹配导致的索引重建。
2.5 字段类型演进与兼容性挑战
随着业务发展,索引字段类型可能需要变更。搜索引擎对字段类型变更的兼容性策略如下:
| 变更场景 | 兼容性 | 解决方案 |
|---|---|---|
| keyword → text | 不兼容 | 新建字段,reindex |
| text → keyword | 不兼容 | 新建字段,reindex |
| integer → long | 兼容(向上扩展) | 直接修改映射 |
| date 格式变更 | 部分兼容 | 需更新 format 参数 |
| object → nested | 不兼容 | 新建字段,reindex |
| nested → object | 不兼容 | 新建字段,reindex |
重要提示:搜索引擎(如 Elasticsearch 7.x+)不允许直接修改已有字段的类型。任何不兼容的类型变更都需要创建新索引、执行 reindex 操作,并切换别名。在 NestJS 中,建议通过索引模板(Index Template)和别名机制实现零停机索引迁移。
理解倒排索引原理和字段类型差异,是后续章节中设计 NestJS 索引映射、实现字段类型演进策略、以及构建兼容性测试套件的基础。下一章将深入 NestJS 与搜索引擎的集成模式,从实体映射到索引生命周期管理。