我有大量“插入一次”的表,然后是只读的。即:在INSERT
记录的首字母之后,永远不会有任何UPDATE
或DELETE
语句。因此,磁盘上表的数据碎片很少。
我现在正在考虑needs_action
为每个表添加一个布尔字段。该字段只会更新一次,并且会缓慢/定期更新。作为 MVCC 的结果,当在VACUUM
之后出现(甚至更慢的时间表)时UPDATE
,表变得非常碎片化,因为它清除了最初插入的元组,并且它们随后被新的插入回填。
简而言之:添加这个“始终更新一次”字段将表格从设计上的最小碎片化变为设计上的高度碎片化。
是否有某种方法可以有效地实现单needs_action
记录标记,从而避免产生的表碎片?
.
.
.
.
<现在获取一些背景/补充信息... >
到目前为止考虑的一些选项...
冒着使这个问题变得庞大(因此被忽视?)的风险,以下是迄今为止已经考虑过的一些选项:
只需将列添加到每个表中,
UPDATE
然后不要担心会产生碎片,直到它实际上被证明是一个问题。- 我意识到这里过早的优化,但是随着一些表变大(> 1M,甚至> 1B),我宁愿把设计放在前面。
制作一个独立的跟踪表(对于每个表),仅包含 A)主表中的 PK 和 B)
needs_action
标志。AFTER INSERT
使用主表中的触发器在跟踪表中创建记录- 这将在主表上保留“仅插入”最小碎片级别......以增加(显着?)前期写入开销为代价
- 将跟踪表放在单独的模式中也可以巧妙地将功能与核心表分开
强制
needs_action
字段为 HOT 更新以避免元组复制- 需要索引
WHERE needs_action = TRUE
似乎排除了这个选项,但也许还有另一种方法可以快速找到它们?
- 需要索引
使用表格填充因子(50?)为不可避免的事情留出空间
UPDATE
- 例如:将 fillfactor 设置为 50 为 留出空间
UPDATE
,因此将其保持在同一页面中 UPDATE
但是......似乎只有一个,这将使表格包装分数永远保持在 50% 并占用两倍的存储空间?我还没有 100% 理解这个选项……还在学习。
- 例如:将 fillfactor 设置为 50 为 留出空间
在主表记录中找到一个特殊/神奇的字段/位,可以在没有 MVCC 影响的情况下进行旋转。
- 这似乎在 postgres 中不存在。即使这样做,也需要对其进行索引(或具有类似于
WHERE needs_action = TRUE
部分索引的其他快速查找机制) - 能够选择性地抑制特定列上的 MVCC 操作似乎在这里会很好(尽管肯定充满危险)
- 这似乎在 postgres 中不存在。即使这样做,也需要对其进行索引(或具有类似于
存储在 postgres
needs_action
之外(例如:作为<table_name>:needs_copying
redis 中的 PK 列表)以避免由于 mvcc 造成的碎片。- 不过,我担心保持这种原子性。也许
redis_fdw
在触发器中使用(或其他一些 fdw?)AFTER INSERT
可以保持原子性?我需要了解有关 fdw 功能的更多信息……不过,我能找到的所有 fdw 似乎都是只读的。
- 不过,我担心保持这种原子性。也许
运行具有背景碎片整理/压缩的精美视图,如这篇精彩的文章中所述
- 似乎对所有桌子都做了很多事情。
只需在 postgres 表中跟踪需要复制的 ids/PKs
- 只需将需要操作的 id 作为记录存储到快速惰性表中(例如:无 PK),以及
DELETE
操作完成时的记录 - 类似于
RPUSH
ing 到离线 redis 列表(但绝对是ACID) - 这似乎是目前最好的选择。
- 只需将需要操作的 id 作为记录存储到快速惰性表中(例如:无 PK),以及
还有其他选择吗?
更多关于驱动这个的具体用例......
我对如何避免这种碎片的一般情况感兴趣,但这里有更多关于当前用例的信息:
- 读取性能比所有表的写入性能重要得多(但避免疯狂的慢写显然是可取的)
- 一些表将达到数百万行。少数可能会达到数十亿行。
SELECT
查询将跨越广泛的表范围(不仅仅是最近的数据),范围可以从单个结果记录到 100k+- 表格设计可以从头开始...无需担心现有数据
- PostgreSQL 9.6