Wes*_*ork 3 postgresql index queue event query-performance
我有一个 PostgreSQL 表设置作为队列/事件源。
我非常希望保留事件的“顺序”(即使在处理队列项之后)作为 e2e 测试的来源。
我开始遇到查询性能下降的问题(可能是因为表膨胀),并且我不知道如何有效地查询不断变化的键上的表。
Postgres:v15
CREATE TABLE eventsource.events (
id serial4 NOT NULL,
message jsonb NOT NULL,
status varchar(50) NOT NULL,
createdOn timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT events_pkey PRIMARY KEY (id)
);
CREATE INDEX ON eventsource.events (createdOn)
Run Code Online (Sandbox Code Playgroud)
BEGIN; -- Start transaction
SELECT message, status
FROM eventsource.events ee
WHERE status = 'PENDING'
ORDER BY ee.createdOn ASC
FOR UPDATE SKIP LOCKED
LIMIT 10; -- Get the OLDEST 10 events that are pending
-- I found that having a batch of work items was more performant than taking 1 at a time.
...
-- The application then uses the entries as tickets for doing work as in "I am working on these 10 items, no one else touch"
...
UPDATE ONLY eventsource.events SET status = 'DONE' WHERE id = $id_1
UPDATE ONLY eventsource.events SET status = 'DONE' WHERE id = $id_2
UPDATE ONLY eventsource.events SET status = 'FAIL' WHERE id = $id_3
UPDATE ONLY eventsource.events SET status = 'DONE' WHERE id = $id_n
...
END; -- finish transaction
Run Code Online (Sandbox Code Playgroud)
多个工作人员从队列中取出一批工作项目,然后对它们进行操作并报告其状态。我希望尽可能少的重叠。
查看执行计划时,查询似乎必须遍历整个表才能获取处于“PENDING”状态的记录。
我想这可能是因为一ORDER BY ee.createdOn ASC开始的原因。但在查看执行计划后,我发现查询正在遍历整个表来搜索status,然后才对其进行排序。
我看到了部分索引,希望它可以减少查询的搜索空间。
CREATE INDEX ON eventsource.events (status)
WHERE status = 'PENDING'
Run Code Online (Sandbox Code Playgroud)
但我想我让事情变得更糟了......
记录以“PENDING”状态插入,然后几乎立即更改为“DONE”(或“FAIL”),因为应用程序正在消耗队列。我认为这可能每次都会破坏索引,然后在更新字段后从头开始重新创建它status(可能非常昂贵)。
更新部分索引的键/谓词有什么影响(如果很重要)我如何有效地根据变化的键过滤大表?
我的索引方法合理吗?
我的第一个想法是索引,但也许分区更适合这里?
如果分区键更改会发生什么?
它和破坏索引一样具有破坏性吗?
我知道默认索引类型是 B 树,在这种情况下 HASH 索引(或其他索引)会更好吗?
在幕后,更改 HASH 索引的索引键是否会导致破坏/重新创建索引表,就像使用 B 树一样?
我不确定部分索引的键与谓词的效果是什么。索引之间的有效区别是什么:
CREATE INDEX ON eventsource.events (status)
WHERE status = 'PENDING'
Run Code Online (Sandbox Code Playgroud)
和
CREATE INDEX ON eventsource.events (createdOn)
WHERE status = 'PENDING'
Run Code Online (Sandbox Code Playgroud)
我在这里使用它是createdOn因为它在我的抓取查询中,但我认为id也可以工作。
将索引键移动到不同的字段会影响索引创建/重新创建吗?在本例中,我将其从
status字段(会发生变化)移至字段createdOn,而字段不会发生变化。我不太明白这意味着什么。
我对Postgres文档对这种类型的部分索引有点不清楚。
timestamp( without time zone)你的整个设置很容易失败:
CREATE TABLE eventsource.events (
...
createdOn timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP -- !
..
Run Code Online (Sandbox Code Playgroud)
CURRENT_TIMESTAMP(又名now())返回timestamptz,而不是timestamp。
如果偶然、意外或恶意意图,任何会话设置了不同的值timezone,然后根据列默认值插入一行,您将得到不同的(错误的)本地时间,从而破坏排序顺序。而且你很难找出原因。不要这样做。尤其是没有这样的默认列。(LOCALTIMESTAMP遇到同样的问题:也取决于当前的timezone设置。)
有关的:
CREATE TABLE eventsource.event (
event_id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, message jsonb NOT NULL
, status text NOT NULL CHECK (status = ANY ('{PENDING,DONE,FAIL}'::text[])) -- more?
, created_on timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP -- !!!
);
Run Code Online (Sandbox Code Playgroud)
如果可能,请使用合法的小写标识符。看:
使用text并添加CHECK约束来强制执行合法状态。
IDENTITYserial比现代 Postgres更可取。看:
最重要的是,timestamptz按照顶部的说明使用。所有其他要点仅是建议。
使用部分索引,正如Charlieface已经建议的那样:
CREATE INDEX ON eventsource.event (created)
WHERE status = 'PENDING';
Run Code Online (Sandbox Code Playgroud)
对于您的用例来说,它的尺寸要小得多,并且提供排序的行。小索引的维护成本也较低。然而,会有大量的流失,因此索引会很快膨胀。看:
考虑autovacuum对表进行激进的设置。喜欢:
ALTER TABLE eventsource.event SET (autovacuum_vacuum_scale_factor = 0.03);
Run Code Online (Sandbox Code Playgroud)
的全局默认autovacuum_vacuum_scale_factor值为0.2。意思是,autovacuum在 20% 的表行 + autovacuum_vacuum_threshold(默认为 50)更改后触发。如果桌子很大,那么对于您的目的来说可能太懒了。在增加的维护成本和提高的查询性能之间找到平衡。
(created_on)为了其他目的,您可能需要也可能不需要完整的索引。
假设:
BEGIN; -- !!!
UPDATE eventsource.event
SET status = 'DONE'
WHERE event_id = (
SELECT event_id
FROM eventsource.event
WHERE status = 'PENDING'
ORDER BY created_on
LIMIT 1
FOR UPDATE SKIP LOCKED -- !!!
)
RETURNING *; -- or just what you need!
-- The application then processes the entries returned by the query and will then update them
-- ONLY in case of a failure !!!
-- Else just skip this:
UPDATE eventsource.event
SET status = 'FAIL'
WHERE event_id = $id_3; -- your failed ID
COMMIT;
Run Code Online (Sandbox Code Playgroud)
这种方法在并发写入负载下可靠地工作,并且永远不会阻塞。它在每个会话中仅锁定一行,从而最大限度地减少出现复杂情况的可能性。它立即锁定并更新行,这比稍后锁定和更新要快。在极少数发生失败的情况下,您需要进行第二次更新。但相比之下,这很便宜。
如果您需要(或只是想)锁定和处理多行,则可以以类似的方式工作。看:
| 归档时间: |
|
| 查看次数: |
502 次 |
| 最近记录: |