BigTable是慢还是我傻了?

时间:2009-06-05 08:15:35

标签: django google-app-engine django-models bigtable

我基本上拥有经典的多对多模型。用户,奖励和用户与奖励之间的“多对多”表格映射。

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

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

在app引擎上,我看到大约需要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}

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

5 个答案:

答案 0 :(得分:20)

可能两者都有; - )

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

好消息是,在GAE上,记忆包含所有奖项及其积分值的单个哈希表非常简单(好吧,当我一会儿关注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,我将此视为谷歌期望我的数据模型看起来像一个暗示。

答案 1 :(得分:19)

  

我的数据模型错了吗?我在做什么   查找错了?

是的,是的,我很害怕。

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

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

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

答案 2 :(得分:1)

应用引擎的一个重要习惯是存储便宜但时间永远不会过剩。在app引擎中做多对多关系的最佳方法似乎是简单地存储双方的信息。 IE用户具有奖励列表并且每个奖励具有用户列表。要查找用户拥有的所有奖励,您只需查询特定用户的奖励表。

这个想法在这里得到了很好的证明:Building Scalable Complex Apps

答案 3 :(得分:0)

Google BigTable在Google分布式文件系统上运行。

数据已分发。也许400行mysql仍然有更好的,但对于更大的数据,谷歌BigTable可能会更快。

我认为这就是为什么他们鼓励我们使用memcache来加快速度。

答案 4 :(得分:0)

即使您提到BigTable,我认为您正在云SQL上实现关系数据库。

你的模型没问题,这是做这样事情的正确方法。我没有看到将聚合反规范化到用户表的充分理由。

您是否为快速表加入创建索引?这很简单。对于涉及表连接的所有字段,您可能需要BTree索引。无需索引聚合字段(您使用的是SUM)。基本上N:N表的两个外键都应该被索引。如果这些外键引用其他两个表的主键,那就足够了。

超过100行的顺序,外键上的简单BTree索引可以在吞吐量方面有明显的增加。

我在CloudSQL上运行数据库,其中一些边缘表有超过200万条记录。只有在250万条记录之后,我才考虑进行一些非规范化,这也是一些额外的索引,并且仍在聚合SUM。否则,每当添加新记录时,我都会对SUM字段进行不必要的更新。

只有当表格超过100万条记录时,我们才会考虑使用只读副本。那就是我们可以区分只读取某些表而不是写入的进程。

如果您使用的是Django,请注意根据文档实施LIMIT;因为它非常误导。当您在记录集上[:100](拼接)时,它不是您对SQL实际发送到SQL服务器的期望。我很难搞清楚这一点。当你计划做一些会产生很大规模的东西时,Django不是一个好的选择。但是在1000条记录的顺序,它会没事的。