扩展PostgreSQL存储过程

时间:2014-11-05 23:10:23

标签: postgresql stored-procedures plpgsql

我当前对PostgreSQL存储过程的实现不能扩展,尽管问题很容易被分成并行进程/线程。


设置

与约会平台非常相似的应用程序,即用户注册,输入几个配置文件详细信息,并根据这些详细信息完成与所有其他用户的匹配。细节可以总计为60-70个属性,主要是布尔值,它们存储在user_attributes表中的用户记录中。因此,有一个大的user_attributes表,由用户ID和属性组成(其他配置文件数据存储在单独的表中)。由于性能问题,选择逐列方案,即防止获得一个用户的所有属性的附加查询。对于每个匹配,都有一个每用户匹配表,因此每个用户都有自己的表,包括user_id,other_user_id,matching_score。

我们希望每个数据库实例拥有高达30万用户,但有趣的是它可以扩展十倍,即高达300万用户。除此之外,我们可以通过分发到其他数据库实例来扩展。然而,我们开始在大约80k用户中遇到可扩展性问题。


问题

如前所述,由于性能问题,属性全部放在user_attributes表中,每个属性有一列。我们创建了一个存储过程(create_user),它将所有60-70个属性作为参数,在用户表中创建记录,然后从{{开始选择所有其他用户,包括其属性。 1}}表并开始计算匹配分数,最终结果被插入到新创建的user_attributes表中。

我们现在运行测试以查看设置如何执行(一次插入一个用户,直到达到300k用户),结果是大约80k用户,我们的CPU成为瓶颈。虽然测试机器配有4个内核/ 8个线程,但实际上只使用了一个。问题是每个其他用户匹配需要这么长时间(PL / pgSQL在这里表现得很差),但核心问题是所有这些匹配都发生在一个CPU上。例如,与所有其他用户的匹配可以分成8个不同的操作,每个操作占用UserXYZ_matches表记录的1/8,执行匹配并插入结果表。我们可以优化性能不佳的PL / pgSQL,但我不知道如何在其他CPU内核/线程上分配工作。


其他信息

请将整个方法的建议作为评论发布。我非常感谢有关如何做得更好的建议,但不是对这个具体问题的回答。

所有用户匹配表都存储在一个表空间中,该表空间由XFS和LVM条带化支持跨几个磁盘。用户匹配表的数量(每个用户一个)似乎不是可伸缩性问题(正如我们首先想到的)。因此磁盘不是问题,特定设置似乎涵盖了大量的表。

user_attributes的调用/查询应该是原子的,即基于事务的。这是我们的测试运行,但不一定是最终产品的硬性要求。

create_user过程基本上看起来像这样(整个过程太长了):

create_user

我知道高CPU使用率来自IF,ELSIF,END IF块(60-70)。同样,这可以进行优化,但是关于如何扩展此类存储过程的问题仍然存在。

当前运行测试的服务器如下所示,这很好地说明了问题:

nmon output

3 个答案:

答案 0 :(得分:2)

就我所知和文档阅读能力而言,PL / pgSQL不支持并行性,服务器也不会并行处理单个查询。因此,我倾向于说,进一步扩展需要在客户端进行并行化(通过具有单独连接的多个并发线程/进程插入的新用户)。

总的来说,您有一个固有的扩展问题,即添加新记录需要将其与所有其他记录进行比较。 N总记录的成本为N ^ 2,并且您已经将CPU占25%。添加第320,000条记录将是添加第80,000条记录的四倍,并且总共添加320,000条记录将至少十六次与添加80,000条记录一样昂贵。

可以想象,通过使用SELECT INTO查询而不是存储过程,您可以在某种程度上提高性能,但这不会提高渐近复杂度。您还可以考虑异步创建匹配表,以改善初始响应。

答案 1 :(得分:2)

(阐述约翰的答案;请接受他的不是我的):

PL / PgSQL可能是这项任务的不良选择。

数学和逻辑运算速度很慢。非常慢。 PL / PgSQL非常适合将一些SQL语句粘合在一起 - 其中大部分工作都是由SQL语句完成的。很多数学工作和逻辑都很糟糕。

它也无法利用任何CPU并行性。

此外,像你一样运行许多单独的小插件将非常慢。不要那样做。相反,如果你必须在PL / PgSQL中执行此操作,请让函数返回一组元素以便插入结果,并将其称为INSERT INTO target_table SELECT * FROM my_procedure(...)或类似的。它会快得多。

作为用户插入的一部分同步进行工作会使整个事情变得更糟,因为问题对用户来说更加明显。特别是因为,正如约翰所指出的那样,它在O(N 2 )上成比例,即呈二次方。

您的设计完全不可行,需要从头开始重新考虑。

我建议一种依赖于事实表的方法,就像在星型模式中一样。每个属性都是关于用户的“事实”。每个事实表都是(user_id, fact_value)元组。 (user_id, fact_value)上存在复合索引。

插入用户时,将其用户记录标记为等待用户记录中的标志与其自身匹配,并在插入用户的同一事务中将条目插入事实表中。

然后让您的应用程序处理一个用户队列,等待异步匹配作为后台任务。加入事实表以查找最相似的用户,即具有最相似值的最多事实。您的应用程序可以利用多个PostgreSQL连接进行并行操作,既可以同时处理多个用户,也可以进行部分连接以在临时表中生成临时结果,然后将其加入以查找最终结果。

答案 2 :(得分:0)

这是一个古老的步伐和有趣的阅读。当我试图找到是否可以在SP中使用线程类型的机制时,我遇到了这个。

在这个讨论主题中解释的设计(不确定这是否仍然相关),我们可以做的是将insert插入到一个存储过程中的user表中,并创建所有用于创建user_attribute表的逻辑,并且可以移动它触发器。 不确定您是否可以根据您的设计设计多级触发器的流量。 缺点是它可能会增加失败点的数量..但是一旦测试得好,就应该这样做。