PostgreSQL:如何防止SQL存储过程并发访问?(包括SELECT FOR UPDATE)



我有一个执行一些负载平衡算法的存储过程的麻烦。服务远程调用这个过程,这个过程必须用良好的端口应答以转发消息。

问题是我想要一个很好的性能(500个上限或消息/秒)。因此,服务每秒向数据库请求500次,数据库是将消息转发给它的好端口。

注意:我确信我运行数据库服务器的虚拟机有足够的资源达到500个请求/秒。事实上,对于更简单的查询(Select, update…),我达到了这个数字。

下面是存储过程:
 -- Function: loadbalancing(integer)
 -- DROP FUNCTION loadbalancing(integer);
 CREATE OR REPLACE FUNCTION loadbalancing(num integer)   RETURNS
 integer AS $BODY$DECLARE
 -- Declarations 
 port_selected integer; 
 total_call_attempts integer;
 BEGIN
 --
 -- ***   ***   ***   ***   ***   ***
 -- *** Load Balancing Algorithm  ***
 -- ***   ***   ***   ***   ***   ***
 --
 -- ***************
 -- *** locking ***
 -- ***************
 perform number_of_call_attempt, destination_rate FROM load_distribution WHERE loadb_id = num FOR UPDATE;
 -- *****************************
 -- *** select the right port ***
 -- ***************************** 
select port_number into port_selected from  ( select *, row_number()          over(order by destination_ratio asc,
 destination_target_rate desc, port_number asc) as rn from
 load_distribution where active = true and loadb_id = num ) t where
 t.rn = 1;
 -- ******************************************************
 -- *** Update the number of call attempts at one port ***
 -- ****************************************************** 
 UPDATE load_distribution SET number_of_call_attempt = number_of_call_attempt
 + 1 WHERE port_number = port_selected AND loadb_id = num;
 -- **************************************
 -- *** Get the total of call attempts ***
 -- **************************************
 SELECT SUM(number_of_call_attempt) into total_call_attempts FROM
 load_distribution WHERE loadb_id = num;
 -- *******************************
 -- *** Update destination rate ***
 -- ******************************* 
UPDATE load_distribution SET destination_rate = (number_of_call_attempt / total_call_attempts)
 WHERE loadb_id = num;
 -- ********************************
 -- *** Update destination ratio ***
 -- ******************************** 
 UPDATE load_distribution SET destination_ratio = (destination_rate / destination_target_rate) WHERE
 loadb_id = num;
 -- ***********************************************
 -- *** Return a port to the requesting service ***
 -- *********************************************** 
RETURN port_selected;
 END;$BODY$   LANGUAGE plpgsql VOLATILE   COST 100; 
 ALTER FUNCTION loadbalancing(integer)   OWNER TO postgres;

我遇到的问题是,这最多适用于70或100个请求/秒。当我开始达到200或更多的请求/秒,Postgres数据库发送一个超时异常。

你认为这个问题发生的原因是什么?至于我,我认为这是一个并发访问的问题。实际上,该过程是在其他列值的函数中选择端口号。每次调用/请求到达时,这些列的值都会更改。紧跟其后的请求必须使用已更改的值(干净状态),而不能使用脏状态的值或未更改的值(如果下一个请求在前一个请求更改值时或之前到来)。

因此,我想到的一个解决方案是使用SELECT FOR UPDATE进行显式锁定。不幸的是,我在我的算法中放入的那个似乎不起作用,或者它不是一个好的解决方案。我是锁和存储过程的新手。

那么,你认为解决这个问题的办法是什么?

我认为这是在RDBMS中尝试做的一件非常糟糕的事情。我会使用易失性存储,可能是非事务性的。这里最好的做法几乎肯定是使用现有的、建立良好的负载平衡工具,或者如果您必须自己动手,使用专为小原子数据块的极低延迟快速查询而设计的技术。

在pl/pgsql存储过程中,在多个查询中执行多个窗口和聚合传递是最糟糕的方法。特别是因为你基本上是序列化对行的访问。

您可能不需要这个分配是精确的,无错误的,并完全尊重分配的比例。相反,您可能需要它非常快,并且大致足够好。PL/PgSQL不是这项工作的正确工具,以至于几乎没有任何理由建议您改进它。这是行不通的。

除了锁定问题之外,这种快速更新过程还会产生大量的 MVCC膨胀,这反过来又会产生更多的真空清理负载。All for ACID保证您既不需要也不想要。

如果您确实必须使用这种方法,以下几点将会有所帮助:

    调用进程的事务的
  • SET synchronous_commit = off
  • 使用UNLOGGED表作为端口映射表
  • 一条 UPDATE语句中设置destination_ratio, destination_ratenumber_of_call_attempt
  • 使用ORDER BY ... LIMIT 1在你的排序中抓取所需端口,而不是row_number()

…但实际上,整个方法注定要失败。它敲打存储,为行锁做写,为UPDATE做写,每次都做聚合传递。尽管这可能都是在shared_buffers中,但仍存在大量的流失。使用UNLOGGED表和大约30的FILLFACTOR表可能会有所帮助,并结合异步提交。一点。还是会比较慢。

别再做无谓的重复工作了。使用现有的、建立良好的负载平衡系统。您需要一些可以将统计数据保存在内存中、延迟更新的东西,并使用read-copy-update、CPU原子等技巧来允许对共享数据结构进行非常高的并发访问。一些既不安全也不持久的东西,也不试图完全公平地分配。

最新更新