【问题标题】:elasticsearch bulk indexing gets slower over time with constant number of indexes and documents随着时间的推移,随着索引和文档数量的增加,elasticsearch 批量索引会变慢
【发布时间】:2012-11-25 07:05:38
【问题描述】:

我正在体验使用 .NET NEST 客户端和 ElasticSearch 的批量索引性能会随着时间的推移而降低,索引数量和文档数量都是恒定的。

我们在一个 m1.large Amazon 实例上运行 ElasticSearch Version: 0.19.11, JVM: 23.5-b02,该实例带有 Ubuntu Server 12.04.1 LTS 64 位和 Sun Java 7。除了 Ubuntu 安装附带的之外,该实例上没有运行其他任何东西。

Amazon M1 大型实例:来自http://aws.amazon.com/ec2/instance-types/

7.5 GiB memory
4 EC2 Compute Units (2 virtual cores with 2 EC2 Compute Units each)
850 GB instance storage
64-bit platform
I/O Performance: High
EBS-Optimized Available: 500 Mbps
API name: m1.large

ES_MAX_MEM 设置为 4g,ES_MIN_MEM 设置为 2g

每天晚上,我们在 .NET 应用程序中使用 NEST 索引/重新索引约 15000 个文档。在任何给定时间,只有一个索引包含

首次安装服务器时,最初几天的索引和搜索速度很快,然后索引开始变得越来越慢。批量索引一次索引 100 个文档,一段时间后,完成批量操作最多需要 15 秒。在那之后,我们开始看到很多以下异常,并且索引停止了。

System.Net.WebException: The request was aborted: The request was canceled.
   at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization) : 

构建索引实现如下所示

private ElasticClient GetElasticClient()
{
    var setting = new ConnectionSettings(ConfigurationManager.AppSettings["elasticSearchHost"], 9200);
    setting.SetDefaultIndex("products");
    var elastic = new ElasticClient(setting);
    return elastic;
}

private void DisableRefreshInterval()
{
    var elasticClient = GetElasticClient();
    var s = elasticClient.GetIndexSettings("products");
    var settings = s != null && s.Settings != null ? s.Settings : new IndexSettings();
    settings["refresh_interval"] = "-1";
    var result = elasticClient.UpdateSettings(settings);
    if (!result.OK)
        _logger.Warn("unable to set refresh_interval to -1, {0}", result.ConnectionStatus == null || result.ConnectionStatus.Error == null ? "" : result.ConnectionStatus.Error.ExceptionMessage);
}

private void EnableRefreshInterval()
{
    var elasticClient = GetElasticClient();
    var s = elasticClient.GetIndexSettings("products");
    var settings = s != null && s.Settings != null ? s.Settings : new IndexSettings();
    settings["refresh_interval"] = "1s";
    var result = elasticClient.UpdateSettings(settings);
    if (!result.OK)
        _logger.Warn("unable to set refresh_interval to 1s, {0}", result.ConnectionStatus == null || result.ConnectionStatus.Error == null ? "" : result.ConnectionStatus.Error.ExceptionMessage);
}

public void Index(IEnumerable<Product> products)
{
    var enumerable = products as Product[] ?? products.ToArray();
    var elasticClient = GetElasticClient();
    try
    {
        DisableRefreshInterval();

        _logger.Info("Indexing {0} products", enumerable.Count());
        var status = elasticClient.IndexMany(enumerable as IEnumerable<Product>, "products");

        if (status.Items != null)
            _logger.Info("Done, Indexing {0} products, duration: {1}", status.Items.Count(), status.Took);

        if (status.ConnectionStatus.Error != null)
        {
            _logger.Error(status.ConnectionStatus.Error.OriginalException);
        }
    }
    catch(Exception ex)
    {
        _logger.Error(ex);
    }
    finally
    {
        EnableRefreshInterval();
    }
}

重新启动 elasticsearch 守护进程似乎没有任何区别,但删除索引并重新索引一切都会产生影响。但几天后,我们会遇到同样的索引缓慢问题。

我刚刚删除了索引,并在每次批量索引操作后重新启用刷新间隔后添加了优化,希望这样可以防止索引降级。

...
...
finally
{
    EnableRefreshInterval();
    elasticClient.Optimize("products");
}

我在这里做错了什么吗?

【问题讨论】:

  • 15000 对于 NEST 和 Elasticsearch 来说应该是轻而易举的事,几天后的实际文档数和索引大小是多少?
  • 自从这个问题以来,您的 ES 配置进展如何?我注意到这里肯定需要解决的一件事;您已将最大和最小 ES 堆内存设置为不同的值。它们应该是相同的;系统可用总内存的 50% 到 60% 之间。我目前在使用 NEST 作为客户端的回填应用程序中每分钟批量索引 100,000 个文档,因此 15,000 个应该是微不足道的。您还使用了一个非常旧的 ES 版本 - 自 19.11(当前为 0.20.4)以来,它有了一些很大的改进

