【问题标题】:Service fabric reliable dictionary performance with 1 million keys具有 100 万个键的 Service Fabric 可靠的字典性能
【发布时间】:2018-04-11 01:46:05
【问题描述】:

我正在使用包含约 100 万个键的可靠字典来评估 Service Fabric 的性能。我得到了相当令人失望的结果,所以我想检查一下我的代码或我的期望是否错误。

我有一个用初始化的字典 dict = await _stateManager.GetOrAddAsync<IReliableDictionary2<string, string>>("test_"+id);

id 对于每次测试运行都是唯一的。

我用一个字符串列表填充它,比如 "1-1-1-1-1-1-1-1-1", "1-1-1-1-1-1-1-1-2", “1-1-1-1-1-1-1-1-3”.... 多达 576,000 个项目。字典中的值没有使用,我目前只是使用“1”。

将所有项目添加到字典大约需要 3 分钟。我必须一次将事务拆分为100,000,否则它似乎永远挂起(在需要CommitAsync()之前,事务中的操作数是否有限制?)

//take100_000 is the next 100_000 in the original list of 576,000
using (var tx = _stateManager.CreateTransaction())
{
    foreach (var tick in take100_000) {
        await dict.AddAsync(tx, tick, "1");
    }
    await tx.CommitAsync();
}

之后,我需要遍历字典来访问每一项:

using (var tx = _stateManager.CreateTransaction())
{

    var enumerator = (await dict.CreateEnumerableAsync(tx)).GetAsyncEnumerator();

    try
    {
        while (await enumerator.MoveNextAsync(ct))
        {
            var tick = enumerator.Current.Key;                
            //do something with tick                    
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

这需要 16 秒。

我不太关心写入时间,我知道它必须被复制和持久化。但是为什么要花这么长时间阅读呢? 576,000 个 17 字符的字符串键在内存中应不超过 11.5mb,并且值仅为单个字符,将被忽略。 Reliable Collections 不是缓存在 ram 中吗?遍历具有相同值的常规字典需要 13 毫秒。

然后我在一个空字典上调用了ContainsKeyAsync 576,000 次(在 1 个事务中)。这花了 112 秒。在任何其他数据结构上尝试这个可能需要大约 0 毫秒。

这是在本地 1 节点集群上。部署到 Azure 时,我得到了类似的结果。

这些结果可信吗?我应该检查任何配置吗?我做错了什么,还是我的期望非常不准确?如果是这样,是否有更适合这些要求的东西? (约 100 万个小键,无值,持久事务更新)

【问题讨论】:

  • 你可以使用 AddRange 命令之类的东西吗?在它之后 CommitAsync?我对经典数据库有类似的经验......如果单独保存更多实体 - 时间比 AddRange 命令长得多......
  • IReliableDictionary 没有这样的 AddRange 批量插入方法docs.microsoft.com/en-us/dotnet/api/… 但比起初始插入,我更关心读取速度。
  • 您不仅可以读取数据,还可以读取数据。请仅检查读取数据并测量延迟:
  • IAsyncEnumerable> enumerable = await dictionary.CreateEnumerableAsync(tx);

标签: azure-service-fabric


【解决方案1】:

好吧,不管它值多少钱:

  • 并非所有内容都存储在内存中。为了支持大型可靠集合,一些值被缓存,其中一些驻留在磁盘上,这可能会导致在检索您要求的数据。我听说在某个时候我们可能有机会调整缓存策略,但我认为它尚未实施。

  • 你一个一个地遍历数据读取记录。恕我直言,如果您尝试针对任何数据源发出 50 万个单独的顺序查询,结果将不会很乐观。我并不是说每一个 MoveNext() 都会导致一个单独的 I/O 操作,但我想说的是,总的来说它看起来不像是一次提取。

  • 这取决于您拥有的资源。例如,尝试在具有单个分区和三个副本的本地计算机上重现您的案例,我平均在 5 秒内获得记录。

考虑一种解决方法,想到的是:

  • 分块我尝试过将记录拆分为字符串数组,上限为 10 个元素(IReliableDictionary)。所以本质上它是相同数量的数据,但时间范围从 5 秒减少到 7 毫秒。我想如果你将你的项目保持在 80KB 以下,从而减少往返次数并保持 LOH 小,你应该会看到你的表现有所提升。

  • 过滤 CreateEnumerableAsync 有一个重载,允许您指定委托以避免从磁盘检索与过滤器不匹配的键的值。

  • State Serializer 如果您超越了简单的字符串,您可以开发自己的Serializer 并尝试减少针对您的类型产生的 I/O。

希望这是有道理的。

【讨论】:

  • 谢谢。我得到该数据将被分页到磁盘,但在我的情况下,我有小键并且想忽略values。要是有Set 类型就好了! 11mb 的密钥应该没什么大不了的。感谢您自己尝试并获得 5s,这很有用。因为我的密钥是可预测的,所以我应该能够将它们分块到具有可预测名称的存储桶中(80kb 的约 400 个项目 = 576,000 的 1,400 个密钥),同时编辑同一个存储桶时可能会出现更多的写入死锁。
  • @rockgecko 很高兴它对您有任何帮助。你会考虑将你的存储桶放在 Actor 中吗?由于 Actor 本质上是线程安全的,即使创建了数千个 Actor,它们也能正常工作,您可以试验它们的性能。此外,如果在整个 SF 集群出现故障的情况下丢失集合中的数据对您来说是可以的,您可以将您的 Actor 设置为仅将所有数据保留在内存中(易失性状态)。否则,如果 Actors impl 不假设在 Actor 激活时从磁盘加载所有状态,您可以模拟上面提到的 Set 类型。
猜你喜欢
  • 2017-12-07
  • 2018-12-06
  • 2019-01-08
  • 2018-07-04
  • 1970-01-01
  • 2017-01-28
  • 2017-03-20
  • 1970-01-01
  • 2016-10-12
相关资源
最近更新 更多