了解数据存储区在Google App Engine中获取RPC

时间:2016-01-07 01:36:36

标签: google-app-engine google-cloud-datastore app-engine-ndb appstats

我出于性能原因在我的GAE应用程序中使用分片计数器(https://cloud.google.com/appengine/articles/sharding_counters),但我在理解为什么它如此缓慢以及如何加快速度方面遇到了一些麻烦起来。

背景
我有一个API,一次抓取一组20个对象,对于每个对象,它从计数器中获取一个总数以包含在响应中。

度量
随着Appstats打开并清除缓存,我注意到获取20个计数器的总数由datastore_v3.Get产生120个RPC,这需要2500ms。

思想
这似乎是相当多的RPC调用和相当多的时间来读取20个计数器。我认为这会更快,也许这就是我错的地方。它应该比这更快吗?

进一步检查
我更多地挖掘了统计数据,在get_count方法中查看这两行:

all_keys = GeneralCounterShardConfig.all_keys(name)
for counter in ndb.get_multi(all_keys):

如果我注释掉get_multi行,我看到datastore_v3.Get有20个RPC调用总共185ms。

正如预期的那样,这使得get_multi成为datastore_v3进行100次RPC调用的罪魁祸首。获取超过2500毫秒。我验证了这一点,但这是我感到困惑的地方。为什么用20个键调用get_multi导致100个RPC调用?

更新#1
我在GAE控制台中查看了Traces并查看了一些其他信息。他们也在那里显示了RPC调用的细分 - 但是在他们对#34;批处理的目标中,他们减少了远程过程调用的数量。"不知道如何在使用get_multi之外做到这一点。以为做了这个工作。这里有什么建议吗?

更新#2
以下是一些显示我正在观看的统计数据的屏幕截图。第一个是我的基线 - 没有任何计数器操作的功能。第二个是在为一个计数器调用get_count之后。这显示了6个datastore_v3.Get RPC的差异。

基线 enter image description here

在一个计数器上调用get_count enter image description here

更新#3
根据Patrick的要求,我在控制台跟踪工具中添加了信息截图。 enter image description here

2 个答案:

答案 0 :(得分:1)

尝试拆分遍历每个项目的for循环和实际的get_multi调用本身。如下所示:

all_values = ndb.get_multi(all_keys)
for counter in all_values:
    # Insert amazeballs codes here

我感觉它是其中之一:

  1. 生成器模式(for循环的产生)导致get_multi执行路径出现问题
  2. 也许您期望的项目数量与实际结果计数不匹配,这可能会导致GeneralCounterShardConfig.all_keys(名称)出现问题
  3. 分片数设置得太高。我已经意识到超过10个分片会导致性能问题。

答案 1 :(得分:0)

当我遇到类似问题时,我学到的一件事是get_multi会导致从您的应用程序发送多个RPC。看起来SDK中的默认值设置为每个获取1000个密钥,但我在生产应用程序中观察到的批量大小要小得多:更像是10(从内存开始)。

我怀疑这样做的原因是,在某些批量大小,实际上使用多个RPC实际上更好:您的应用程序有更多的RPC开销,但有更多的数据存储并行性。换句话说:这仍然是阅读大量数据存储对象的最佳方式。

但是,如果您不需要读取绝对最新值,则可以尝试设置db.EVENTUAL_CONSISTENCY选项,但这似乎仅在较旧的db库中提供,不在ndb。 (虽然它似乎也可以通过Cloud Datastore API获得)。

<强>详情

如果您查看App Engine SDK中的Python代码,特别是文件google/appengine/datastore/datastore_rpc.py,您将看到以下行:

max_count = (Configuration.max_get_keys(config, self.__config) or
             self.MAX_GET_KEYS)
...

if is_read_current and txn is None:
  max_egs_per_rpc = self.__get_max_entity_groups_per_rpc(config)
else:
  max_egs_per_rpc = None

...

pbsgen = self._generate_pb_lists(indexed_keys_by_entity_group,
                                 base_req.ByteSize(), max_count,
                                 max_egs_per_rpc, config)

rpcs = []
for pbs, indexes in pbsgen:
  rpcs.append(make_get_call(base_req, pbs,
                            self.__create_result_index_pairs(indexes)))

我对此的理解:

  • 从配置对象设置max_count,或将1000设置为默认值
  • 如果请求必须读取当前值,请从配置中设置max_gcs_per_rpc,或将10设置为默认值
  • 将输入键拆分为单独的RPC,同时使用max_countmax_gcs_per_rpc作为限制。

因此,这是由Python数据存储库完成的。