【问题标题】:Calculating and saving space in PostgreSQL在 PostgreSQL 中计算和节省空间
【发布时间】:2011-02-27 08:01:20
【问题描述】:

我在 pg 中有一个表格,如下所示:

CREATE TABLE t (
    a BIGSERIAL NOT NULL,               -- 8 b
    b SMALLINT,                         -- 2 b
    c SMALLINT,                         -- 2 b
    d REAL,                             -- 4 b
    e REAL,                             -- 4 b
    f REAL,                             -- 4 b
    g INTEGER,                          -- 4 b
    h REAL,                             -- 4 b
    i REAL,                             -- 4 b
    j SMALLINT,                         -- 2 b
    k INTEGER,                          -- 4 b
    l INTEGER,                          -- 4 b
    m REAL,                             -- 4 b
    CONSTRAINT a_pkey PRIMARY KEY (a)
);

以上内容每行最多加起来 50 个字节。我的经验是,我需要另外 40% 到 50% 的系统开销,甚至没有任何用户创建的上述索引。因此,每行大约 75 个字节。我将在表中有很多很多行,可能超过 1450 亿行,因此该表将推送 13-14 TB。如果有的话,我可以使用什么技巧来压缩这张桌子?下面是我可能的想法...

real 值转换为integer。如果它们可以存储为smallint,则每个字段可以节省 2 个字节。

将列 b .. m 转换为数组。我不需要搜索这些列,但我确实需要能够一次返回一列的值。所以,如果我需要 g 列,我可以这样做

SELECT a, arr[5] FROM t;

我会使用数组选项节省空间吗?会不会有速度惩罚?

还有其他想法吗?

【问题讨论】:

  • PostgreSQL 文档建议避免对exact floating point number representation 使用realdouble precision 类型(例如存储会计值)。请改用numeric 数据类型。如果这些 real 值用于会计或精确结果操作,只是一个提示。

标签: postgresql database-design storage bigdata


【解决方案1】:

我认为将多个数值字段存储在一个数组中没有任何好处(也没有什么可失去的)。

size of each numerical type 已明确记录,您应该简单地使用与所需范围分辨率兼容的最小尺寸类型;这就是你所能做的。

我不认为(但我不确定)是否对沿行的列有一些字节对齐要求,在这种情况下,列的重新排序可能会改变使用的空间 - 但我不认为所以。

顺便说一句,每行有一个修复开销,大约是 23 bytes

【讨论】:

  • 从 9.2 开始,行标题为每行 24 个字节,页偏移量为 4 个字节(存储在页标题中),或每行 28 个字节。还有其他项目可以发挥作用,例如每 8 列 1 个字节支持 NULL 值(NULL 值存储为位掩码)。
  • @Sean:这不太正确。根据手册hereThere is a fixed-size header (occupying 23 bytes on most machines), followed by an optional null bitmap, an optional object ID field,行头(HeapTupleHeader)有23个字节,而不是24个。区别是相关的,最多 8 列的表的 NULL 位掩码适合这个备用字节,使这些表有效地释放 NULL 存储。
  • 正确,但是由于数据类型的对齐,在字节 23 和 24 之间几乎可以肯定存在一个漏洞,并且从字节 25 开始一个 INT 开始。所以header只有23个字节,但是消耗的空间是24个字节。
【解决方案2】:

“列俄罗斯方块”

其实你可以做某事,但这需要更深入的了解。关键字是对齐填充Every data type has specific alignment requirements.

您可以通过有利的顺序将它们在列之间最小化。以下(极端)示例会浪费大量物理磁盘空间:

CREATE TABLE t (
    e int2    -- 6 bytes of padding after int2
  , a int8
  , f int2    -- 6 bytes of padding after int2
  , b int8
  , g int2    -- 6 bytes of padding after int2
  , c int8
  , h int2    -- 6 bytes of padding after int2
  , d int8)

要每行节省 24 字节,请改用:

CREATE TABLE t (
    a int8
  , b int8
  , c int8
  , d int8
  , e int2
  , f int2
  , g int2
  , h int2)   -- 4 int2 occupy 8 byte (MAXALIGN), no padding at the end

db小提琴here
sqlfiddle

根据经验,如果先放置 8 字节列,然后放置 4 字节、2 字节和 1 字节列,则不会出错。

booleanuuid (!) 和其他一些类型不需要对齐填充。 textvarchar 和其他“varlena”(可变长度)类型名义上需要“int”对齐(大多数机器上为 4 个字节)。但我观察到磁盘格式没有对齐填充(与 RAM 不同)。最终,我在note in the source code:找到了解释

