【发布时间】:2014-11-05 23:10:23
【问题描述】:
我当前的 PostgreSQL 存储过程实现无法扩展,但问题很容易拆分为并行进程/线程。
设置
一个行为与约会平台非常相似的应用程序,即用户注册,输入一些个人资料详细信息,然后根据这些详细信息与所有其他用户进行匹配。详细信息可以总结为 60-70 个属性,主要是布尔值,这些属性存储在 user_attributes 表中的用户记录中。所以有一个大的user_attributes 表,由用户ID 和属性组成(其他配置文件数据存储在单独的表中)。出于性能考虑,选择了逐列属性方案,即防止为获取一个用户的所有属性而进行额外查询。对于每个匹配,每个用户都有一个匹配表,因此每个用户都有自己的表,由 user_id、other_user_id、matching_score 组成。
我们希望每个数据库实例拥有多达 30 万用户,但看看它如何扩展十倍(即多达 300 万用户)会很有趣。除此之外,我们可以通过分发到其他数据库实例来进行扩展。然而,我们开始遇到大约 80k 用户的可扩展性问题。
问题
如前所述,出于性能考虑,所有属性都放在了user_attributes 表中,每个属性一列。我们创建了一个存储过程 (create_user),它将所有 60-70 个属性作为参数,在用户表中创建一条记录,然后开始从 user_attributes 表中选择所有其他用户,包括他们的属性并开始计算匹配分数,并将最终结果插入到新创建的UserXYZ_matches 表中。
我们现在运行测试以查看设置的执行情况(每次插入一个用户,直到达到 30 万用户),结果发现大约 8 万用户,我们的 CPU 成为瓶颈。虽然测试机配备了 4 核 / 8 线程,但实际上只使用了一个。问题是每个其他用户的匹配需要很长时间(PL/pgSQL 在这里的性能很差),但核心问题是所有这些匹配都发生在一个 CPU 上。例如,对所有其他用户的匹配可以分成8个不同的操作,每个操作取user_attributes表记录的1/8,执行匹配并插入到结果表中。我们可以优化性能不佳的 PL/pgSQL,但我不知道如何在其他 CPU 内核/线程之间分配工作。
其他信息
请作为 cmets 发布有关该方法的整体建议。我非常感谢有关如何总体上做得更好的建议,但不能作为对这个特定问题的答案。
所有用户匹配的表都存储在一个表空间中,该表空间由跨几个磁盘的 XFS 和 LVM 条带化支持。用户匹配表的数量(每个用户一个)似乎不是可伸缩性问题(正如我们最初认为的那样)。所以磁盘不是问题,而且大量的表似乎都被特定的设置所覆盖。
对create_user 的调用/查询应该是原子的,即基于事务的。这是为了我们的测试运行,但不一定是最终产品的硬性要求。
create_user 过程基本上是这样的(太长了,不能作为一个整体发布):
CREATE OR REPLACE FUNCTION create_user(...)
-- (1) input_user = INSERT INTO user_attributes VALUES (parameter0, parameter1, ...)
-- (2) create userXYZ_matching_table
-- (3) FOR row IN SELECT * FROM "user_attributes" WHERE "id" <> input_user."id" LOOP
-- -- repeat for every attribute
-- IF row.this_attribute = input_user.this_attribute THEN
-- match := match + 1
-- END IF;
-- -- finally
-- INSERT INTO userXYZ_matching_table VALUES (input.user.id, row.id, match)
-- END LOOP;
LANGUAGE PLPGSQL;
我知道高 CPU 使用率来自于 IF、ELSIF、END IF 块的数量(60-70)。同样,这可以优化,但如何扩展这种存储过程的问题仍然存在。
当前运行测试的服务器如下所示,很好地说明了问题:
【问题讨论】:
标签: postgresql stored-procedures plpgsql