_source 字段

编辑

_source 字段包含索引时传递的原始 JSON 文档主体。_source 字段本身不被索引(因此不可搜索),但会存储起来,以便在执行 *fetch* 请求时返回,例如 getsearch

如果磁盘使用对您来说很重要,请考虑以下选项

  • 使用 合成 _source,它在检索时重建源内容,而不是将其存储在磁盘上。这减少了磁盘使用量,但代价是在 GetSearch 查询中访问 _source 的速度较慢。
  • 完全禁用 _source 字段。这会减少磁盘使用量,但会禁用依赖于 _source 的功能。

合成 _source

编辑

虽然源字段非常方便,但它会占用大量的磁盘空间。Elasticsearch 可以不将源文档按您发送的格式原样存储在磁盘上,而是在检索时动态重建源内容。要启用此 订阅 功能,请为索引设置 index.mapping.source.mode 使用值 synthetic

resp = client.indices.create(
    index="idx",
    settings={
        "index": {
            "mapping": {
                "source": {
                    "mode": "synthetic"
                }
            }
        }
    },
)
print(resp)
const response = await client.indices.create({
  index: "idx",
  settings: {
    index: {
      mapping: {
        source: {
          mode: "synthetic",
        },
      },
    },
  },
});
console.log(response);
PUT idx
{
  "settings": {
    "index": {
      "mapping": {
        "source": {
          "mode": "synthetic"
        }
      }
    }
  }
}

虽然这种动态重建通常比原样保存源文档并在查询时加载它们要慢,但它节省了大量存储空间。在不需要加载 _source 字段的查询中不加载它,可以避免额外的延迟。

支持的字段

编辑

所有字段类型都支持合成 _source。根据实现细节,字段类型在使用合成 _source 时具有不同的属性。

大多数字段类型 使用现有数据构建合成 _source,最常见的是 doc_values存储字段。对于这些字段类型,不需要额外的空间来存储 _source 字段的内容。由于 doc_values 的存储布局,生成的 _source 字段与原始文档相比会经历 修改

对于所有其他字段类型,字段的原始值将按原样存储,方式与非合成模式下的 _source 字段相同。在这种情况下,没有修改,_source 中的字段数据与原始文档中的数据相同。类似地,使用 ignore_malformedignore_above 的字段的格式错误的值需要按原样存储。这种方法存储效率较低,因为重建 _source 所需的数据会与索引字段所需的其他数据(如 doc_values)一起存储。

合成 _source 限制

编辑

某些字段类型具有其他限制。这些限制记录在字段类型的 合成 _source 部分的文档中。

合成 _source 修改

编辑

启用合成 _source 后,检索到的文档与原始 JSON 相比会经历一些修改。

数组移动到叶字段
编辑

合成 _source 数组将移动到叶子。例如

resp = client.index(
    index="idx",
    id="1",
    document={
        "foo": [
            {
                "bar": 1
            },
            {
                "bar": 2
            }
        ]
    },
)
print(resp)
response = client.index(
  index: 'idx',
  id: 1,
  body: {
    foo: [
      {
        bar: 1
      },
      {
        bar: 2
      }
    ]
  }
)
puts response
const response = await client.index({
  index: "idx",
  id: 1,
  document: {
    foo: [
      {
        bar: 1,
      },
      {
        bar: 2,
      },
    ],
  },
});
console.log(response);
PUT idx/_doc/1
{
  "foo": [
    {
      "bar": 1
    },
    {
      "bar": 2
    }
  ]
}

将变成

{
  "foo": {
    "bar": [1, 2]
  }
}

这可能会导致某些数组消失

resp = client.index(
    index="idx",
    id="1",
    document={
        "foo": [
            {
                "bar": 1
            },
            {
                "baz": 2
            }
        ]
    },
)
print(resp)
response = client.index(
  index: 'idx',
  id: 1,
  body: {
    foo: [
      {
        bar: 1
      },
      {
        baz: 2
      }
    ]
  }
)
puts response
const response = await client.index({
  index: "idx",
  id: 1,
  document: {
    foo: [
      {
        bar: 1,
      },
      {
        baz: 2,
      },
    ],
  },
});
console.log(response);
PUT idx/_doc/1
{
  "foo": [
    {
      "bar": 1
    },
    {
      "baz": 2
    }
  ]
}

将变成

{
  "foo": {
    "bar": 1,
    "baz": 2
  }
}
字段按映射方式命名
编辑

合成源按照它们在映射中命名的方式命名字段。当与 动态映射 一起使用时,名称中带有句点 (.) 的字段默认解释为多个对象,而当禁用 subobjects 时,字段名称中的句点在对象中保留。例如

resp = client.index(
    index="idx",
    id="1",
    document={
        "foo.bar.baz": 1
    },
)
print(resp)
const response = await client.index({
  index: "idx",
  id: 1,
  document: {
    "foo.bar.baz": 1,
  },
});
console.log(response);
PUT idx/_doc/1
{
  "foo.bar.baz": 1
}