还请注意,我们允许在存储“打包”varlenas 时违反名义对齐; TOAST 机制负责从大多数代码中隐藏它。

所以“int”对齐仅在包含单个前导长度字节的(可能压缩的)数据超过 127 个字节时才强制执行。然后 varlena 存储切换到四个前导字节并需要“int”对齐。

通常,播放“俄罗斯方块”,每行最多可以节省几个字节。在大多数情况下,这些都不是必需的。但是对于数十亿行,这可能意味着几千兆字节。

您可以使用函数pg_column_size() 测试实际的列/行大小。
某些类型在 RAM 中占用的空间比在磁盘上的空间多(压缩或“打包”格式)。使用pg_column_size() 测试相同的值(或值行与表行)时,常量(RAM 格式)的结果比表列的结果更大。

最后,有些类型可以是compressed or "toasted"(离线存储)或两者兼有。

每个元组(行)的开销

项目标识符每行 4 个字节 - 不受上述注意事项的影响。
元组标头至少有 24 个字节(23 + 填充)。 The manual on Database Page Layout:

有一个固定大小的标头(在大多数机器上占用 23 个字节), 后跟一个可选的空位图、一个可选的对象 ID 字段和 用户数据。

对于标头和用户数据之间的填充,您需要知道服务器上的MAXALIGN - 通常在 64 位操作系统上为 8 个字节(或在 32 位操作系统上为 4 个字节)。如果您不确定,请查看pg_controldata

在您的 Postgres 二进制目录中运行以下命令以获得明确的答案:

./pg_controldata /path/to/my/dbcluster

The manual:

实际的用户数据(行的列)从偏移量开始 由t_hoff 表示,它必须始终是MAXALIGN 的倍数 平台的距离。

因此,您通常通过将数据打包成 8 个字节的倍数来获得最佳存储空间。

您发布的示例没有任何好处。已经包得很严实了。最后一个int2 之后的 2 个字节的填充,最后一个 4 个字节。您可以在最后将填充合并为 6 个字节,这不会改变任何内容。

每个数据页的开销

数据页大小通常为 8 KB。在这个级别上也有一些开销/膨胀:剩余部分不足以容纳另一个元组,更重要的是死行或FILLFACTOR setting 保留的百分比。

还有几个其他因素需要考虑磁盘大小:

数组类型?

对于您正在评估的 array 类型,您将为该类型添加 24 字节的开销。另外,数组元素像往常一样占用空间。没有任何收获。

【讨论】:

  • 根据经验,如果先放置 8 字节列,然后放置 4 字节、2 字节和 1 字节列,则不会出错。 一些很酷的建议。
  • 这是一个很棒/有趣的答案,但我不明白的一件事是为什么CREATE TABLE 中的列名顺序很重要。我不认为这很重要。为什么 postgres 不能/不能为你做这个俄罗斯方块优化?为什么列顺序被认为足够重要以保持定义?
  • @Russ:因为没有人实现逻辑和物理列顺序之间的划分。 It's an open TODO item,但并非微不足道,因为它到处都在弄乱系统目录。差不多 6 年后仍然如此。由于 Postgres 9.3 VIEW 可以为 simple 情况提供不同的列顺序(写入也会自动传播。)
  • 谢谢@Erwin。不过,留给用户似乎仍然很奇怪。我觉得在创建表后弄乱系统目录会很棘手,但如果重新排序是预先完成的,这似乎不是问题。使用诸如存储参数之类的东西来实现这一点会有什么问题?类似于CREATE TABLE WITH column_reorder_ok 的内容暗示“我不关心逻辑列顺序,所以随便摆弄它,但是你喜欢优化表格”。或者,使用您的术语,WITH column_tetris_ok。 :)
  • @Russ:听起来很有用。一种在创建表之前重新排序列的工具,它避免了与系统目录混淆的复杂性。也可以在任何客户端软件中实现,一种优化CREATE TABLE 语句以实现最小存储的工具...
【解决方案3】:

来自这个伟大的文档:https://www.2ndquadrant.com/en/blog/on-rocks-and-sand/

对于您已经拥有的表,或者您正在开发中的表,名为my_table,此查询将给出从左到右的最佳顺序。

SELECT a.attname, t.typname, t.typalign, t.typlen
FROM pg_class c
JOIN pg_attribute a ON (a.attrelid = c.oid)
JOIN pg_type t ON (t.oid = a.atttypid)
WHERE c.relname = 'my_table'
 AND a.attnum >= 0
ORDER BY t.typlen DESC

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-28
  • 1970-01-01
  • 2015-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-13
相关资源
最近更新 更多