Timescale Cloud:性能、规模、企业级

自托管产品

MST

本文档提供详细的分步说明,介绍如何使用双写和回填迁移方法,将数据从使用 PostgreSQL 的源数据库迁移到 Timescale。

注意

在迁移的上下文中,您现有的生产数据库被称为“源”数据库,而您打算将数据迁移到的新 Timescale 数据库则被称为“目标”数据库。

具体而言,迁移过程包括以下步骤:

  1. 在 Timescale 中设置目标数据库实例。
  2. 修改应用程序以写入目标数据库。
  3. 将模式和关系数据从源数据库迁移到目标数据库。
  4. 以双写模式启动应用程序。
  5. 确定完成点 T
  6. 将时间序列数据从源数据库回填到目标数据库。
  7. 验证目标数据库中是否存在所有数据。
  8. 验证目标数据库能否处理生产负载。
  9. 切换应用程序以将目标数据库视为主要数据库(可能继续写入源数据库作为备份)。
提示

如果您遇到困难,可以通过开启支持请求或在社区 Slack#migration 频道寻求帮助,该迁移方法的开发人员在那里提供帮助。

您可以直接从Timescale 控制台打开支持请求,或通过电子邮件发送至support@timescale.com

在 Timescale 中创建数据库服务.

如果您打算迁移超过 400 GB 的数据,请开启支持请求,以确保您的 Timescale 实例预先配置了足够的磁盘空间。

您可以直接从Timescale 控制台打开支持请求,或通过电子邮件发送至support@timescale.com

具体如何操作取决于您的应用程序所用的语言,以及您的数据摄入和应用程序功能。最简单的情况是,您只需并行执行两次插入。通常情况下,您必须考虑如何处理写入源数据库或目标数据库失败的情况,以及您希望或能够构建何种机制来从此类故障中恢复。

如果您的时间序列数据包含指向普通表的外部键引用,则必须确保您的应用程序正确维护外部键关系。如果被引用的列是 *SERIAL 类型,则插入到源数据库和目标数据库的同一行*可能不会*获得相同的自动生成 ID。如果发生这种情况,从源数据库回填到目标数据库的数据在内部将不一致。最好情况下,它会导致外部键冲突;最坏情况下,外部键约束得到维护,但数据引用了错误的外部键。为避免这些问题,最佳实践是遵循实时迁移

您可能还希望在源数据库和目标数据库上执行相同的读取查询,以评估查询结果的正确性和性能。请记住,目标数据库在一段时间内可能不包含所有数据,因此您应该预期在某些时期(可能几天)结果不会相同。

您可能希望将一些包含时间序列数据的大型表转换为超表。此步骤包括识别这些表,将它们的数据从数据库转储中排除,复制数据库模式和表,并将时间序列表设置为超表。数据将在后续步骤中回填到这些超表中。

注意

为方便起见,本指南中将源数据库和目标数据库的连接字符串分别称为 $SOURCE$TARGET。这可以在您的 shell 中设置,例如:

export SOURCE="postgres://<user>:<password>@<source host>:<source port>/<db_name>"
export TARGET="postgres://<user>:<password>@<target host>:<target port>/<db_name>"
pg_dumpall -d "$SOURCE" \
-l $DB_NAME \
--quote-all-identifiers \
--roles-only \
--file=roles.sql

Timescale 服务不支持具有超级用户权限的角色。如果您的 SQL 转储包含此类权限的角色,您需要修改文件以符合安全模型。

您可以使用以下 sed 命令从角色 SQL 文件中删除不受支持的语句和权限:

sed -i -E \
-e '/CREATE ROLE "postgres";/d' \
-e '/ALTER ROLE "postgres"/d' \
-e '/CREATE ROLE "tsdbadmin";/d' \
-e '/ALTER ROLE "tsdbadmin"/d' \
-e 's/(NO)*SUPERUSER//g' \
-e 's/(NO)*REPLICATION//g' \
-e 's/(NO)*BYPASSRLS//g' \
-e 's/GRANTED BY "[^"]*"//g' \
roles.sql
注意

