数据库存储

了解 PostgreSQL 表空间

作者:Dylan Paulus

表空间是 PostgreSQL 的一个功能,它很容易被忽视,但可以提供巨大的速度提升和节省成本的潜力。随着数据库的增长,表空间成为我们工具箱中一个强大的工具。

了解如何使用 PostgreSQL 表空间以及如何使用便捷的多层存储解决方案来规避其复杂性,从而一次性获得快速的查询性能和经济的扩展能力。

什么是 PostgreSQL 表空间?

一个 表空间 是一个磁盘位置,数据库对象(表、索引、序列等)的物理数据文件存储在那里。将表空间视为服务器/计算机上名称和目录之间的一个映射。PostgreSQL 默认情况下带有两个表空间

  • pg_default 是 PostgreSQL 在创建表时使用的默认表空间。

    • pg_default 映射到 PostgreSQL 目录(由 $PGDATA 定义)中的 /data 目录。

  • pg_global 用于 PostgreSQL 内部存储系统相关对象。

表空间和模式有什么区别?

模式允许我们将数据库对象组织到组或“桶”中,以更轻松地管理这些对象。模式是一个概念或逻辑表示。另一方面,表空间代表数据库对象在硬盘上物理存在的位置。这些是 PostgreSQL 在创建表或插入数据时创建的文件。

PostgreSQL 表空间的用途

在 PostgreSQL 表空间方面,我们可以利用两个主要用例。

首先,表空间是防止存储空间耗尽的战略工具。当磁盘变得越来越满时,您可以将表或分区分配到不同的磁盘位置以避免数据库出现故障。需要注意的是,只有新数据才会存储在新的磁盘上,而现有数据将继续驻留在旧的磁盘上。

其次,也许是最重要的,表空间是优化 PostgreSQL 数据库性能和货币成本的关键工具。通过将频繁访问的数据存储在高速、昂贵的磁盘上,同时将不太频繁访问的数据移动到更具成本效益的、速度较慢的磁盘上,可以实现这一点。例如,使用时间序列数据,您可以将最近三天的数据保存在快速磁盘上。同时,可以将超过三天的时间历史数据和报告数据移动到廉价的冷存储中。

如何在 PostgreSQL 中使用表空间

创建表空间相当简单,但维护起来可能很棘手。要创建新的表空间,您必须以数据库超级用户身份登录,并且您要存储数据的目录位置需要创建。要创建表空间,请运行 CREATE TABLESPACE,并指定数据将要存储的名称和位置。

CREATE TABLESPACE my_tablespace LOCATION '/tmp/pgdata';

创建完成后,我们可以向数据库对象提供表空间的名称,以将其存储在表空间定义的路径中。让我们通过几个示例来探讨如何做到这一点。

在创建数据库时使用表空间

CREATE DATABASE payments TABLESPACE my_tablespace;

使用表空间是在 PostgreSQL 中物理分离数据库的好方法。在创建数据库时提供表空间将自动将该数据库下的所有对象都放在给定的表空间中。使用上面的示例,在 payments 数据库中创建的任何表、索引、物化视图或其他数据库对象默认情况下将位于 /tmp/pgdata 中。

在创建表时使用表空间

CREATE TABLE line_items (
	id      SERIAL PRIMARY KEY,
	name    TEXT NOT NULL,
	price   DECIMAL NOT NULL
) 
TABLESPACE my_tablespace;

我们还可以定义单个表所属的表空间。在创建表时不包含表空间,它将默认使用数据库的表空间。

通过显式地为表定义表空间,我们可以将它的数据放置在与数据库的表空间不同的位置。例如,如果我们在 payments 数据库中运行了以下查询(请记住,payments 位于 my_tablespace 表空间中)

CREATE TABLE line_items (
	id      SERIAL PRIMARY KEY,
	name    TEXT NOT NULL,
	price   DECIMAL NOT NULL
) 
TABLESPACE pg_default;

line_items 的物理数据将存储在 pg_default (/data) 中,而不是 /tmp/pgdata 中。

如何将表移动到另一个表空间?

一旦为表定义了表空间,它就不是一成不变的。更改表空间可以让我们在磁盘空间不足时避免故障。当我们注意到磁盘空间不足时,可以将表的表空间更改为空磁盘,所有将来写入的数据都将写入该新的磁盘。要更新表的表空间,请运行 ALTER 查询

ALTER TABLE line_items SET TABLESPACE pg_default;
ALTER TABLE line_items SET TABLESPACE my_tablespace;

如何删除表空间?

一旦我们决定不再需要表空间,我们可能希望将其删除。在 PostgreSQL 允许我们删除表空间之前,必须先从表空间中删除所有数据库对象。删除了对象后,我们可以通过运行以下命令来删除表空间

DROP TABLESPACE [tablespace_name];

在超表中使用表空间

与表类似,我们可以为每个超表(TimescaleDB 的一项功能,与 PostgreSQL 表的功能相同,但会自动对数据进行分区,从而提高性能)配置表空间,甚至可以在表空间之间移动块。在表空间之间移动块对于将历史时间序列数据保留在访问较少的较慢磁盘上特别有用,这可以释放近期相关数据的空间以供更快访问。

让我们通过创建两个表空间和一个超表来演示这一点。

注意:请确保在运行以下CREATE TABLESPACE命令之前,/tmp/pgdata-fast/tmp/pgdata目录已存在。

CREATE TABLESPACE fast_disk LOCATION '/tmp/pgdata-fast';
CREATE TABLESPACE slow_disk LOCATION '/tmp/pgdata';

