postgresql - 扩展 PostgreSQL 存储过程

标签 postgresql stored-procedures plpgsql

我当前的 PostgreSQL 存储过程实现无法扩展,尽管这个问题可以很容易地拆分为并行进程/线程。


设置

一个行为非常类似于约会平台的应用程序,即用户注册,输入一些个人资料详细信息,并根据这些详细信息完成与所有其他用户的匹配。详细信息可以概括为 60-70 个属性,大部分是 bool 值,这些属性存储在 user_attributes 表的用户记录中。所以有一个很大的 user_attributes 表,由用户 ID 和属性组成(其他配置文件数据存储在单独的表中)。由于性能问题,选择了逐列属性方案,即防止为获取一个用户的所有属性而进行的额外查询。对于每个匹配,都有一个针对每个用户的匹配表,因此每个用户都有自己的表,其中包含 user_id、other_user_id、matching_score。

我们希望每个数据库实例拥有多达 30 万用户,但看看它如何扩展十倍(即多达 300 万用户)会很有趣。除此之外,我们可以通过分发到其他数据库实例来扩展。然而,我们在 8 万用户左右开始出现可扩展性问题。


问题

如前所述,出于对性能的考虑,所有属性都被放入一个 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 内核/线程之间分配工作。


其他信息

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

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

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 block 的数量 (60-70)。同样,这可以进行优化,但如何扩展此类存储过程的问题仍然存在。

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

nmon output

最佳答案

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

不过,总的来说,你有一个固有的缩放问题,因为要添加一个新记录,你需要将它与所有其他记录进行比较。对 N 条总记录执行此操作的成本为 N^2,并且您已经将 CPU 占用了 25%。添加第 320,000 条记录的成本是添加第 80,000 条的四倍,而添加总共 320,000 条记录的成本至少是添加 80,000 条的十六倍

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

关于postgresql - 扩展 PostgreSQL 存储过程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26768919/

相关文章:

c# - 无法从使用自定义运行时在 Docker 中运行 .NET Core 应用程序的 App Engine 连接到 Google Cloud SQL 中的 postgres

c# - 调用多个存储过程最快的代码是什么?

sql-server - 从 SQL Native 存储过程 (Hekaton) 中的表进行更新

database - 我可以在 PostgreSQL 的异常中参数化 SQLSTATE

sql - 生成一年中特定一周的日期

java - 创建 PostgreSql 触发器来更新库存数量

java - Postgres UUID 和 Hibernate → 未找到列

laravel - 限制特定用户对 POST、DELETE、PATCH、PUT 的访问

java - ClassNotFoundException : org. postgresql.util.PSQLException

mysql - 如何在mysql中定义返回值的存储过程