此命令仅适用于 GNU 版本的 sed (有时称为 gsed)。对于 BSD 版本 (macOS 上的默认版本),您需要添加一个额外参数,将 -i 标志更改为 -i ''

要检查 sed 版本,可以使用命令 sed --version。GNU 版本会明确标示自己为 GNU,而 BSD 版本的 sed 通常不提供直接的 --version 标志,只会输出“非法选项”错误。

此脚本的简要解释是:

  • CREATE ROLE "postgres"; 和 ALTER ROLE "postgres":这些语句被移除,因为它们需要超级用户权限,Timescale 不支持这些权限。

  • (NO)SUPERUSER | (NO)REPLICATION | (NO)BYPASSRLS:这些是需要超级用户权限的权限。

  • GRANTED BY role_specificationGRANTED BY 子句也可能包含需要超级用户权限的权限,因此应将其移除。注意:根据 TimescaleDB 文档,GRANTED BY 子句中的 GRANTOR 必须是当前用户,并且此子句主要用于 SQL 兼容性。因此,可以安全地将其移除。

超表的理想候选是包含时间序列数据的大型表。这通常是主维度为某种时间戳值(TIMESTAMPTZTIMESTAMPBIGINTINT 等)以及其他测量值的数据。

pg_dump -d "$SOURCE" \
--format=plain \
--quote-all-identifiers \
--no-tablespaces \
--no-owner \
--no-privileges \
--exclude-table-data=<table name or pattern> \
--file=dump.sql
  • 使用 --exclude-table-data 排除超表候选的所有数据。您可以指定一个表模式,或者多次指定 --exclude-table-data,每个要转换的表指定一次。
  • --no-tablespaces 是必需的,因为 Timescale 不支持除默认表空间之外的任何表空间。这是一个已知限制。

  • --no-owner 是必需的,因为 Timescale 的 tsdbadmin 用户不是超级用户,不能在所有情况下分配所有权。此标志意味着所有权归连接到目标数据库的用户所有,而与源数据库中的所有权无关。这是一个已知限制。

  • --no-privileges 是必需的,因为 Timescale 的 tsdbadmin 用户不是超级用户,不能在所有情况下分配权限。此标志意味着分配给其他用户的权限必须在目标数据库中作为手动清理任务重新分配。这是一个已知限制。

psql -X -d "$TARGET" \
-v ON_ERROR_STOP=1 \
--echo-errors \
-f roles.sql \
-f dump.sql

对于目标数据库中应转换为超表的每个表,执行:

SELECT create_hypertable('<table name>', by_range('<time column name>'));
注意

by_range 维度构建器是 TimescaleDB 2.13 的新增功能。对于更简单的情况,例如本例,您也可以使用旧语法创建超表:

SELECT create_hypertable('<table name>', '<time column name>');

有关可传递给 create_hypertable 的选项的更多信息,请查阅create_table API 参考。有关超表的一般信息,请查阅超表文档

您可能还希望考虑利用 Timescale 的一些杀手级功能,例如:

  • 保留策略以自动删除不需要的数据
  • 分层存储以自动将数据移动到 Timescale 的低成本无底对象存储层
  • hypercore通过压缩列存储中的数据来减小超表的大小
  • 连续聚合以对数据进行极速聚合查询

设置好目标数据库后,您的应用程序现在可以以双写模式启动。

双写操作执行一段时间后,目标超表包含三个时间范围内的数据:缺失写入、迟到数据和“一致性”范围。

Hypertable dual-write ranges

如果应用程序由多个写入器组成,并且这些写入器并未同时开始写入目标超表,则会有一段时间并非所有写入都已进入目标超表。此期间从第一个写入器开始双写时开始,到最后一个写入器开始双写时结束。

某些应用程序有迟到数据:时间戳在过去,但尚未写入的测量值(例如来自连接间歇性设备的数据)。迟到数据窗口在当前时刻和最大延迟之间。

一致性范围是指没有缺失写入且所有数据都已到达的范围,即在缺失写入范围结束和迟到数据范围开始之间。

这些范围的长度由应用程序的特性决定,没有一劳永逸的方法来确定它们。

完成点 T 是在一致性范围内任意选择的一个时间点。它是数据可以安全回填的时间点,确保不会丢失数据。

