ES 中 mapping 的 true、false、runtime:区别与实践

242 阅读10分钟

在 Elasticsearch(ES)的 mapping 配置中,truefalseruntime 是三个容易混淆但至关重要的参数。它们分别控制着字段的动态映射行为、索引状态以及运行时计算逻辑,直接影响 ES 的性能、灵活性和数据规范性。本文将从概念、区别、适用场景和实践案例四个维度,带你彻底搞懂这三个参数。

一、核心概念:参数的本质作用

在 ES 中,这三个参数的作用域和目标不同:

  • true / false:主要用于控制 动态映射(dynamic)  或 字段是否被索引(index) ,决定字段能否被自动添加到 mapping 或参与搜索。

  • runtime:是 ES 7.10+ 引入的 运行时字段 特性,允许在查询时动态生成字段,无需提前定义或索引。

下面我们逐个拆解。

二、动态映射(dynamic)中的 true /false/strict

dynamic 是 mapping 中的顶层配置,用于控制当文档包含 mapping 中未定义的新字段 时,ES 如何处理这些字段。它的可选值包括 truefalsestrict,三者的核心区别在于对新字段的 “容忍度”。

1. dynamic: true(默认值):自动新增字段

行为逻辑:

当文档中出现未定义的新字段时,ES 会自动为其创建映射(根据值推断类型),并将字段添加到 mapping 中。新字段可被搜索、聚合和排序。

适用场景:
  • 快速原型开发(无需提前设计完整 mapping);
  • 字段结构动态变化的场景(如日志数据,不同日志可能包含随机字段)。
示例:

假设我们创建一个 logs 索引,mapping 仅定义 message 字段,且 dynamic: true

PUT /logs
{
  "mappings": {
    "dynamic": true,  // 默认值,可省略
    "properties": {
      "message": { "type": "text" }
    }
  }
}

写入一条包含新字段 level 和 timestamp 的文档:

POST /logs/_doc/1
{
  "message": "system error",
  "level": "error",  // 新字段
  "timestamp": "2024-07-01T12:00:00"  // 新字段
}

此时查看 mapping,会发现 ES 已自动添加 level(推断为 keyword)和 timestamp(推断为 date):

GET /logs/_mapping
{
  "logs": {
    "mappings": {
      "dynamic": "true",
      "properties": {
        "message": { "type": "text" },
        "level": { "type": "keyword" },  // 自动新增
        "timestamp": { "type": "date" }  // 自动新增
      }
    }
  }
}
优缺点:
  • 优点:灵活性极高,无需提前定义字段,适合快速迭代。
  • 缺点:字段可能失控(如恶意写入大量无效字段),导致 mapping 臃肿,拖慢搜索和索引性能。

2. dynamic: false:忽略新字段

行为逻辑:

文档中的新字段会被 保留在 _source 中(原始文档可查看),但不会被添加到 mapping,也不会被索引(无法搜索、聚合)。

适用场景:
  • 希望保留原始数据(_source 完整),但不希望新字段影响 mapping 结构;
  • 字段偶尔变化,但核心字段固定(如用户行为日志中的临时参数)。
示例:

基于上面的 logs 索引,修改 dynamic: false

PUT /logs
{
  "mappings": {
    "dynamic": false,
    "properties": {
      "message": { "type": "text" }
    }
  }
}

写入同样包含 level 和 timestamp 的文档后,查看 mapping 会发现新字段未被添加:

GET /logs/_mapping
{
  "logs": {
    "mappings": {
      "dynamic": "false",
      "properties": {
        "message": { "type": "text" }  // 仅原始字段
      }
    }
  }
}

但 _source 中仍包含新字段(可查看但不可搜索):

GET /logs/_doc/1
{
  "_source": {
    "message": "system error",
    "level": "error",
    "timestamp": "2024-07-01T12:00:00"
  }
}
优缺点:
  • 优点:避免 mapping 膨胀,保护核心字段的索引效率。
  • 缺点:新字段无法搜索,可能导致业务逻辑错误(如误写字段名却查不到数据)。

3. dynamic: strict:严格拒绝新字段

行为逻辑:

文档中若包含未定义的新字段,ES 会直接 拒绝索引请求 并抛出异常,强制要求所有字段必须提前在 mapping 中定义。

适用场景:
  • 字段结构固定的核心业务数据(如订单、用户信息);
  • 需严格控制数据规范性的场景(如金融、医疗数据)。