将变成

{
  "foo": {
    "bar": {
      "baz": 1
    }
  }
}

这会影响如何在 脚本 中引用源内容。例如,以其原始源形式引用脚本将返回 null

"script": { "source": """  emit(params._source['foo.bar.baz'])  """ }

相反,源引用需要与映射结构一致

"script": { "source": """  emit(params._source['foo']['bar']['baz'])  """ }

或者简单地

"script": { "source": """  emit(params._source.foo.bar.baz)  """ }

以下 字段 API 更可取,因为除了与映射结构无关之外,它们还利用了 docvalues(如果可用),并且仅在需要时才回退到合成源。这减少了源合成,这是一个缓慢且代价高昂的操作。

"script": { "source": """  emit(field('foo.bar.baz').get(null))   """ }
"script": { "source": """  emit($('foo.bar.baz', null))   """ }
按字母顺序排序
编辑

合成 _source 字段按字母顺序排序。JSON RFC 将对象定义为“零个或多个名称/值对的无序集合”,因此应用程序不应该关心,但在没有合成 _source 的情况下,原始顺序会被保留,并且某些应用程序可能会违反规范对该顺序做一些事情。

范围的表示
编辑

范围字段值(例如 long_range)始终表示为两侧都包含边界,并且会相应地调整边界。请参阅 示例

geo_point 值的精度降低
编辑

geo_point 字段的值在合成 _source 中以降低的精度表示。请参阅 示例

最大限度地减少源修改
编辑

可以避免特定对象或字段的合成源修改,但这会增加额外的存储成本。这通过参数 synthetic_source_keep 和以下选项进行控制

  • none:合成源与上面描述的原始源不同(默认)。
  • arrays:相应字段或对象的数组保留原始元素顺序和重复元素。此类数组的合成源片段不能保证与原始源完全匹配,例如数组 [1, 2, [5], [[4, [3]]], 5] 可能会按原样显示或以等效格式显示,例如 [1, 2, 5, 4, 3, 5]。确切的格式可能会在未来发生变化,以努力减少此选项的存储开销。
  • all:记录相应字段或对象的单例实例和数组的源。当应用于对象时,将捕获所有子对象和子字段的源。此外,将捕获数组的原始源,并在合成源中显示,且不做任何修改。

例如