CREATE TABLE measurements (  
    time    TIMESTAMPTZ NOT NULL,
	data    DECIMAL NOT NULL
) TABLESPACE fast_disk;

SELECT create_hypertable('measurements', by_range('time'));

这里发生了一些事情。我们使用任意位置创建了两个表空间,以模拟快速和慢速磁盘。接下来,我们创建了一个measurements表,将其默认设置为fast_disk表空间。为超表定义表空间并非必需,但我希望强调表上的任何表空间都会传递到超表中。或者,如果表没有显式定义表空间,则超表将默认使用数据库的默认表空间。

表上的表空间与超表上的表空间之间的一个主要区别是,超表可以有多个表空间附加到它们。TimescaleDB 会将块分配到超表上给定的所有表空间中。若要删除或添加超表的表空间,请分别使用detach_tablespace()attach_tablespace()

让我们将slow_disk添加为measurements的额外表空间。

SELECT attach_tablespace('slow_disk', 'measurements');

The resulting output of running show_tablespaces displaying two tablespaces attached to the measurements hypertable: fast_disk and slow_disk.

若要显示给定超表上的表空间,请使用show_tablespaces([hyper_table])函数。

SELECT * FROM show_tablespaces('measurements');

我们希望保持超表的快速性。让我们通过运行detach_tablespace()来删除slow_disk表空间。

SELECT detach_tablespace('slow_disk', 'measurements');

The resulting output of running show_tablespaces displaying one tablespace (fast_disk) after detaching the slow_disk tablespace.

在表空间之间移动块

能够将表空间附加到和从超表中分离非常有用。但是,为了真正优化时间序列数据访问,我们需要能够在表空间之间移动块,因为数据分区发生在块级别。与超表不同,块只能属于单个表空间。我们可以使用move_chunk()函数在表空间之间移动块。首先,在我们能够看到move_chunk()的实际效果之前,我们需要向measurements中添加数据以在超表中创建一些块。

INSERT INTO measurements (time, data)
SELECT
    time_hour,
    0.25
FROM generate_series(
    TIMESTAMPTZ '2023-11-01', 
    TIMESTAMPTZ '2023-11-07', 
    INTERVAL '1 hour'
) as time_hour;

然后,使用show_chunks查看生成的块。

SELECT show_chunks('measurements');

The output of show_chunks on the measurements hypertable. Two chunks are shown: hyper_1_1_chunk and hyper_1_2_chunk.

假设_timescaledb_internal._hyper_1_1_chunk不再在我们的应用程序中使用,但我们希望保留此块以供历史参考。现在是时候将_hyper_1_1_chunk移动到较慢的磁盘。我们可以通过运行move_chunk()将其移动到slow_disk表空间。

SELECT move_chunk(
	chunk => '_timescaledb_internal._hyper_1_1_chunk',
	destination_tablespace => 'slower_disk', 
	index_destination_tablespace => 'slower_disk', 
	reorder_index => 'measurements_time_idx'
);

等等,这是什么?这有点复杂,让我们分解一下。在表空间之间移动块时,move_chunk就像PostgreSQL CLUSTERPostgreSQL ALTER TABLE...SET TABLESPACE命令的组合。为了保留块的索引,我们需要告诉move_chunk该块使用哪个索引来分区reorder_index以及将索引存储在磁盘上的目标位置index_destination_tablespace

若要确定要使用哪个索引,请通过 psql cli 连接到数据库时运行\d [table_name]

The output from running '\d measurements;' which shows the 'measurements_time_idx' index being used.

_hyper_1_1_chunk块移动到slow_disk后,我们可以通过查询timescaledb_information.chunks表来再次检查块使用的表空间。

SELECT 
	hypertable_name, 
	chunk_name, 
	chunk_tablespace 
FROM timescaledb_information.chunks 
WHERE hypertable_name = 'measurements';

The output of running the query. Shows hyper_1_1_chunk on the slow_disk tablespace and hyper_1_2_chunk on the fast_disk tablespace

若要自动在表空间之间移动块,我们需要设置一个 cron 以定期评估哪些块需要移动表空间,然后运行move_chunks()。这样做可能会出错、麻烦,而且并不总是完美。但当然,Timescale 有解决方案!

Timescale 分层存储

分层存储是一种用于自动管理数据的无缝且快速的解决方案。使用分层存储,插入 Timescale 云数据库的数据首先会写入高性能存储。随着时间的推移,当这些数据变得很少被访问时,它们会移动到低成本存储层,无需用户干预 - 这样可以降低成本,同时优化快速查询和高吞吐量。

在现有超表中分层数据非常轻松,无需进行大的调整。运行add_tiering_policy()函数,提供一个超表和一个阈值,该阈值表示何时将数据移动到较低的层。

SELECT add_tiering_policy('measurements', INTERVAL '7 days');

就这样!现在已经为measurements超表设置了数据分层策略。任何超过 7 天的数据将自动移动到低成本存储。

我们可以使用 Timescale 的分层存储后端完全控制我们的数据。如果我们发现将来需要将数据从低成本存储移回高性能存储,我们可以取消分层任何块。

CALL untier_chunk('[chunk_name]');

查看我们的文档以了解有关使用分层存储的更多信息。.

结论

表空间不仅是管理磁盘使用量的工具,还可以帮助数据库保持快速运行和健康状态。在本文中,我们介绍了如何在 PostgreSQL 中创建和分配表空间,如何利用超表和块中的表空间进一步优化 TimescaleDB 中的数据访问,以及最后如何使用分层存储来简化云中表空间的管理 - 为您节省成本和性能。

免费创建 Timescale 帐户,开始自行尝试表空间和分层存储。