Elasticsearch 7.4的 soft-deletes 是个什么鬼
新概念
从 Elasticsearch 7.4 开始,peer-recovery(副分片的恢复)不再依赖从主分片拉取 translog。在 6.0-7.3的版本中,Elasticsearch默认会保留512M 或12小时的 translog 用于 peer-recovery,副分片进行恢复时,只要待获取的差异数据是在 translog 所保留的数据范围的,就可以只从 trasnlog 复制差异的部分数据,而不用拖取整个分片。
现在,追踪主分片上的操作历史可以通过 Lucene 的新特性“软删除”来实现,不再依赖 translog。CCR 也会使用这个特性。与 Lucene 中原有的“删除”(或者说硬删除)相比,原有的“删除”会做一些标记, doc 在磁盘中还存在。然后在segment merge 的时候真正的删除这些 doc。现在既然要从 Lucene 获取操作历史,就要避免这种情况,让被删除的数据在 merge 时不被影响,所以“软删除”的概念其实和原来差不多:
- 还是标记删除,让文档看上去被删除了,用户查询不到,但磁盘上存在
- merge 操作不清理被”软删除“的 doc
- 在一定时间后,允许 merge 操作清理被”软删除“的 doc,实现保留一定期限的历史,而非无限期保留。
如果单单从效果来看,软删除和硬删除的区别就是 merge 之后,被软删除的 doc会被保留。而后通过一些其他的接口可以读到被软删除的文档。
Elasticsearch 的软删除原理
soft-deletes 是 Lucene 中实现的特性,本质上是增加一个额外的字段代表doc是否被软删除,执行删除操作的时候,新增了一个 doc,在这个 doc 中将代表软删除的字段标记为1。
Lucene 的软删除过程
我们用 Lucene 的 API 演示一下删除过程,首先初始化 IndexWriter,并指定哪个字段代表软删除的字段:
1 2 3 4 5 |
String softDeletesField = "soft_delete"; indexWriterConfig.setSoftDeletesField(softDeletesField); IndexWriter writer = new IndexWriter(dir, indexWriterConfig); |
然后新增一个 doc,docId 为1,这与之前的使用方式没有什么区别:
1 2 3 4 5 6 |
docId = 1; doc = new Document(); doc.add(new StringField("id", String.valueOf(docId), Field.Store.YES)); writer.updateDocument(new Term("id", String.valueOf(docId)), doc); |
接着用软删除的方式删除他,实际就是创建一个新的 doc,将docId设置为要删除的 docId,并将 softDelete 字段值设置为1:
1 2 3 4 5 6 7 8 |
docId = 1; doc = new Document(); doc.add(new StringField("id", String.valueOf(docId), Field.Store.YES)); doc.add(new NumericDocValuesField(softDeletesField, 1)); writer.softUpdateDocument(new Term("id", String.valueOf(docId)), doc, new NumericDocValuesField(softDeletesField, 0)); |
writer.softUpdateDocument完成了文档的软删除过程,接下来用不同的 reader 就可以读取到,或者过滤掉被软删除的文档。例如搜索分片时应该过滤掉被软删除的 doc,而 recovery 的时候需要读取所有操作历史,包括被软删除的文档。
Elasticsearch 中关于软删除的变更
现在看一下Elasticsearch中应用软删除之后,涉及到 Lucene读写API 的变更。
删除文档时
在 deleteInLucene 函数删除文档的时候,之前的硬删除使用Lucene deleteDocuments删除文档:
1 2 3 |
indexWriter.deleteDocuments(delete.uid()); |
使用软删除方式时,实现改为:
1 2 3 4 5 |
doc.add(softDeletesField); indexWriter.softUpdateDocument(delete.uid(), doc, softDeletesField); |
与我们上一个演示 Lucene 中软删除的例子类似。
搜索时
与之前的读取方式相比没有变化
1 2 3 |
SearchContext context = createContext(request); |
创建的Context为DefaultSearchContext,其中searcher中的 reader 为ElasticsearchDirectoryReader,这个 reader 不会读取到被软删除的文档。
恢复时
peer-recovery 的时候RecoverySourceHandler#recoverToTarget函数中,获取 translog 快照:
1 2 3 |
final Translog.Snapshot phase2Snapshot = shard.getHistoryOperations("peer-recovery", startingSeqNo); |
快照最终通过 LuceneChangesSnapshot类创建,其中会初始化一个indexSearcher负责读取,他的初始化方式如下:
1 2 3 4 |
this.indexSearcher = new IndexSearcher(Lucene.wrapAllDocsLive(engineSearcher.getDirectoryReader())); |
通过 Lucene.wrapAllDocsLive 返回一个IndexReader,这种方式创建的 reader可以获取到包括被软删除的所有的 doc。
目前存在的问题
软删除也带来了一些负面影响。截止目前的版本为止(7.5.2),对于 update 操作,他导致 refresh 变得很慢。以下面这个 UT 为例:
1 2 3 4 5 6 7 8 |
testRefresh(){ while (i++<100000) { engine.index(indexForDoc(createParsedDoc("1", null))); } engine.refresh("test", randomFrom(Engine.SearcherScope.values()), randomBoolean()); } |
上面这个10W 条 doc 的 update 之后,engine.refresh函数的运行在我的测试环境中消耗了100多秒的时间。
refresh慢导致了另外一个问题,他确实足够慢,以至于很可能会小于数据写入速度,indexing buffer 的内存来不及 refresh 到磁盘中。如果对节点执行 update 压测,你会发现indexWriter会暴涨到index_buffer_size配置的阈值,并持续占据这些内存。
当 indexWriter大于index_buffer_size配置的阈值,Elasticsearch 会对写入操作执行反压,降低分片的写入速度,被反压的分片除了在执行 update 的分片,其他索引的分片也可能会受影响,因此可能会导致整体写入速度下降。
总结
soft-deletes 本质上就是加了一个额外的字段表示文档被删除了,然后在通过一些其他 api 将soft-deletes的 doc 读出来。但是目前(v7.5.2)为止对 update 的影响比较大,如果你已经升级到这个版本,可以在写入请求中加上 ?refresh 参数,让每个请求都被 refresh,可以避免对indexWriter内存及后续的影响。
参考
https://www.elastic.co/cn/blog/follow-the-leader-an-introduction-to-cross-cluster-replication-in-elasticsearch
https://www.elastic.co/guide/en/elasticsearch/reference/current/release-notes-7.4.0.html
https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules-history-retention.html
(转载请注明作者和出处 easyice.cn ,请勿用于任何商业用途)
5 thoughts on “Elasticsearch 7.4的 soft-deletes 是个什么鬼”
为什么软删除会对update影响如此大,这个在写入过程中做了什么耗性能的操作了吗?
需要分析一下 lucene 的过程,太忙了,目前还没排期:(
测试中的这个问题在 ES 7.7.0中通过:
https://github.com/apache/lucene-solr/pull/1263
和
https://github.com/elastic/elasticsearch/pull/52950
这个两个PR修复了
感谢反馈 🤝
你好,没理解7.4后不通过translog replay 操作记录,而是通过 Lucene 的新特性“软删除”来实现。这个区别是什么呢?我看源码还是通过translog读取操作记录