示例:

将 logs 索引的 dynamic 设为 strict

PUT /logs
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "message": { "type": "text" }
    }
  }
}

再次写入含 level 的文档时,ES 会抛出异常:

{
  "error": {
    "root_cause": [
      {
        "type": "strict_dynamic_mapping_exception",
        "reason": "mapping set to strict, dynamic introduction of [level] within [_doc] is not allowed"
      }
    ]
  },
  "status": 400
}

此时必须先手动添加 level 字段到 mapping,才能成功写入:

PUT /logs/_mapping
{
  "properties": {
    "level": { "type": "keyword" }
  }
}
优缺点:
  • 优点:数据规范性极强,杜绝无效字段,保护集群性能。
  • 缺点:灵活性低,新字段需手动更新 mapping,增加开发成本。

三、index: true /false:控制字段是否被索引

与 dynamic 不同,index 是 单个字段的配置,用于控制已定义的字段是否被索引(与新字段无关)。

1. index: true(默认值)

行为逻辑:

字段会被索引,可用于搜索、聚合、排序。ES 会为该字段创建倒排索引,占用磁盘空间。

适用场景:

所有需要参与查询或分析的字段(如 namepricestatus)。

示例:
PUT /users
{
  "mappings": {
    "properties": {
      "username": { 
        "type": "text", 
        "index": true  // 可搜索
      }
    }
  }
}

2. index: false

行为逻辑:

字段 不被索引,仅保存在 _source 中(可查看原始值),但无法用于搜索、聚合或排序。

适用场景:
  • 仅需展示但无需搜索的字段(如用户头像 URL、原始 JSON 字符串);
  • 敏感字段(如身份证号,仅需存储无需检索)。
示例:
PUT /users
{
  "mappings": {
    "properties": {
      "avatar_url": { 
        "type": "keyword", 
        "index": false  // 不可搜索,仅存储
      }
    }
  }
}

此时查询 avatar_url 会返回空结果:

GET /users/_search
{
  "query": {
    "match": { "avatar_url": "https://example.com/avatar.jpg" }
  }
}

四、runtime 字段:动态计算的临时字段

runtime 字段是 ES 7.10+ 引入的特性,允许在查询时动态生成字段,无需提前定义或索引,字段值仅在查询时计算,不占用磁盘空间。

核心特点:

  • 不写入 mapping,不占用索引空间;
  • 计算逻辑可动态修改(无需重建索引);
  • 支持通过脚本(Painless)基于已有字段生成新值。

适用场景:

  • 临时分析(如动态转换时间格式);
  • 低频使用的字段(无需长期索引);
  • 字段逻辑频繁变化(避免重建索引)。

示例:动态生成日期格式化字段

假设文档中存储的是毫秒级时间戳 timestamp,我们希望在查询时动态生成 date_str 字段(格式为 yyyy-MM-dd),无需提前定义:

GET /logs/_search
{
  "runtime_mappings": {
    "date_str": {  // 运行时字段名
      "type": "keyword",
      "script": {
        "source": "emit(doc['timestamp'].value.toString('yyyy-MM-dd'))"
      }
    }
  },
  "query": {
    "match_all": {}
  },
  "fields": ["date_str", "timestamp"]  // 返回运行时字段
}

查询结果中会新增 date_str 字段:

{
  "hits": {
    "hits": [
      {
        "_source": { "timestamp": 1688208000000 },
        "fields": {
          "timestamp": [1688208000000],
          "date_str": ["2024-07-01"]  // 动态生成
        }
      }
    ]
  }
}

优缺点:

  • 优点:灵活度极高,不占用磁盘空间,适合动态分析。
  • 缺点:每次查询都需计算,增加 CPU 开销,不适合高频查询字段。

五、参数对比与选择建议

参数作用域核心行为适用场景性能影响
dynamic: true新字段动态映射自动新增字段到 mapping 并索引快速开发、日志等动态数据可能导致 mapping 臃肿
dynamic: false新字段动态映射忽略新字段,仅保存在 _source需保留原始数据但控制 mapping 结构无额外开销
dynamic: strict新字段动态映射拒绝含新字段的文档核心业务数据、强规范场景无额外开销
index: true单个字段字段被索引,可搜索需查询 / 分析的字段占用磁盘空间
index: false单个字段字段不被索引,仅存储仅展示的字段节省磁盘空间
runtime查询时动态生成字段临时计算,不存储动态分析、低频字段增加查询时 CPU 开销

