ElasticSearch核心引擎Apache Lucene(二):正排索引的奥秘

文章目录

    • [一、 引言](#一、 引言)
      • [1.1 倒排索引极其适合"搜索"](#1.1 倒排索引极其适合“搜索”)
      • [1.2 倒排索引不适合"分析"](#1.2 倒排索引不适合“分析”)
    • [二、 列式存储(Columnar Storage)](#二、 列式存储(Columnar Storage))
      • [2.1 行式存储 vs 列式存储](#2.1 行式存储 vs 列式存储)
      • [2.2 为什么聚合和排序需要列式存储?](#2.2 为什么聚合和排序需要列式存储?)
    • [三、 FieldData vs DocValues:新旧之争](#三、 FieldData vs DocValues:新旧之争)
    • [四、 深度解密:DocValues 的磁盘存储格式](#四、 深度解密:DocValues 的磁盘存储格式)
      • [4.1 数值类型压缩策略(Numeric DocValues)](#4.1 数值类型压缩策略(Numeric DocValues))
        • [A. 增量编码(Delta Encoding)](#A. 增量编码(Delta Encoding))
        • [B. 最大公约数压缩(GCD Compression)](#B. 最大公约数压缩(GCD Compression))
        • [C. 表压缩(Table Encoding)](#C. 表压缩(Table Encoding))
      • [4.2 字符串类型存储(Binary/Sorted DocValues)](#4.2 字符串类型存储(Binary/Sorted DocValues))
    • [五、 最佳实践与性能调优](#五、 最佳实践与性能调优)
    • 总结

摘要 :ElasticSearch 之所以强大,不仅在于它能通过倒排索引实现毫秒级的全文检索,更在于它能通过DocValues实现高效的聚合(Aggregation)、排序(Sorting)和脚本计算。本文将深入 Apache Lucene 内核,拆解正排索引的设计哲学、列式存储的优势以及磁盘上的编码格式。


一、 引言

在深入 DocValues 之前,我们必须先理解它解决的问题。ElasticSearch 最引以为豪的是倒排索引(Inverted Index)

1.1 倒排索引极其适合"搜索"

倒排索引是一个 Term -> List<DocId> 的映射关系。

例如,搜索关键词 pixel

Term Doc IDs
pixel [1, 3]
phone [1, 2, 3]

Lucene 可以极其快速地定位包含 pixel 的文档是 ID 为 1 和 3 的文档。

1.2 倒排索引不适合"分析"

但是,如果我们想执行以下操作:

  • 排序:按价格从低到高排序。
  • 聚合:计算所有手机的平均价格。

问题出现了 :倒排索引是"词"到"文档"的映射。而排序和聚合需要的是"文档"到"值"的映射(即:DocId -> Value)。

如果仅依靠倒排索引,Lucene 必须遍历所有 Term,加载整个倒排表,并在内存中构建文档到值的映射。这被称为 Un-inverting(反向索引),在数据量大时,这会消耗巨大的堆内存(Heap),导致 OOM 或频繁 GC。


二、 列式存储(Columnar Storage)

为了解决上述性能瓶颈,Lucene 引入了 DocValues 。它本质上是一个正排索引 ,采用列式存储结构。

2.1 行式存储 vs 列式存储

  • 行式存储(Row-oriented):如 MySQL 的 InnoDB,将一行数据的所有字段存储在一起。
  • 列式存储(Columnar-oriented):将同一字段的所有值存储在一起。

列式存储 (DocValues)
ID Column: 1, 2, 3...
Name Column: Pixel, iPhone, Galaxy...
Price Column: 699, 999, 899...
行式存储 (如 MySQL)
Doc1: ID, Name, Price
Doc2: ID, Name, Price
Doc3: ID, Name, Price

2.2 为什么聚合和排序需要列式存储?

  1. 缓存局部性(Cache Locality)

    当计算平均价格时,CPU 只需要处理 Price 这一列的数据。在列式存储中,这些数据在物理磁盘和内存中是连续排列的。这意味着 CPU 可以通过预取(Prefetching)高效地加载数据,极大地减少磁盘寻道和内存缺页中断。

  2. 压缩率极高

    同一列的数据类型通常相同(例如都是数字),且数值分布往往具有规律性。这使得 Lucene 可以应用极其激进的压缩算法(如 Delta 编码、GCD 压缩等),从而显著减少磁盘 I/O。


三、 FieldData vs DocValues:新旧之争

在 ES 早期版本中,主要依靠 FieldData ,而现在默认使用 DocValues

特性 FieldData DocValues
创建时机 查询时(Query Time)动态构建 索引时(Index Time)构建写入磁盘
存储位置 JVM Heap 内存 磁盘文件(依靠 OS FileSystem Cache)
性能 加载极慢,查询快(纯内存) 加载无感知,查询极快(内存+磁盘)
缺点 容易导致 OOM,GC 压力大 占用额外的磁盘空间
适用场景 极少使用(仅 text 字段) 默认开启(numeric, keyword, date 等)

思维导图:DocValues 的生命周期
DocValues
构建阶段
IndexWriter buffer
Segment Flush
.dvd, .dvm
读取阶段
mmap 映射
OS Page Cache
随机访问 / 迭代器
应用场景
排序
聚合
脚本


四、 深度解密:DocValues 的磁盘存储格式

DocValues 并非简单地将数字写入文件,而是根据数据分布特征动态选择最佳编码。

DocValues 主要涉及两个文件:

  1. .dvm (DocValues Metadata): 索引文件,记录数据的偏移量。
  2. .dvd (DocValues Data): 实际的数据存储文件。

4.1 数值类型压缩策略(Numeric DocValues)

假设我们有一列价格数据:[100, 101, 102, 103]。Lucene 会尝试以下策略:

A. 增量编码(Delta Encoding)

Lucene 不存储原始值,而是存储最小值和偏移量。

  • Base: 100
  • Values: [0, 1, 2, 3]
  • 优势:原数值可能需要 8 字节(long),压缩后仅需几个比特。
B. 最大公约数压缩(GCD Compression)

假设数据为:[100, 200, 300, 400]

  • GCD: 100
  • Values: [1, 2, 3, 4]
  • 优势:通过除法大幅缩小数值范围。
C. 表压缩(Table Encoding)

如果数值的唯一值(Unique Values)很少(< 256个),Lucene 会构建一个字典表。

  • Table: [0: 100, 1: 999]
  • Stored: [0, 0, 1, 0, 1...]
  • 优势:直接将数值转换为极小的 ID。

存储流程图解:
唯一值极少
有公约数
数值紧凑
原始数值流: 100, 105, 100, 110
分析数值特征
Table Encoding
GCD Compression
Delta Encoding
Bit Packing (位打包)
写入 .dvd 文件

4.2 字符串类型存储(Binary/Sorted DocValues)

对于 Keyword 类型,DocValues 采用 SortedDocValues 格式。它结合了"字典编码"和"数值映射"。

  1. 去重与排序:首先提取所有唯一的字符串,按字典序排列,生成一个字典(Dictionary)。
  2. ID 映射:将文档中的字符串替换为字典中的对应的 Ordinal(序号)。
  3. 存储
    • Terms 部分:存储去重后的字符串列表(前缀压缩)。
    • Ordinals 部分:存储文档 ID 对应的序号(数值类型,应用上述数值压缩策略)。

示例:

文档: Doc1: "Apple", Doc2: "Banana", Doc3: "Apple"

  • Dictionary : 0: "Apple", 1: "Banana"
  • Ordinals Stream : 0, 1, 0

这使得字符串的排序和聚合变成了纯数字的操作,效率极高。


五、 最佳实践与性能调优

理解了原理,我们就能更好地使用 ElasticSearch。

  1. 不需要聚合的字段,禁用 DocValues

    如果你明确知道某个字段只需要被搜索(倒排索引),永远不需要排序或聚合,可以在 Mapping 中设置 doc_values: false。这能节省磁盘空间并加快索引速度。

    json 复制代码
    PUT my_index
    {
      "mappings": {
        "properties": {
          "session_id": { 
            "type": "keyword",
            "doc_values": false 
          }
        }
      }
    }
  2. 关注磁盘 I/O 和 Page Cache

    DocValues 极度依赖操作系统的文件系统缓存(Page Cache)。确保给 ES 留足堆外内存(Off-heap memory),通常建议 JVM Heap 设置为物理内存的 50%,剩下的 50% 留给 Lucene 使用。

  3. 稀疏字段的代价

    Lucene 默认会对所有文档生成 DocValue 占位。虽然现代 Lucene 对稀疏数据有优化(使用 ADVANCE 迭代器),但大量空置字段仍会带来轻微的元数据开销。


总结

Apache Lucene 通过 DocValues 完美补全了倒排索引在分析领域的短板。

  • 它采用列式存储,将随机 I/O 转化为顺序 I/O。
  • 它利用位打包、GCD、Delta 等算法将磁盘占用降到最低。
  • 它利用OS Cache,让大数据量的聚合分析在毫秒级完成。

理解 DocValues 的底层原理,不仅能帮助我们写出更高效的 DSL,更能让我们在面对海量数据分析架构设计时游刃有余。

相关推荐
Elasticsearch7 小时前
通用表达式语言 ( CEL ): CEL 输入如何改进 Elastic Agent 集成中的数据收集
elasticsearch
海兰2 天前
离线合同结构化提取与检索:LangExtract + 本地DeepSeek + Elasticsearch 9.x
大数据·elasticsearch·django
yumgpkpm2 天前
AI视频生成:Wan 2.2(阿里通义万相)在华为昇腾下的部署?
人工智能·hadoop·elasticsearch·zookeeper·flink·kafka·cloudera
james的分享2 天前
大数据领域核心 SQL 优化框架Apache Calcite介绍
大数据·sql·apache·calcite
莫寒清2 天前
Apache Tika
java·人工智能·spring·apache·知识图谱
Sheffield2 天前
如果把ZooKeeper按字面意思比作动物园管理员……
elasticsearch·zookeeper·kafka
归叶再无青2 天前
web服务安装部署、性能升级等(Apache、Nginx)
运维·前端·nginx·云原生·apache·bash
嗝屁小孩纸2 天前
ES索引重建(零工具纯脚本执行)
大数据·elasticsearch·搜索引擎
Elastic 中国社区官方博客2 天前
使用 Jina Embeddings v5 和 Elasticsearch 构建“与你的网站数据聊天”的 agent
大数据·人工智能·elasticsearch·搜索引擎·容器·全文检索·jina