3

我有大量“插入一次”的表,然后是只读的。即:在INSERT记录的首字母之后,永远不会有任何UPDATEDELETE语句。因此,磁盘上表的数据碎片很少。

我现在正在考虑needs_action为每个表添加一个布尔字段。该字段只会更新一次,并且会缓慢/定期更新。作为 MVCC 的结果,当在VACUUM之后出现(甚至更慢的时间表)时UPDATE,表变得非常碎片化,因为它清除了最初插入的元组,并且它们随后被新的插入回填。

简而言之:添加这个“始终更新一次”字段将表格从设计上的最小碎片化变为设计上的高度碎片化。

是否有某种方法可以有效地实现单needs_action记录标记,从而避免产生的表碎片?

.

.

.

.


<现在获取一些背景/补充信息... >


到目前为止考虑的一些选项...

冒着使这个问题变得庞大(因此被忽视?)的风险,以下是迄今为止已经考虑过的一些选项:

  1. 只需将列添加到每个表中,UPDATE然后不要担心会产生碎片,直到它实际上被证明是一个问题。

    • 我意识到这里过早的优化,但是随着一些表变大(> 1M,甚至> 1B),我宁愿把设计放在前面。
  2. 制作一个独立的跟踪表(对于每个表),仅包含 A)主表中的 PK 和 B)needs_action标志。AFTER INSERT使用主表中的触发器在跟踪表中创建记录

    • 这将在主表上保留“仅插入”最小碎片级别......以增加(显着?)前期写入开销为代价
    • 将跟踪表放在单独的模式中也可以巧妙地将功能与核心表分开
  3. 强制needs_action字段为 HOT 更新以避免元组复制

    • 需要索引WHERE needs_action = TRUE似乎排除了这个选项,但也许还有另一种方法可以快速找到它们?
  4. 使用表格填充因子(50?)为不可避免的事情留出空间UPDATE

    • 例如:将 fillfactor 设置为 50 为 留出空间UPDATE,因此将其保持在同一页面中
    • UPDATE但是......似乎只有一个,这将使表格包装分数永远保持在 50% 并占用两倍的存储空间?我还没有 100% 理解这个选项……还在学习。
  5. 在主表记录中找到一个特殊/神奇的字段/位,可以在没有 MVCC 影响的情况下进行旋转。

    • 这似乎在 postgres 中不存在。即使这样做,也需要对其进行索引(或具有类似于WHERE needs_action = TRUE部分索引的其他快速查找机制)
    • 能够选择性地抑制特定列上的 MVCC 操作似乎在这里会很好(尽管肯定充满危险)
  6. 存储在 postgresneeds_action 之外(例如:作为<table_name>:needs_copyingredis 中的 PK 列表)以避免由于 mvcc 造成的碎片。

    • 不过,我担心保持这种原子性。也许redis_fdw在触发器中使用(或其他一些 fdw?)AFTER INSERT可以保持原子性?我需要了解有关 fdw 功能的更多信息……不过,我能找到的所有 fdw 似乎都是只读的。
  7. 运行具有背景碎片整理/压缩的精美视图,如这篇精彩的文章中所述

    • 似乎对所有桌子都做了很多事情。
  8. 只需在 postgres 表中跟踪需要复制的 ids/PKs

    • 只需将需要操作的 id 作为记录存储到快速惰性表中(例如:无 PK),以及DELETE操作完成时的记录
    • 类似于RPUSHing 到离线 redis 列表(但绝对是ACID)
    • 这似乎是目前最好的选择。

还有其他选择吗?


更多关于驱动这个的具体用例......

我对如何避免这种碎片的一般情况感兴趣,但这里有更多关于当前用例的信息:

  1. 读取性能比所有表的写入性能重要得多(但避免疯狂的慢写显然是可取的)
  2. 一些表将达到数百万行。少数可能会达到数十亿行。
  3. SELECT查询将跨越广泛的表范围(不仅仅是最近的数据),范围可以从单个结果记录到 100k+
  4. 表格设计可以从头开始...无需担心现有数据
  5. PostgreSQL 9.6
4

2 回答 2

2

我只是将填充因子降低到默认值 100 以下。

根据行的大小,使用 80 或 90 之类的值,这样一些新行仍将适合块。更新后,旧行可以被“修剪”并由下一个事务进行碎片整理,以便可以重用空间。

50 的值似乎太低了。诚然,这将为同时更新的块中的所有行留出空间,但这不是您的用例,对吗?

于 2017-05-10T09:08:35.550 回答
1

您可以尝试使用继承的表。这种方法不直接支持对表进行 PK,但它可能由触发器解决。

CREATE TABLE data_parent (a int8, updated bool); 
CREATE TABLE data_inserted (CHECK (NOT updated)) INHERITS (data_parent);
CREATE TABLE data_updated (CHECK (updated)) INHERITS (data_parent);



CREATE FUNCTION d_insert () RETURNS TRIGGER AS $$
BEGIN
    NEW.updated = false;
    INSERT INTO data_inserted VALUES (NEW.*);

    RETURN NULL;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER d_insert BEFORE INSERT ON data_parent FOR EACH ROW EXECUTE PROCEDURE d_insert();


CREATE FUNCTION d_update () RETURNS TRIGGER AS $$
BEGIN
    NEW.updated = true;
    INSERT INTO data_updated VALUES (NEW.*);
    DELETE FROM data_inserted WHERE (data_inserted.*) IN (OLD);

    RETURN NULL;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER d_update BEFORE INSERT ON data_inserted FOR EACH ROW EXECUTE PROCEDURE d_update();


-- GRANT on d_insert to regular user
-- REVOKE insert / update to regular user on data_inserted/updated


INSERT INTO data_parent (a) VALUES (1);


SELECT * FROM ONLY data_parent;
SELECT * FROM ONLY data_inserted;
SELECT * FROM ONLY data_updated;



INSERT 0 0
 a | updated 
---+---------
(0 rows)

 a | updated 
---+---------
 1 | f
(1 row)

 a | updated 
---+---------
(0 rows)


UPDATE data_parent SET updated = true;

SELECT * FROM ONLY data_parent;
SELECT * FROM ONLY data_inserted;
SELECT * FROM ONLY data_updated;


UPDATE 0
 a | updated 
---+---------
(0 rows)

 a | updated 
---+---------
(0 rows)

 a | updated 
---+---------
 1 | t
(1 row)
于 2017-05-09T20:09:34.787 回答