ElasticSearch 删除日志¶
https://blueprints.launchpad.net/searchlight/+spec/es-deletion-journaling
此特性启用对 ElasticSearch 中对象删除的跟踪,以便实现更好的连贯性和异步操作。
问题描述¶
作为提供 OpenStack 生态系统快照的服务,我们期望以下特性:
快照是最新且连贯的,与从其他各种 OpenStack 服务接收到的更新顺序无关。
否则,我们将因不可靠的数据而让用户失望。
背景¶
当创建时,ElasticSearch 文档存储在索引中。当删除时,我们野蛮地进行 Damnatio Memoriae(遗忘),ElasticSearch 文档将永久从索引中删除。在大多数情况下,这没有问题,文档也不会丢失。但计算机科学充满了边缘情况。其中一种边缘情况是由新的零停机时间重新索引功能 [1] 引入的。现在 Searchlight 需要记住已删除文档的存在。
随着零停机时间重新索引的添加,Searchlight 将同时从重新同步(使用 API 查询)和从服务发送的通知中对文档执行 CRUD 操作。所有这些都在分布式环境中进行。这些通知将导致异步的 ElasticSearch 操作序列。Searchlight 需要确保生态系统的状态始终正确地反映在 ElasticSearch 中,与服务抛出的通知的非确定性顺序无关。如果删除通知在相应的创建通知之前收到,此事件序列仍然需要导致正确的 ElasticSearch 状态。
鉴于这种严峻的现实,我们需要使 Searchlight 文档 CRUD 命令与顺序无关。这意味着需要一种跟踪已删除文档状态的方法。请参阅以下更具体的示例,了解我们正在使用此蓝图解决的问题。
作为补充说明,跟踪已删除文档将允许 Searchlight 轻松提供“delta”(增量)功能,如果将来需要的话。
此规范的先前版本详细介绍了设置删除标志并使用 ElasticSearch 的 TTL 功能来延迟删除文档。事实证明,当使用版本控制时,ElasticSearch 已经内置了此功能(有关说明,请参见 [3] 的底部)。
示例¶
这里提供一些更具体的示例可能会有所帮助。我们将 Nova 作为资源类型,这里再次提醒一下,插件命令如何映射到 ES 操作。
Nova 插件中的“创建文档”命令变成一个 ES “index” 操作,带有来自 Nova 的新负载。
Nova 插件中的“更新文档”命令变成一个 ES “index” 操作,带有来自 Nova 的新负载,该负载已经包含修改。需要注意的是,我们既不执行 ES “update” 操作,也不使用多个 ES 操作进行读取/修改/写入。从 ElasticSearch 的角度来看,更新命令与创建命令相同。
“删除文档”命令变成一个 ES “delete” 操作。
示例 #1(齐射)¶
考虑以下 Nova 通知:“创建 Obj1”、“修改 Obj1”、“修改 Obj1”、“修改 Obj1”和“删除 Obj1”。由于生态系统的分布式和异步特性,监听器发送通知的顺序可能与 ElasticSearch 接收操作的顺序不同。在某些情况下,最后一个 ElasticSearch 修改操作将在 ElasticSearch 删除操作之后到达。ElasticSearch 将看到以下操作(按此顺序):
PUT /searchlight/obj1 # Create Obj1
PUT /searchlight/obj1 # Modify Obj1
PUT /searchlight/obj1 # Modify Obj1
DELETE /searchlight/obj1 # Delete Obj1
PUT /searchlight/obj1 # Modify Obj1
在 ElasticSearch 执行所有操作后,最终结果将是 Nova 对象“Obj1”的索引。当好奇的用户查询时,Searchlight 会尴尬地返回幻影文档,就好像它真实存在一样。一丘之貉!对所有相关人员都不好。
示例 #2(诺查丹玛斯)¶
我们还需要处理 Nova 创建文档然后 Nova 删除文档的简单情况。这种情况在零停机时间重新索引工作 [1] 中可能很常见。此序列将导致 Nova 通知“创建 Obj2”和“删除 Obj2”。如果 ElasticSearch 创建操作在 ElasticSearch 删除操作之后到达,ElasticSearch 将看到以下操作(按此顺序):
DELETE /searchlight/obj2 # Delete Obj2
PUT /searchlight/obj2 # Create Obj2
在 ElasticSearch 执行这两个操作后,最终结果将是 Nova 对象“Obj2”的索引。这种不端行为是不正确的,也需要避免。
此示例还阐明了乱序删除通知的一个微妙之处。有时 ElasticSearch 会被要求删除一个(当前)不存在的文档。这种未来事件的预兆需要被解释并正确处理。
提议的变更¶
有一个索引设置名为 ‘index.gc_deletes’,默认值为 60 秒。当使用指定版本删除文档时,它不会立即从索引中删除。相反,它被标记为准备进行垃圾收集(在文档意义上,而不是内存意义上)。如果发布了具有更高版本的更新,则该文档将被复活。如果发布了具有更低版本的文档,则会引发 ConflictError,就像使用“存活”文档一样。已删除的文档不可见于搜索中。
由于这本质上与下面描述的实现相同,并且似乎完全支持于 ElasticSearch 2.x 中,因此没有必要自己实现它。我们可能会决定建议更高的 gc_deletes 值,但唯一需要的更改是在删除操作中传递版本(参见 [4])。
备选方案¶
另一种选择在下面的“先前设计”中表达。
先前设计¶
瞧!有了此蓝图,基本思想是保留已删除文档的状态,直到不再需要它。在高级别上,我们需要对 Searchlight 进行三个主要修改。
我们需要修改 ElasticSearch 索引映射。
我们需要修改删除功能以利用新的映射字段。
我们需要修改查询功能以了解新的映射字段。
ElasticSearch 索引映射修改¶
需要对每个索引定义的映射进行两个修改。
第一个修改是启用 TTL 字段。我们需要像这样定义特定索引的映射:
{
"mappings": {
"resource_index": {
"_ttl": { "enabled": true }
}
}
}
通过不指定默认 TTL 值,文档在显式设置 TTL 之前不会过期。这正是我们需要的。
第二个修改是在映射中添加一个新的元数据字段。元数据字段将命名为“deleted”,并且始终定义。当创建/修改文档时,该字段将设置为“False”。当删除文档时,该字段将设置为“True”。有人担心我们需要一个比布尔值更多的字段。版本或时间戳可能更合适。这是一个设计细节,如果需要可以在那时进一步完善。
Searchlight 删除功能修改¶
当删除文档时,我们需要设置 TTL 字段和元数据字段。这被视为对原始文档的修改。
如果文档尚不存在,我们需要创建文档并设置“deleted”和“TTL”字段。这将防止乱序的创建/更新操作成功。
Searchlight 查询功能修改¶
当查询文档时,我们需要修改查询以排除元数据指示文档已被删除的任何文档。我们还需要过滤掉元数据字段。
Searchlight 创建/修改功能修改¶
当创建文档时,映射需要添加新的“deleted”字段并启用 TTL 功能。“deleted”字段需要适当设置。如果“deleted”字段设置为 true,我们将不会修改文档。这些修改取决于版本功能到位 [2]。
配置更改¶
我们需要定义 TTL 值以确定已删除文档的持续时间。可以通过配置值覆盖此默认值。
设置 TTL 值不足以删除文档。同时,我们需要 ElasticSearch 运行其清除过程。清除过程将轮询所有文档并删除 TTL 值过期的文档。默认情况下,清除过程每 60 秒运行一次。可以通过配置值覆盖此默认值。
Deleted 字段选项¶
为了历史完整性,以下是考虑过的“deleted”元数据字段的不同选项。
元数据字段将命名为“deleted”,并且仅在删除文档时定义。当创建/修改文档时,此字段未定义。要检测文档是否已删除,我们将搜索此字段的存在。这简化了创建/修改代码,但使查询代码复杂化。
元数据字段将命名为“deleted”,并且始终定义。当创建/修改文档时,该字段将设置为“False”。当删除文档时,该字段将设置为“True”。这给创建/修改增加了一点工作,但简化了查询命令。
元数据字段将命名为“state”,并且始终定义。“state”的值将是文档的当前状态:“Created”(已创建)、“Modified”(已修改)或“Deleted”(已删除)。此选项需要更多的工作来区分“Modified”和“Create”,因为它们在插件中被视为相同。这将允许在未来将“delta”功能添加到 Searchlight 中。这项工作与选项 (2) 相同。
参考资料¶
- [1] 零停机时间重新索引工作在这里描述
https://blueprints.launchpad.net/searchlight/+spec/zero-downtime-reindexing
- [2] 添加到 ElasticSearch 文档的外部版本在这里描述
- [3] ElasticSearch 文档垃圾收集在这里讨论
- [4] 处理乱序通知的错误报告