选择建议:

  1. 核心业务数据(如订单):用 dynamic: strict + index: true,保证规范和性能。
  2. 日志、埋点等动态数据:用 dynamic: false,保留原始数据同时避免 mapping 膨胀。
  3. 仅展示的字段(如 URL):用 index: false,节省空间。
  4. 临时分析或动态字段:用 runtime 字段,避免频繁修改 mapping。

六、总结

ES 中的 truefalseruntime 三个参数,分别从动态映射、索引状态和运行时计算三个维度,提供了对字段的精细化控制。理解它们的区别,不仅能避免 “字段无法搜索”“mapping 臃肿” 等常见问题,还能根据业务场景平衡 ES 的灵活性和性能。

问题

ES 中 dynamic特性引发的问题

前置知识:

  1. 在 Elasticsearch(ES)中,dynamic特性用于控制在文档索引过程中,遇到新字段时是否自动映射新字段并建立索引。它有三种取值:true、false、strict,每种取值都可能引发一些问题。
  2. 向一个不存在的 索引 写数据,会根据写入的字段类型建mapping
  3. 数据写入失败也会自动创建 索引 ,但不会根据数据字段类型建mapping。【mapping可以理解为表结构~】

问题:

  • 字段数量不可控
  • 误操作写入引入无关字段
  • 集群性能杀手

方案:

  1. 开启索引和mapping的严格模式

    1. 限制只有系统级的索引可以自动创建
    2. dynamic设置为strict
  2. 使用Flattened类型

store 和 _source 是与文档字段存储及读取效率问题

  • store 是字段级别的一个设置(默认值为 false ),用于决定是否在 索引 中单独存储该字段的数据。当为某个字段设置 "store": true 时,ES 会在索引中为该字段开辟独立的存储空间来保存其原始值 。从存储结构上看,每个设置了 store 的字段,其数据是单独存放的。当需要读取多个不同 store 字段的值时,ES 需要在不同的存储位置之间进行 “跳转”(也就是所谓的 seek 操作 )。
  • _source 是 ES 中一个特殊的元字段,默认情况下,它会存储整个文档的原始 JSON 内容 。也就是说,文档中所有字段(除非通过 _source 的 includes/excludes 配置排除 )的信息都包含在 _source 里,以一个整体的形式存储。

fielddata 和 doc_values 是处理字段数据访问的两种机制

  • doc_values: 是列式存储结构,在索引时生成,默认启用(除 text 字段外)。它以列的形式存储字段值,专为聚合(Aggregation)、排序(Sorting)、脚本(Script)等操作优化,数据存储在磁盘,访问时按需加载到内存,读写效率高、内存占用可控 。
  • fielddata: 是一种内存数据结构,在查询时动态构建(首次查询触发)。它从倒排索引中提取字段值并加载到内存,专为 text 字段的聚合、排序等操作设计(因 text 字段默认无 doc_values ),但存在 内存占用大、构建耗时 等问题。

“使用 doc_values 代替 fielddata” 关键原因

原因1:

  • doc_values: 索引时生成,存储在磁盘,采用列式压缩(如 FOR 压缩、前缀压缩),磁盘占用小 。查询时按需加载,借助磁盘顺序读和内存缓存,聚合、排序等操作 延迟低、吞吐量高 。
  • fielddata: 查询时动态构建,数据存储在 JVM 堆内存,且未充分压缩(因需快速构建),内存占用大 。若字段值多(如大文本分词后大量词条),会导致 堆内存溢出(OOM) ,甚至集群崩溃;同时,首次查询构建 fielddata 时,会有明显延迟(冷启动问题)。

原因2:

  • _id 字段特性: _id 是 ES 文档的唯一标识,默认映射为 keyword 类型,但为了支持基于 _id 的聚合、排序(如按 _id 分组统计),ES 对其默认开启 fielddata 。
  • 潜在风险: 若业务中频繁对 _id 进行聚合、排序(实际场景较少见,因 _id 主要用于精准查询),大量 _id 值加载到内存可能引发 堆内存压力 。若需优化,可显式关闭 fielddata(若无需此类操作),或改用 doc_values 逻辑(但 _id 较特殊,需结合业务判断)。