批量加载最佳做法

本页面提供了一些准则,旨在帮助您高效地将大量数据批量加载到 Spanner 中。

您可以通过多种方式将数据批量加载到 Spanner 中:

虽然您也可以使用 Google Cloud CLI 插入行,但我们不建议您使用 gcloud CLI 进行批量加载。

批量加载的性能准则

为实现最佳批量加载性能,请最大限度地利用分区功能,以跨多个工作器任务分配数据写入。

Spanner 使用基于负载的分块来跨实例的计算资源平均分配数据负载。在几分钟的高负载之后,Spanner 会在行之间引入分块边界。一般而言,如果数据负载分布均衡,并且您遵循了架构设计和批量加载的最佳做法,那么在实例中可用的 CPU 资源达到饱和前,写入吞吐量应该每隔几分钟就会增加一倍。

通过主键对数据进行分区

Spanner 会自动将表划分为多个较小的范围。行的主键决定了行的分区位置。

要获得批量加载的最佳写入吞吐量,请使用以下模式通过主键对数据进行分区:

  • 每个分区包含一系列连续的行(由键列确定)。
  • 每个提交包含仅用于单个分区的数据。

我们建议分区数为 Spanner 实例中节点数的 10 倍。要将行分配给分区,请执行以下操作:

  • 通过主键对数据进行排序。
  • 将数据划分到 10 *(节点数)个独立且大小相等的分区。
  • 为每个分区创建并分配单独的工作器任务。您应该在您的应用中创建工作器任务。这不是 Spanner 功能。

若遵循这种模式,对于大型加载作业,每个节点的最大整体批量写入吞吐量应该会达到每秒 10-20 MB。

当您加载数据时,Spanner 会创建并更新分块,以平衡实例中节点上的负载。在此过程中可能会出现吞吐量暂时下降的情况。

示例

您有一个包含 3 个节点的区域配置。非交错表的行数为 90000 行。表中的主键范围为 1 到 90000。

  • 行数:90000
  • 节点数:3
  • 分区数:10 * 3 = 30
  • 每个分区的行数:90000 / 30 = 3000。

第一个分区包括键范围 1 到 3000。第二个分区包括键范围 3001 到 6000。第 30 个分区包括键范围 87001 到 90000。(不应在大型表中使用顺序键。此示例仅出于演示目的。)

每个工作器任务都负责发送对单个分区的写入操作。在每个分区中,您应该按主键并按顺序写入行。在分区中随机写入行(就主键而言)也应提供合理的高吞吐量。 通过衡量测试结果,您可以深入了解哪种方法可为数据集提供最佳性能。

不涉及分区的批量加载

在提交中写入一组连续的行会比写入随机的行更快。随机行也可能包含来自不同分区的数据。

提交中写入的分区越多,需要的跨服务器协调就更多,从而增加提交延迟时间和开销。

这可能会涉及多个分区,因为每个随机行可能属于不同的分区。在最坏的情况下,每次写入都会涉及 Spanner 实例中的所有分区。如前所述,当涉及更多分区时,写入吞吐量会降低。

避免过载

有时候,发送的写请求有可能会多于 Spanner 能够处理的写请求。Spanner 通过中止事务来处理过载,这种情况称作“回退”(pushback)。对于只写事务,Spanner 会自动重试事务。在这种情况下,回退表现为高延迟。在高负载期间,回退可能会持续长达一分钟。在严重高负载期间,回退可能会持续几分钟。为了避免回退,您应该对写入请求进行限流,以将 CPU 利用率保持在合理的限制范围内。或者,用户可以增加节点数量,使 CPU 利用率保持在限制范围内。

每次提交 1 MB 到 5 MB 的变更

无论写入量是大还是小,每次写入 Spanner 都会产生一些开销。要使吞吐量最大化,应最大限度地提高每次写入所存储的数据量。增加写入量有助于降低每次写入的开销比率。 一个好的技巧是每个提交都修改数百行。写入相对较大的行时,大小为 1 MB 到 5 MB 的提交通常可提供最佳性能。在写入较小的值或编入索引的值时,最佳做法通常是,在单个提交中最多写入几百行。 请注意,不管提交大小和行数是多少,每个提交包含的变更数不得超过 80,000。要确定最佳性能,您应该测试并测量吞吐量。

大于 5 MB 或超过几百行的提交不会带来额外的好处,并且有可能超出 Spanner 对提交大小以及每个提交包含的变更数的限制

二级索引准则

如果数据库具有二级索引,您必须选择在加载表数据之前还是之后将索引添加到数据库架构。

  • 在加载数据之前添加索引可让架构更改立即完成。但是,影响索引的每次写入将需要更长时间,因为它还需要更新索引。数据加载完成后,所有索引就绪,数据库将立即可用。要同时创建表及其索引,请在一个请求中将添加新表和添加新索引的 DDL 语句一并发送到 Spanner。

  • 在加载数据后添加索引意味着每次写入都是高效的。但是,每个索引回填的架构更改可能需要较长时间。 在所有架构更改完成之前,数据库不是完全可用的,并且查询无法使用索引。数据库仍可响应写入和查询,但速度会变慢。

建议您在加载数据之前添加对业务应用至关重要的索引。对于所有非关键索引,请在迁移数据后添加。

在批量加载期间使用 INTERLEAVE IN

对于在多个表之间存在许多父子引用的架构,您必须始终先加载父级,然后再加载子级,以确保参照完整性。这种编排可能会很复杂,尤其是在存在多个层次结构级别的情况下。这种复杂性还使得批量处理和并行处理变得更加困难,并可能会极大地影响总体批量加载时间。在 Spanner 中,这些关系通过 INTERLEAVE IN PARENT(即外键)来强制执行。如需了解详情,请参阅 CREATE TABLE 文档。

在批量加载后添加外键会在后台创建索引,因此请遵循二级索引中的指南。

不过,对于 INTERLEAVE IN PARENT 表,建议您在批量加载期间使用 INTERLEAVE IN 语义创建所有表,这样可以使行交错排列,但不会强制实施参照完整性。这样,您既可以获得局部性的性能优势,又无需预先排序。现在,子行可以插入到相应父行之前,这使得 Spanner 可以并行写入所有表。

加载所有表后,您就可以迁移交错表,开始使用 ALTER TABLE t1 SET INTERLEAVE IN PARENT t2 语句强制执行父子关系。这会验证参照完整性,如果存在任何孤立的子行,则会失败。如果验证失败,请使用以下查询来确定缺少的父行。

      SELECT pk1, pk2 FROM child
      EXCEPT DISTINCT
      SELECT pk1, pk2 FROM parent;

测试并测量吞吐量

预测吞吐量可能较为困难。我们建议您在最终加载数据之前先测试批量加载策略。如需查看使用分区和监控性能的详细示例,请参阅最大化数据加载吞吐量

定期批量加载到现有数据库的最佳做法

如果要更新包含数据但没有任何二级索引的现有数据库,本文档中的建议仍然适用。

如果您有二级索引,这些说明可能会帮助您获得合理的性能。性能取决于事务涉及的平均分片数量。如果吞吐量下降至太低的水平,您可以尝试以下方法:

  • 减少每个提交中包含的变更数量,这可能会提高吞吐量。
  • 如果您的上传量大于正在更新的表的当前总大小,请删除二级索引,并在上传数据后重新添加。通常没有必要执行此步骤,但这样做可能会提高吞吐量。