BigTable 是慢还是我笨?

Posted

技术标签:

【中文标题】BigTable 是慢还是我笨?【英文标题】:Is BigTable slow or am I dumb? 【发布时间】:2010-10-31 14:04:27 【问题描述】:

我基本上有经典的多对多模型。一个用户、一个奖项以及用户和奖项之间的“多对多”表映射。

每个用户有大约 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 数据存储.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

我的数据模型错了吗?我做错了吗?这是我必须处理缓存和批量更新的缺点吗(这将是一个非常痛苦的事情)。

【问题讨论】:

+1 大声笑。我喜欢这个问题的标题! Google 的 BigTable 基本上不是哈希表吗? 那个***功能是等待...你为什么要在等待中花费 6 秒? @balpha:不,它没有实现为哈希表。 @workmad3:那是等待数据存储进程的应用程序进程(我不确定,它可能在也可能不在同一个节点上运行)。 【参考方案1】:

可能两者兼而有之;-)

如果您在 Awards 表上执行 400 次查询,每个查询返回一个映射表上的查询,那么我预计这会很痛苦。查询有 1000 个结果的限制是因为 BigTable 认为返回 1000 个结果是其在合理时间内运行的能力的限制。根据架构,我希望 400 个查询比返回 400 个结果的一个查询慢得多(400 log N vs. (log M) + 400)。

好消息是,在 GAE 上,memcache 包含所有奖项及其积分值的单个哈希表非常简单(好吧,当我不久前查看 memcache 文档时,看起来非常简单。我不需要做呢)。

另外,如果您还不知道,for result in query.fetch(1000)for result in query 快得多,而且您只能获得 1000 个结果。后者的优点是 (1) 如果您提前退出可能会更快,以及 (2) 如果 Google 将限制提高到 1000 以上,则无需更改代码即可获得好处。

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

BigTable 不一定能做关系数据库设计好的事情。相反,它将数据很好地分布在许多节点上。但是几乎所有网站都可以在单个数据库服务器上运行良好,并且存在瓶颈,因此并不严格需要 BigTable 所做的事情。

另一件事:如果您对单个 http 请求执行 400 个数据存储查询,那么您会发现您在达到请求固定配额之前就已经达到了数据存储固定配额。当然,如果你在配额之内,或者如果你先打别的东西,那么这可能与你的应用程序无关。但是这两个配额之间的比例大约是 8:1,我认为这暗示了 Google 期望我的数据模型是什么样的。

【讨论】:

很好的建议。听起来我应该转向普通的 django 模型并将其全部存储在 MySQL 上,直到遇到扩展问题。 如果您在 MySQL 中的数据比 BigTable 更好,我认为您必须问自己为什么要使用应用程序引擎。如果有充分的理由(例如“免费托管”),那么我想是的,但对我来说这看起来有点像黑客。 BigTable(一般分布在 Google 的云中)可能是 GAE 与任何旧 LAMP 堆栈之间唯一有趣的技术差异。 或者你可以重新考虑你的模型。使用 appengine 数据存储,您不想在请求期间迭代行,而是快速拉出一行。一种方法是在写入时而不是在读取时使总计/小计/汇总保持最新。另一种方法是运行后台进程(使用它们的 cron 或 remote_api)来异步更新总计/小计/聚合。 @dar:是的。事实上,我想说,目前使用 GAE 的所有最佳理由都意味着您应该学会在其限制范围内工作,而不是绕过它们。这是获得分布/可扩展性的唯一方法,这是它最有趣的特性。【参考方案2】:

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

是的,是的,我很害怕。

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

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

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

【讨论】:

谢谢。为了这个问题,我确实简化了我的问题。我确实更新了很多奖项,以及获奖者。为了返回“UserAwards”,我需要更多的信息,而不仅仅是积分。我想要奖项的图标,可能还有标题。您的批处理 get() 是否在引用上完成?当我有 400 个 UserAward 行并开始步行获取 userAward.award 时,它会通过 ID 获取并批量处理它们吗?那可能是救命稻草。 不可能以“自然”方式批量引用属性解析。您可以做的是调用 myent.properties()['propname'].get_value_for_datastore(myent) 来检索密钥,这使您可以批量处理。即使您更新了很多奖励,我仍然建议将它们全部存储在本地内存或 memcache 中,并通过某种方式使缓存无效。您的另一个选择是在每个实体上使用奖励的 ListProperty(Reference)。如果您不需要通过奖励来查找用户,您也可以设置 indexed=False 以减少开销。【参考方案3】:

应用引擎的一个重要习语是存储很便宜,但时间永远不会过剩。似乎在应用引擎中建立多对多关系的最佳方法是简单地存储双方的信息。 IE 一个用户有一个奖励列表,每个奖励都有一个用户列表。要查找用户获得的所有奖励,您只需查询某个用户的奖励表。

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

【讨论】:

【参考方案4】:

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

数据是分布式的。也许 400 行 mysql 仍然更好,但对于更大的数据,google BigTable 可能会更快。

我认为这就是他们鼓励我们使用 memcache 来提高速度的原因。

【讨论】:

【参考方案5】:

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

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

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

超过 100 行,一个简单的外键 BTree 索引可以显着提高吞吐量。

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

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

如果你使用的是 Django,当你根据他们的文档实现 LIMIT 时要小心;因为它非常具有误导性。当您在记录集上 [:100](拼接)时,实际发送到 SQL 服务器的 SQL 并不是您所期望的。我很难弄清楚这一点。当您计划做一些会产生非常大规模的事情时,Django 不是一个好的选择。但是在 1000 条记录的数量级上,就可以了。

【讨论】:

以上是关于BigTable 是慢还是我笨?的主要内容,如果未能解决你的问题,请参考以下文章

Cloud Bigtable:区域或区域服务

Bigtable---分布式的结构化数据存储系统

BigTable 设计 - BigTable 单元格大小的上限

如何将数据从一个 BigTable 表复制到另一个 BigTable 表

BigTable Design - BigTable单元格大小的上限

从 BigTable 中删除与 BigTable 中的正则表达式匹配的行