BigTable是慢还是我傻了?

Pau*_*jan 27 django google-app-engine bigtable django-models

我基本上拥有经典的多对多模型.用户,奖励以及用户和奖励之间的"多对多"表格映射.

每个用户拥有400个奖项的订单,每个奖励给予大约1/2个用户.

我想迭代所有用户的奖励并总结他们的积分.在SQL中,它将是多对多之间的表连接,然后遍历每个行.在具有MySQL实例的体面机器上,400行应该不是什么大问题.

在应用程序引擎上,我看到大约需要10秒才能完成总和.大部分时间都花在Google的数据存储中.这是cProfile的前几行

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      462    6.291    0.014    6.868    0.015 {google3.apphosting.runtime._apphosting_runtime___python__apiproxy.Wait}
      913    0.148    0.000    1.437    0.002 datastore.py:524(_FromPb)
     8212    0.130    0.000    0.502    0.000 datastore_types.py:1345(FromPropertyPb)
      462    0.120    0.000    0.458    0.001 {google3.net.proto._net_proto___parse__python.MergeFromString}

我的数据模型错了吗?我在查找错误吗?这是一个我必须处理缓存和bulkupdating(这将是一个王室痛苦的屁股)的缺点.

Ste*_*sop 20

可能是两者兼而有之;-)

如果你在奖励表上进行了400次查询,那么对于映射表上的查询返回的每个结果都有一个,那么我希望这很痛苦.由于BigTable认为返回1000个结果是在合理时间内运行的极限,因此查询的结果限制为1000.根据架构,我希望400个查询比返回400个结果的400个查询慢(400 log N vs.(log M)+ 400).

好消息是,在GAE上,memcaching包含所有奖项及其积分值的单一散列表非常简单(好吧,当我一会儿关注memcache文档时看起来非常简单.我不需要这样做然而).

此外,如果您还不知道,for result in query.fetch(1000)速度要快for result in query,并且您无论如何都限制为1000个结果.后者的优点是(1)如果你提前纾困可能会更快,(2)如果谷歌将限额提高到超过1000,它就会在不改变代码的情况下获得收益.

删除用户(或奖励)时,您可能也会遇到问题.我在一次测试中发现我可以在时间限制内删除300个对象.这些对象比映射对象更复杂,有3个属性和5个索引(包括隐式索引),而映射表可能只有2个属性和2个(隐式)索引.[编辑:刚才意识到在我知道db.delete()可以获取列表之前我做了这个测试,这可能要快得多].

BigTable不一定能做关系数据库设计好的事情.相反,它可以跨多个节点很好地分配数据.但是几乎所有网站都在单个数据库服务器上运行正常,因此并不严格需要BigTable所做的事情.

另一件事:如果您在单个http请求上执行了400个数据存储区查询,那么您会发现在达到请求固定配额之前,您已达到数据存储区固定配额.当然,如果你在配额范围内,或者如果你先点击其他东西,那么这可能与你的应用程序无关.但两个配额之间的比例大约是8:1,我将此视为谷歌期望我的数据模型的样子.

  • 很棒的建议.听起来我应该转移到正常的django模型并将其全部存储在MySQL上,直到遇到缩放问题. (4认同)
  • 或者您可以重新考虑您的模型.使用appengine数据存储区,您不希望在请求期间迭代行,而是快速拉出一行.一种方法是在写入时保持总计/小计/聚合是最新的,而不是在读取时.另一种方法是运行后台进程(使用cron或remote_api)以异步方式更新总计/小计/聚合. (4认同)
  • 如果您的数据在MySQL中比BigTable更好,我想您必须问自己为什么要使用应用程序引擎.如果有一个很好的理由(例如"免费托管"),那么我想是的,但对我而言,它看起来有点像黑客.BigTable(以及整个Google云端的分布)可能是GAE和任何旧LAMP堆栈之间唯一有趣的技术差异. (2认同)

Nic*_*son 19

我的数据模型错了吗?我在查找错误吗?

是的,是的,我很害怕.

就您的数据模型而言,到目前为止处理此问题的最佳方法是将总和存储在用户记录中,并在用户获得/失去奖励时更新它.在绝大部分时间里,每次计算他们的分数都没有意义,它将保持不变.如果您使"UserAward"实体键入"User"的子实体,您可以更新分数并在单个原子事务中插入或删除UserAward条目,从而确保您的计数始终准确.

onebyone指出你可以记得奖励表.这是个好主意,但鉴于数据量有限,更好的方法是将其存储在本地内存中.全局成员在HTTP请求之间保持不变,并且因为我认为您不经常更新奖励表,所以您不必担心使缓存失效.只需在第一个请求中加载它(甚至将其硬编码到您的源中).如果您确实更改了奖励列表,则部署新的次要更新将重置所有实例,从而导致重新加载.

对于查找,请记住,执行数据存储操作的大量成本是往返时间.一个get()操作,按ID查找一个或多个记录(你可以批量!)大约需要20-40毫秒.但是,查询大约需要160-200ms.因此,非规范化的力量.