resp = client.indices.create(
    index="idx_keep",
    settings={
        "index": {
            "mapping": {
                "source": {
                    "mode": "synthetic"
                }
            }
        }
    },
    mappings={
        "properties": {
            "path": {
                "type": "object",
                "synthetic_source_keep": "all"
            },
            "ids": {
                "type": "integer",
                "synthetic_source_keep": "arrays"
            }
        }
    },
)
print(resp)
const response = await client.indices.create({
  index: "idx_keep",
  settings: {
    index: {
      mapping: {
        source: {
          mode: "synthetic",
        },
      },
    },
  },
  mappings: {
    properties: {
      path: {
        type: "object",
        synthetic_source_keep: "all",
      },
      ids: {
        type: "integer",
        synthetic_source_keep: "arrays",
      },
    },
  },
});
console.log(response);
PUT idx_keep
{
  "settings": {
    "index": {
      "mapping": {
        "source": {
          "mode": "synthetic"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "path": {
        "type": "object",
        "synthetic_source_keep": "all"
      },
      "ids": {
        "type": "integer",
        "synthetic_source_keep": "arrays"
      }
    }
  }
}
resp = client.index(
    index="idx_keep",
    id="1",
    document={
        "path": {
            "to": [
                {
                    "foo": [
                        3,
                        2,
                        1
                    ]
                },
                {
                    "foo": [
                        30,
                        20,
                        10
                    ]
                }
            ],
            "bar": "baz"
        },
        "ids": [
            200,
            100,
            300,
            100
        ]
    },
)
print(resp)
const response = await client.index({
  index: "idx_keep",
  id: 1,
  document: {
    path: {
      to: [
        {
          foo: [3, 2, 1],
        },
        {
          foo: [30, 20, 10],
        },
      ],
      bar: "baz",
    },
    ids: [200, 100, 300, 100],
  },
});
console.log(response);
PUT idx_keep/_doc/1
{
  "path": {
    "to": [
      { "foo": [3, 2, 1] },
      { "foo": [30, 20, 10] }
    ],
    "bar": "baz"
  },
  "ids": [ 200, 100, 300, 100 ]
}

返回原始源,没有数组重复数据删除和排序

{
  "path": {
    "to": [
      { "foo": [3, 2, 1] },
      { "foo": [30, 20, 10] }
    ],
    "bar": "baz"
  },
  "ids": [ 200, 100, 300, 100 ]
}

可以通过将 index.mapping.synthetic_source_keep 设置为 arrays 在索引级别应用捕获数组源的选项。这适用于索引中的所有对象和字段,除了显式覆盖 synthetic_source_keep 设置为 none 的对象和字段。在这种情况下,存储开销自然会随着每个文档的源中存在的数组的数量和大小而增加。

支持合成源且无存储开销的字段类型

编辑

以下字段类型支持使用 doc_values 或 <stored-fields, 存储字段>> 中的数据合成源,并且不需要额外的存储空间来构建 _source 字段。

如果您启用 ignore_malformedignore_above 设置,则需要额外的存储空间来存储这些类型的被忽略的字段值。

禁用 _source 字段

编辑

虽然源字段非常方便,但它确实会在索引中产生存储开销。因此,可以按如下方式禁用它

resp = client.indices.create(
    index="my-index-000001",
    mappings={
        "_source": {
            "enabled": False
        }
    },
)
print(resp)
response = client.indices.create(
  index: 'my-index-000001',
  body: {
    mappings: {
      _source: {
        enabled: false
      }
    }
  }
)
puts response
const response = await client.indices.create({
  index: "my-index-000001",
  mappings: {
    _source: {
      enabled: false,
    },
  },
});
console.log(response);
PUT my-index-000001
{
  "mappings": {
    "_source": {
      "enabled": false
    }
  }
}

在禁用 _source 字段之前要三思

用户通常会在不考虑后果的情况下禁用 _source 字段,然后才后悔。如果 _source 字段不可用,则不支持许多功能

  • updateupdate_by_queryreindex API。
  • 在 Kibana Discover 应用程序中,不会显示字段数据。
  • 动态 突出显示
  • 能够将一个 Elasticsearch 索引重新索引到另一个索引,以更改映射或分析,或将索引升级到新的主要版本。
  • 能够通过查看索引时使用的原始文档来调试查询或聚合。
  • 将来,有可能自动修复索引损坏。

如果担心磁盘空间,请增加 压缩级别,而不是禁用 _source

_source 中包含/排除字段

编辑

一个仅限专家的功能是能够在文档索引后,但在存储 _source 字段之前,修剪 _source 字段的内容。

_source 中删除字段与禁用 _source 有类似的缺点,特别是您无法将文档从一个 Elasticsearch 索引重新索引到另一个索引。请考虑改用 源过滤

includes/excludes 参数(也接受通配符)可以按如下方式使用

resp = client.indices.create(
    index="logs",
    mappings={
        "_source": {
            "includes": [
                "*.count",
                "meta.*"
            ],
            "excludes": [
                "meta.description",
                "meta.other.*"
            ]
        }
    },
)
print(resp)

resp1 = client.index(
    index="logs",
    id="1",
    document={
        "requests": {
            "count": 10,
            "foo": "bar"
        },
        "meta": {
            "name": "Some metric",
            "description": "Some metric description",
            "other": {
                "foo": "one",
                "baz": "two"
            }
        }
    },
)
print(resp1)

resp2 = client.search(
    index="logs",
    query={
        "match": {
            "meta.other.foo": "one"
        }
    },
)
print(resp2)
response = client.indices.create(
  index: 'logs',
  body: {
    mappings: {
      _source: {
        includes: [
          '*.count',
          'meta.*'
        ],
        excludes: [
          'meta.description',
          'meta.other.*'
        ]
      }
    }
  }
)
puts response

response = client.index(
  index: 'logs',
  id: 1,
  body: {
    requests: {
      count: 10,
      foo: 'bar'
    },
    meta: {
      name: 'Some metric',
      description: 'Some metric description',
      other: {
        foo: 'one',
        baz: 'two'
      }
    }
  }
)
puts response

response = client.search(
  index: 'logs',
  body: {
    query: {
      match: {
        'meta.other.foo' => 'one'
      }
    }
  }
)
puts response
const response = await client.indices.create({
  index: "logs",
  mappings: {
    _source: {
      includes: ["*.count", "meta.*"],
      excludes: ["meta.description", "meta.other.*"],
    },
  },
});
console.log(response);

const response1 = await client.index({
  index: "logs",
  id: 1,
  document: {
    requests: {
      count: 10,
      foo: "bar",
    },
    meta: {
      name: "Some metric",
      description: "Some metric description",
      other: {
        foo: "one",
        baz: "two",
      },
    },
  },
});
console.log(response1);

const response2 = await client.search({
  index: "logs",
  query: {
    match: {
      "meta.other.foo": "one",
    },
  },
});
console.log(response2);
PUT logs
{
  "mappings": {
    "_source": {
      "includes": [
        "*.count",
        "meta.*"
      ],
      "excludes": [
        "meta.description",
        "meta.other.*"
      ]
    }
  }
}

PUT logs/_doc/1
{
  "requests": {
    "count": 10,
    "foo": "bar" 
  },
  "meta": {
    "name": "Some metric",
    "description": "Some metric description", 
    "other": {
      "foo": "one", 
      "baz": "two" 
    }
  }
}

GET logs/_search
{
  "query": {
    "match": {
      "meta.other.foo": "one" 
    }
  }
}

这些字段将从存储的 _source 字段中删除。

我们仍然可以搜索此字段,即使它不在存储的 _source 中。