【问题标题】:Postgres partitioning?Postgres分区?
【发布时间】:2015-12-24 03:50:16
【问题描述】:

我的软件每 30 分钟运行一次 cronjob,它从 Google Analytics(分析)/社交网络中提取数据并将结果插入 Postgres 数据库。

数据如下:

url text NOT NULL,    
rangeStart timestamp NOT NULL,
rangeEnd timestamp NOT NULL,
createdAt timestamp DEFAULT now() NOT NULL,
...
(various integer columns)

由于一个查询返回 10 000 多个项目,因此将这些数据存储在单个表中显然不是一个好主意。按照这个速度,cronjob 每天将生成约 480 000 条记录,每月将生成约 1450 万条记录。

我认为解决方案是使用多个表,例如我可以使用特定表来存储给定月份生成的数据:stats_2015_09、stats_2015_10、stats_2015_11 等。

我知道 Postgres 支持表分区。但是,我对这个概念很陌生,所以我不确定最好的方法是什么。在这种情况下我需要分区,还是应该手动创建这些表?或者也许有更好的解决方案?

稍后将通过各种方式查询数据,并且这些查询预计会运行得很快。

编辑:

如果我最终得到 12-14 个表,每个表存储 10-20 百万行,Postgres 应该仍然能够快速运行 select 语句,对吧?插入不必非常快。

【问题讨论】:

  • The official documentation 在这个问题上很精辟,应该足够了。
  • 另见pg_partman,这是一个易于使用的扩展,提供了许多内置管道所缺乏的细节。

标签: postgresql database-partitioning


【解决方案1】:

在各种情况下,分区都是一个好主意。想到的两个是:

  • 您的查询有一个WHERE 子句,可以很容易地映射到一个或几个分区。
  • 您需要一种快速删除历史数据的方法(删除分区比删除记录更快)。

如果不知道要运行的查询类型,很难说分区是否是个好主意。

我想我可以说将数据分成不同的表是一个的想法,因为它是维护的噩梦:

  • 表中不能有外键引用。
  • 跨多个表的查询很麻烦,因此很难回答简单的问题。
  • 维护表成为一场噩梦(添加/删除列)。
  • 如果您有不同角色的用户,则必须小心维护权限。

无论如何,从 Postgres 的分区文档开始,即here。我应该指出,Postgres 的实现比其他数据库中的实现要笨拙一些,因此您可能需要查看 MySQL 或 SQL Server 的文档以了解它在做什么。

【讨论】:

  • 感谢您的回复。我无法准确说出我们想要哪种查询,因为历史数据将由不同的应用程序查询。但我认为最重要的关键是日期范围(从 - 到),因此基于此创建分区可能是个好主意。
【解决方案2】:

首先,我想挑战你问题的前提:

由于一个查询返回 10 000 多个项目,因此将这些数据存储在单个表中显然不是一个好主意。

据我所知,数据库不能很好地处理具有数百万行的单个表没有根本原因。在极端情况下,如果您创建了一个没有索引的表,并且只是在其中附加了行,那么 Postgres 可以简单地将这些行写入磁盘,直到您的存储空间用完为止。 (内部可能还有其他限制,我不确定;但如果是这样,它们就很大。)

只有当您尝试使用该数据做某事时,问题才会出现,而确切的问题 - 以及确切的解决方案 - 取决于您所做的什么

如果您想定期删除超过固定时间尺度之前插入的所有行,您可以在createdAt 列上对数据进行分区。然后DELETE 将成为一个非常有效的DROP TABLE,并且所有INSERTs 将通过触发器路由到“当前”分区(或者如果您的导入脚本知道分区命名,甚至可以绕过它方案)。但是,SELECTs 可能无法在其WHERE 子句中指定createAt 值的范围,因此需要查询所有分区并组合结果。您一次保留的分区越多,效率就越低。

或者,您可能会检查表上的工作负载,并查看所有查询已经或很容易明确声明 rangeStart 值。在这种情况下,您可以在rangeStart 上进行分区,并且查询规划器在规划每个SELECT 查询时能够消除除一个或几个分区之外的所有分区。 INSERTs 需要通过触发器路由到适当的表,并且维护操作(例如删除不再需要的旧数据)效率会低得多。

或者也许您知道一旦rangeEnd 变得“太旧”,您将不再需要这些数据,并且可以获得两个好处:按rangeEnd 分区,确保您的所有SELECT 查询明确提及rangeEnd,并删除包含您不再感兴趣的数据的分区。

借用 git 的 Linus Torvald 的术语,用于分区的“管道”以表继承的形式内置于 Postgres,as documented here,但除了手册中的示例外,几乎没有“瓷器”的方式。但是,有一个非常好的extension called pg_partman,它提供了基于 ID 或日期范围管理分区集的功能;阅读文档以了解不同的操作模式非常值得。在我的情况下,没有一个完全匹配,但是分叉​​该扩展比从头开始编写所有内容要容易得多。

请记住,分区并不是免费的,如果基于上述考虑,没有明显的候选列作为分区依据,那么实际上最好将数据留在一个表中,并考虑其他优化策略.例如,部分索引 (CREATE INDEX ... WHERE) 可能能够处理最常查询的行子集;也许与“覆盖索引”结合使用,Postgres 可以直接从索引返回查询结果,而无需参考主表结构(“仅索引扫描”)。

【讨论】:

  • 谢谢你的详细解释,很有用!我已经检查了文档,现在将检查 pg_partman。到目前为止,createdAt 似乎是一个很好的分区列。我查看了查询代码,最重要的查询可以明确提及它(作为一个范围)。
  • 关于将数据保存在一个表中:这是一个选项。但是即使该表被正确索引,如果那里有 5 亿行,SELECT 将相对较慢,除非索引以某种方式保证每次查询只需要检查数据集的一小部分。因此,使用分区或其他技术拆分数据似乎是一种更好的方法。我错了吗?
  • @user2297996 “除非索引以某种方式保证每次查询只需要检查数据集的一小部分” - 这几乎正是索引的用途,有效地跳转到正确的部分的数据集。我不知道 btree 索引如何工作的细节,但我对数据库的经验法则是,如果我不知道它是如何工作的,DBMS 可能会做出更好的决策这个盒子比我尝试手动优化低级结构。
猜你喜欢
  • 1970-01-01
  • 2022-11-23
  • 2012-04-17
  • 1970-01-01
  • 2014-03-14
  • 1970-01-01
  • 1970-01-01
  • 2021-11-03
相关资源
最近更新 更多