我出于性能原因在我的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的差异。
答案 0 :(得分:1)
尝试拆分遍历每个项目的for循环和实际的get_multi调用本身。如下所示:
all_values = ndb.get_multi(all_keys)
for counter in all_values:
# Insert amazeballs codes here
我感觉它是其中之一:
答案 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
设置为默认值max_count
和max_gcs_per_rpc
作为限制。因此,这是由Python数据存储库完成的。