bulk update 重复的文档id 导致更新性能下降?
bulk update 重复的文档id 导致更新性能下降?
Elasticsearch • 白衬衣 回复了问题 • 16 人关注 • 10 个回复 • 12274 次浏览 • 2017-09-14 09:32
@240475588 终于找到答案了。
update操作是分为两个步骤进行,即先根据文档ID做一次GET,得到旧版本的文档,然后在其基础上做更新再写回去,问题就出在这个GET上面。
在 core/src/main/java/org/elasticsea... 显示全部 »
update操作是分为两个步骤进行,即先根据文档ID做一次GET,得到旧版本的文档,然后在其基础上做更新再写回去,问题就出在这个GET上面。
在 core/src/main/java/org/elasticsea... 显示全部 »
@240475588 终于找到答案了。
update操作是分为两个步骤进行,即先根据文档ID做一次GET,得到旧版本的文档,然后在其基础上做更新再写回去,问题就出在这个GET上面。
在 core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java 这个类里面,get函数会根据一个realtime参数(默认是true),决定如何拿到文档。 public GetResult get(Get get, Function<String, Searcher> searcherFactory, LongConsumer onRefresh) throws EngineException {
assert Objects.equals(get.uid().field(), uidField) : get.uid().field();
try (ReleasableLock lock = readLock.acquire()) {
ensureOpen();
if (get.realtime()) {
VersionValue versionValue = versionMap.getUnderLock(get.uid());
if (versionValue != null) {
if (versionValue.isDelete()) {
return GetResult.NOT_EXISTS;
}
if (get.versionType().isVersionConflictForReads(versionValue.getVersion(), get.version())) {
throw new VersionConflictEngineException(shardId, get.type(), get.id(),
get.versionType().explainConflictForReads(versionValue.getVersion(), get.version()));
}
long time = System.nanoTime();
refresh("realtime_get");
onRefresh.accept(System.nanoTime() - time);
}
}
// no version, get the version from the index, we know that we refresh on flush
return getFromSearcher(get, searcherFactory);
}
可以看到realtime参数决定了GET到的数据的实施性。 如果设置为false,就从searcher里面拿,而searcher只能访问refresh过的数据,即刚写入的数据存在于index writter buffer里,暂时无法搜索到,所以这种方式拿到的数据是准实时的。 而默认的realtime:true,则决定了获取到的数据需要是实时的,也就是说需要也能够访问到writter buffer里的数据。 所以,中间有一个 refresh("realtime_get"); 的函数调用,这个函数调用会检查,GET的doc id是否都是可以被搜索到。 如果已经写入了但无法搜索,也就是写入到writter buffer里这种情况,就会强制执行一次refresh操作,让数据可以被searcher检索到,保证之后从getFromSearcher调用拿的是完全实时的数据。
实际上测试下来,也是这样,关闭自动刷新,写入一条文档,然后对该文档ID执行一个GET操作,就会看到有一个新的segment生成。 默认的realtime GET 强制刷新后,生成了新的segment file。
查了下文档,GET api调用时,url里可以带可选参数realtime=false,关闭实时功能: https://www.elastic.co/guide/en/elasticsearch/reference/5.6/docs-get.html#realtime
然而,不幸的是,update API的文档和源码都没有提供一个“禁用”实时性的参数。 update对GET的调用,传入的realtime是写死为true的。
至于为什么update一定需要实时GET,想了一下,是因为update允许对文档做部分字段更新。如果有2个请求分别更新了不同的字段, 可能先更新的数据只在writter buffer里,searcher里看不到,那后面的更新还是在老版本文档上做的,造成部分更新丢失。
另外一个问题,为啥之前的版本不是这种行为? 看了下2.4的GET方法源码,其没有采用refresh的方式让数据实时,而是直接访问的translog来保证GET的实时性。官方在这个变更里 https://github.com/elastic/elasticsearch/pull/20102 将其更新方式改为了refresh。理由是之前ES里有很多地方用translog维护数据的位置,使得很多操作变得很慢,去掉对translog的依赖可以提高性能。
但是很不幸,这个更改,对于短时间反复大量更新相同doc id的操作,会因为过于频繁的refresh短时间生成很多小segment,继而不断做短合产生性能损耗。 从上面链接里的讨论看,官方认为,在提升大多数应用场景性能情况下,对于这种较少见的场景下的性能损失是值得的,应该在应用层面解决。
因此,对于你的特定问题,要么优化应用数据架构,在应用层面合并相同doc id的更新,要么只能使用ES 2.x了。
update操作是分为两个步骤进行,即先根据文档ID做一次GET,得到旧版本的文档,然后在其基础上做更新再写回去,问题就出在这个GET上面。
在 core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java 这个类里面,get函数会根据一个realtime参数(默认是true),决定如何拿到文档。 public GetResult get(Get get, Function<String, Searcher> searcherFactory, LongConsumer onRefresh) throws EngineException {
assert Objects.equals(get.uid().field(), uidField) : get.uid().field();
try (ReleasableLock lock = readLock.acquire()) {
ensureOpen();
if (get.realtime()) {
VersionValue versionValue = versionMap.getUnderLock(get.uid());
if (versionValue != null) {
if (versionValue.isDelete()) {
return GetResult.NOT_EXISTS;
}
if (get.versionType().isVersionConflictForReads(versionValue.getVersion(), get.version())) {
throw new VersionConflictEngineException(shardId, get.type(), get.id(),
get.versionType().explainConflictForReads(versionValue.getVersion(), get.version()));
}
long time = System.nanoTime();
refresh("realtime_get");
onRefresh.accept(System.nanoTime() - time);
}
}
// no version, get the version from the index, we know that we refresh on flush
return getFromSearcher(get, searcherFactory);
}
可以看到realtime参数决定了GET到的数据的实施性。 如果设置为false,就从searcher里面拿,而searcher只能访问refresh过的数据,即刚写入的数据存在于index writter buffer里,暂时无法搜索到,所以这种方式拿到的数据是准实时的。 而默认的realtime:true,则决定了获取到的数据需要是实时的,也就是说需要也能够访问到writter buffer里的数据。 所以,中间有一个 refresh("realtime_get"); 的函数调用,这个函数调用会检查,GET的doc id是否都是可以被搜索到。 如果已经写入了但无法搜索,也就是写入到writter buffer里这种情况,就会强制执行一次refresh操作,让数据可以被searcher检索到,保证之后从getFromSearcher调用拿的是完全实时的数据。
实际上测试下来,也是这样,关闭自动刷新,写入一条文档,然后对该文档ID执行一个GET操作,就会看到有一个新的segment生成。 默认的realtime GET 强制刷新后,生成了新的segment file。
查了下文档,GET api调用时,url里可以带可选参数realtime=false,关闭实时功能: https://www.elastic.co/guide/en/elasticsearch/reference/5.6/docs-get.html#realtime
然而,不幸的是,update API的文档和源码都没有提供一个“禁用”实时性的参数。 update对GET的调用,传入的realtime是写死为true的。
至于为什么update一定需要实时GET,想了一下,是因为update允许对文档做部分字段更新。如果有2个请求分别更新了不同的字段, 可能先更新的数据只在writter buffer里,searcher里看不到,那后面的更新还是在老版本文档上做的,造成部分更新丢失。
另外一个问题,为啥之前的版本不是这种行为? 看了下2.4的GET方法源码,其没有采用refresh的方式让数据实时,而是直接访问的translog来保证GET的实时性。官方在这个变更里 https://github.com/elastic/elasticsearch/pull/20102 将其更新方式改为了refresh。理由是之前ES里有很多地方用translog维护数据的位置,使得很多操作变得很慢,去掉对translog的依赖可以提高性能。
但是很不幸,这个更改,对于短时间反复大量更新相同doc id的操作,会因为过于频繁的refresh短时间生成很多小segment,继而不断做短合产生性能损耗。 从上面链接里的讨论看,官方认为,在提升大多数应用场景性能情况下,对于这种较少见的场景下的性能损失是值得的,应该在应用层面解决。
因此,对于你的特定问题,要么优化应用数据架构,在应用层面合并相同doc id的更新,要么只能使用ES 2.x了。
bulk update 重复的文档id 导致更新性能下降?
回复Elasticsearch • 白衬衣 回复了问题 • 16 人关注 • 10 个回复 • 12274 次浏览 • 2017-09-14 09:32