标签: .net lucene amazon-ec2 elasticsearch nest


【解决方案1】:

抱歉 - 刚开始写另一条很长的评论,并认为我会把它全部放在答案中,以防它对其他人有好处......

ES_HEAP_SIZE

我在这里注意到的第一件事是您说您将 elasticsearch 的最大和最小堆值设置为不同的值。这些应该是一样的。在配置/init.d 脚本中应该有一个可以设置的 EX_HEAP_SIZE。请务必仅设置此值(而不是最小值和最大值),因为它将最小值和最大值设置为您想要的相同值。如果你不这样做,当你开始需要更多内存时,JVM 会阻塞 java 进程 - see this great article 最近在 github 上发生了一次中断(这里有一个引用):

设置 ES_HEAP_SIZE 环境变量,以便 JVM 对最小和最大内存使用相同的值。将 JVM 配置为具有不同的最小值和最大值意味着每次 JVM 需要额外的内存(达到最大值)时,它都会阻塞 Java 进程来分配它。结合旧的 Java 版本,这解释了我们的节点在向公共搜索开放时在引入更高负载和连续内存分配时表现出的暂停。 elasticsearch 团队建议设置为系统 RAM 的 50%。

还可以查看this great post,了解更多来自战壕的 elasticsearch 配置。

锁定内存以停止交换

根据我的研究,我发现您还应该锁定 Java 进程可用的内存量以避免内存交换。我不是这个领域的专家,但有人告诉我这也会影响性能。您可以在 elasticsearch.yml 配置文件中找到 bootstrap.mlockall。

升级

Elasticsearch 还是很新的。计划相当频繁地升级,因为在您使用的版本 (0.19.11) 和当前版本 (0.20.4) 之间引入的错误修复非常重要。有关详细信息,请参阅ES site。您使用的是 Java 7,这绝对是正确的方法,我从 Java 6 开始并很快意识到它还不够好,尤其是对于批量插入。

插件

最后,对于遇到类似问题的其他人,请安装一个体面的插件,以概览您的节点和 JVM。我推荐bigdesk - 运行 bigdesk,然后使用一些批量插入来访问 elasticsearch,并注意奇怪的堆内存模式、大量线程等,一切都在那里!

希望有人觉得这很有用!

干杯, 詹姆斯

【讨论】:

  • 在专用服务器上,除了设置bootstrap.mlockall = true 之外,您还应该在启动elasticsearch 的守护进程.sh 脚本中调用ulimit -l unlimited。 (这样做消除了对 elasticsearch 分配虚拟内存能力的人为限制,这是启用 mlock 优化所必需的。)设置 ulimit 可能非常繁琐,因此我还添加了 ulimit -a 行以将值转储到日志中。如果你做得对,运行top 将在启动时立即显示使用大致相同的VIRT 和RSS 大小的elasticsearch;如果 RSS 较小,则 mlockall 设置未生效。
【解决方案2】:

只是冒险猜测:

随着索引性能下降,您是否注意到索引占用了更多磁盘空间?

可能是这样,在重新索引时,不是替换旧索引或旧文档,而是添加一堆新文档,有效地使文档数量增加一倍,并且可能包含大量重复的数据。可能值得抓取一个陈旧、缓慢的索引并将其加载到某个查看器中进行调试(例如Luke)。如果您看到的文档比您预期的要多,那么您可能会考虑让您的重建创建一个新索引来替换旧索引。

由于重新启动守护程序并不能解决问题,我想可以排除保留打开的文件句柄、正在运行的进程、连接等,但我想检查这些统计信息,并确定我是否看到任何可疑的服务器上的行为。

此外,关于优化,您可能会看到一些性能增强,当然,但这是一项非常昂贵的操作。我建议仅在完全重建完成后运行优化,而不是在每次增量批量索引操作后运行。

【讨论】:

  • 感谢指点!我们并没有真正注意到任何增加的磁盘使用率,也没有注意到内存 och CPU。 Mem 通常约为 70%,而 CPU 则从空闲到 60-80%。自从我删除索引并在批量索引中添加Optimize 已经过去了四天。到目前为止,我们还没有看到索引退化,希望它会保持不变,但我确实对无法查明问题感到不安。如果情况再次出现,我会调查卢克并挖掘日志。
  • 当然,优化可以解决问题是相当合理的。累积大量未优化的删除和插入(更新只是删除+插入)肯定会降低搜索性能。在这种情况下,要查看的另一件事可能是您的合并策略,elasticsearch.org/guide/reference/index-modules/merge.html
猜你喜欢
  • 1970-01-01
  • 2016-02-13
  • 2015-11-23
  • 1970-01-01
  • 2014-02-04
  • 2020-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多