完成点应表示为要回填的超表的 time 列的类型。例如,如果您使用 TIMESTAMPTZ time 列,则完成点可以是 2023-08-10T12:00:00.00Z。如果您使用 BIGINT 列,则可能是 1695036737000

如果您的超表的 time 列使用混合类型,您必须为每种类型单独确定完成点,并独立于其他类型回填每组相同类型的超表。

按表将数据从源数据库转储为 CSV 格式,然后使用 timescaledb-parallel-copy 工具将这些 CSV 恢复到目标数据库。

确定要从源数据库复制到目标数据库的数据窗口。根据源表中数据量的大小,将源表分成多个数据块以独立移动可能更合理。在以下步骤中,此时间范围称为 <start><end>

通常,time 列的类型是 timestamp with time zone,因此 <start><end> 的值必须是类似 2023-08-01T00:00:00Z 的格式。如果 time 列不是 timestamp with time zone,则 <start><end> 的值必须是该列的正确类型。

如果您打算从源表中复制所有历史数据,那么 <start> 的值可以为 '-infinity',而 <end> 的值是您确定的完成点 T 的值。

双写过程可能已经在您想要移动的时间范围内将数据写入了目标数据库。在这种情况下,必须删除双写的数据。这可以通过 DELETE 语句实现,如下所示:

psql $TARGET -c "DELETE FROM <hypertable> WHERE time >= <start> AND time < <end>);"
重要

BETWEEN 运算符包含开始和结束范围,因此不建议使用它。

执行以下命令,将 <source table><hypertable> 替换为源表和目标超表的完全限定名称:

psql $SOURCE -f - <<EOF
\copy ( \
SELECT * FROM <source table> WHERE time >= <start> AND time < <end> \
) TO stdout WITH (format CSV);" | timescaledb-parallel-copy \
--connection $TARGET \
--table <hypertable> \
--log-batches \
--batch-size=1000 \
--workers=4
EOF

上述命令不具备事务性。如果出现连接问题或导致其停止复制的其他问题,则必须从目标数据库中删除部分复制的行(使用上面步骤 6b 中的说明),然后才能重新启动复制。

在以下命令中,将 <hypertable> 替换为目标超表的完全限定表名,例如 public.metrics

psql -d $TARGET -f -v hypertable=<hypertable> - <<'EOF'
SELECT public.alter_job(j.id, scheduled=>true)
FROM _timescaledb_config.bgw_job j
JOIN _timescaledb_catalog.hypertable h ON h.id = j.hypertable_id
WHERE j.proc_schema IN ('_timescaledb_internal', '_timescaledb_functions')
AND j.proc_name = 'policy_compression'
AND j.id >= 1000
AND format('%I.%I', h.schema_name, h.table_name)::text::regclass = :'hypertable'::text::regclass;
EOF

现在所有数据都已回填,并且应用程序正在向两个数据库写入数据,因此两个数据库的内容应该相同。如何最佳地验证这一点取决于您的应用程序。

如果您对每个生产查询都并行读取两个数据库,您可以考虑添加一个应用程序级别的验证,以确保两个数据库返回相同的数据。

另一个选项是比较源表和目标表中的行数,尽管这会读取表中的所有数据,这可能会对您的生产工作负载产生影响。

另一个选项是在源表和目标表上运行 ANALYZE,然后查看 pg_class 表的 reltuples 列。这不精确,但不需要读取表中的所有行。注意:对于超表,reltuples 值属于分块表,因此您必须将属于该超表的所有分块的 reltuples 求和。如果分块在一个数据库中被压缩而在另一个数据库中没有,则此检查无法使用。

现在双写已经到位一段时间,目标数据库应该能够承受生产写入流量。现在是时候确定目标数据库是否能够处理所有生产流量(包括读取和写入)了。具体如何操作是应用程序特定的,由您自行决定。

一旦您验证了所有数据都已存在,并且目标数据库能够处理生产工作负载,最后一步就是将目标数据库切换为您的主数据库。您可能希望继续向源数据库写入一段时间,直到您确定目标数据库能够承受所有生产流量。

关键词

此页面有任何问题吗?报告问题 或在 GitHub 编辑此页面