7.6 版本 update 后 refresh 慢,性能问题导致稳定性问题
故障现象 主分片自我恢复非常慢,或者 refresh 慢。refresh 期间的堆栈:
move 分片,或者重启节点,关闭索引等,应用集群状态时也需要 refresh,但是一直拿不到锁,从而导致节点无法处理集群状态:
故障原因
引入 softdelete 后导致的 update 性能问题,该问题触发条件是执行大量更新文档的操作,然后执行 refresh。该问题影响 7.7.0之前所有开启了 softdelete的版本。临时解决方式可以加大 refresh 频率。
解决方式 这是 Lucene 的问题: https://github.com/elastic/elasticsearch/issues/52146 https://issues.apache.org/jira/browse/LUCENE-9228
这两个 issues 所说的”对字段更新为相同值“,在 Elasticsearch 的场景中指的就是一个 es 里的 update 操作,即:put /index/_doc/1,在 lucene 中,更新为同一个值的意思是将 _ id 字段总是更新为 1;而 lucene 执行这个更新的主要消耗,在于将 __soft_deletes 字段设置为 1
假设 update 10w 次 最内层 applyDocValuesUpdates 函数被调用的次数为 shard 中 searcable=true && doc.count>0的分段个数+1,加 1 是因为最后一次为当前要刷下去的段,因此实际上 内层的applyDocValuesUpdates会被调用 2 次,第一次为已经 refresh 下去的,已存在的 segment,第二次为当前 refresh,还没落盘的 segment 因此对于上述每个 segment,执行 applyDocValuesUpdates,该函数的目的是记录哪些 doc 的值要被更新为指定值,保存在 docIdConsumer(dvUpdates)
applyDocValuesUpdates内部有多个循环嵌套,问题版本会产生 10w*10w 次迭代,而 770 只迭代 10w 次。
由两个主要循环组成,下面的循环用于遍历所有的更新操作,对于 762版本,会迭代 10w 次,而优化后的 770版本 只循环 1 次:
while ((bufferedUpdate = iterator.next()) != null)
下列循环迭代 10w 次,遍历倒排链:
while ((doc = docIdSetIterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
770 的优化方式是,在 FieldUpdatesBuffer.BufferedUpdateIterator#nextTerm 进行过滤,如果 刚刚处理的lastTerm和这次迭代到的相同,则跳过,避免后面再去调用 10w 次对倒排链的循环:
BytesRef nextTerm() throws IOException {
if (lookAheadTermIterator != null) {
//nextTerm之后会将bufferedUpdate设置为nextTerm返回的值
final BytesRef lastTerm = bufferedUpdate.termValue;
BytesRef lookAheadTerm;
//在排序的情况下,lookAheadTermIterator和termValuesIterator是相同
while ((lookAheadTerm = lookAheadTermIterator.next()) != null && lookAheadTerm.equals(lastTerm)) {
//进入这里代表这次遍历到的值和上次处理过的相同,直接跳过处理
BytesRef discardedTerm = termValuesIterator.next(); // discard as the docUpTo of the previous update is higher
assert discardedTerm.equals(lookAheadTerm) : "[" + discardedTerm + "] != [" + lookAheadTerm + "]";
assert docsUpTo[getArrayIndex(docsUpTo.length, termValuesIterator.ord())] <= bufferedUpdate.docUpTo :
docsUpTo[getArrayIndex(docsUpTo.length, termValuesIterator.ord())] + ">" + bufferedUpdate.docUpTo;
}
}
return termValuesIterator.next();