Help us learn about your current experience with the documentation. Take the survey.

日期范围分区

Description

GitLab 迁移助手最支持的方案是日期范围分区, 其中表中的每个分区包含单个月份的数据。在这种情况下, 分区键必须是时间戳或日期列。为了使这种类型的 分区工作良好,大多数查询必须访问特定日期范围内的数据。

举一个更具体的例子,考虑使用 audit_events 表。 它是应用数据库中第一个被分区的表。这个 表跟踪应用中发生的安全事件的审计条目。在几乎所有情况下, 用户都希望查看在特定时间段内发生的审计活动。因此,日期范围分区 是数据访问方式的自然选择。

要更详细地了解这一点,想象一个简化的 audit_events 架构:

CREATE TABLE audit_events (
  id SERIAL NOT NULL PRIMARY KEY,
  author_id INT NOT NULL,
  details jsonb NOT NULL,
  created_at timestamptz NOT NULL);

现在想象 UI 中的典型查询会在 特定日期范围内显示数据,比如单周:

SELECT *
FROM audit_events
WHERE created_at >= '2020-01-01 00:00:00'
  AND created_at < '2020-01-08 00:00:00'
ORDER BY created_at DESC
LIMIT 100

如果表按 created_at 列进行分区,基础表将 如下所示:

CREATE TABLE audit_events (
  id SERIAL NOT NULL,
  author_id INT NOT NULL,
  details jsonb NOT NULL,
  created_at timestamptz NOT NULL,
  PRIMARY KEY (id, created_at))
PARTITION BY RANGE(created_at);

分区表的主键必须将分区键作为 主键定义的一部分。

我们可能有一个表的分区列表,例如:

audit_events_202001 FOR VALUES FROM ('2020-01-01') TO ('2020-02-01')
audit_events_202002 FOR VALUES FROM ('2020-02-01') TO ('2020-03-01')
audit_events_202003 FOR VALUES FROM ('2020-03-01') TO ('2020-04-01')

每个分区都是一个独立的物理表,与基础 audit_events 表具有相同的结构,但只包含分区键 落在指定范围内的行的数据。例如,分区 audit_events_202001 包含 created_at 列大于或等于 2020-01-01 且小于 2020-02-01 的行。

现在,如果我们再次查看之前的示例查询,数据库可以 使用 WHERE 来识别所有匹配的行都在 audit_events_202001 分区中。而不是搜索所有分区中的 所有数据,它可以在适当的分区中只搜索单个月份的数据。 在大表中,这可以 显著减少数据库需要访问的数据量。然而,想象一个不基于分区键 进行过滤的查询,例如:

SELECT *
FROM audit_events
WHERE author_id = 123
ORDER BY created_at DESC
LIMIT 100

在这个例子中,数据库无法从搜索中 修剪任何分区,因为匹配的数据可能存在于任何分区中。因此,它必须 单独查询每个分区,并将行聚合到一个结果 集中。由于 author_id 会被索引,性能影响可能 是可以接受的,但在更复杂的查询中,开销可能 很大。只有当数据的访问模式 支持分区策略时,才应该利用分区,否则性能 会受到影响。

时间范围分区策略

GitLab 支持两种时间范围分区策略:

  • 每日分区
  • 每月分区

使用时间范围分区

要在模型中使用时间范围分区,包含 PartitionedTable 模块并配置分区设置:

class WebHookLog < ApplicationRecord
  include PartitionedTable

  partitioned_by :created_at, strategy: :monthly, retain_for: 1.month
end

可用策略

每日策略 (:daily)

每日策略每天创建一个分区:

partitioned_by :created_at, strategy: :daily, retain_for: 7.days

每月策略 (:monthly)

每月策略每月创建一个分区:

partitioned_by :created_at, strategy: :monthly, retain_for: 3.months, analyze_interval: 3.days

配置选项

  • column: 要分区的列(必需,必须是时间戳或日期列)
  • strategy: :daily:monthly(必需)
  • retain_for: 保留分区的持续时间(可选)
  • analyze_interval: 对新分区运行 ANALYZE 的频率(可选)

对于需要细粒度分区的海量表选择 :daily,对于数据量适中的表,每日分区会过于频繁,选择 :monthly

示例

步骤 1:创建分区副本(版本 N)

第一步是添加一个迁移来创建原始表的 分区副本。这个迁移根据原始表中的数据 创建适当的分区,并安装一个触发器,将原始表的写入 同步到分区副本中。

按其 created_at 列对 audit_events 表进行分区的 示例迁移如下:

class PartitionAuditEvents < Gitlab::Database::Migration[2.1]
  include Gitlab::Database::PartitioningMigrationHelpers

  def up
    partition_table_by_date :audit_events, :created_at
  end

  def down
    drop_partitioned_table_for :audit_events
  end
end

执行此迁移后,原始表中的任何插入、更新或删除 也会在新表中复制。对于更新和删除, 只有当相应行存在于分区表中时,操作才有效。

步骤 2:回填分区副本(版本 N)

第二步是添加一个部署后迁移,安排 后台作业,将现有数据从原始表 回填到分区副本中。

继续上面的例子,迁移将如下所示:

class BackfillPartitionAuditEvents < Gitlab::Database::Migration[2.1]
  include Gitlab::Database::PartitioningMigrationHelpers

  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main

  def up
    enqueue_partitioning_data_migration :audit_events
  end

  def down
    cleanup_partitioning_data_migration :audit_events
  end
end

此步骤内部会排队一个批量后台迁移,BATCH_SIZE 和 SUB_BATCH_SIZE 为 50,0002,500。有关更多详细信息,请参考批量后台迁移指南

步骤 3:回填后清理(步骤 2 之后的必需停止后的版本)

在步骤 2 和步骤 3 之间必须有一个必需停止,以便在 GitLab 自托管实例中允许步骤 2 的后台迁移成功完成。

在这一步中, 添加另一个部署后迁移来清理 后台迁移。这包括强制执行任何剩余的作业, 并复制可能因作业被丢弃或失败而 遗漏的数据。

再次继续示例,此迁移将如下所示:

class CleanupPartitionedAuditEventsBackfill < Gitlab::Database::Migration[2.1]
  include Gitlab::Database::PartitioningMigrationHelpers

  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main

  def up
    finalize_backfilling_partitioned_table :audit_events
  end

  def down
    # no op
  end
end

此迁移完成后,原始表和分区表应包含 相同的数据。安装在原始表上的触发器确保数据 保持同步。

步骤 4:交换分区和非分区表(版本 N+1)

这一步用其分区副本替换非分区表,这应该仅在所有其他迁移步骤成功完成后使用。

此方法的一些限制必须在交换迁移之前或期间处理:

  • 二级索引和外键不会在分区表上自动重新创建。
  • 依赖索引的某些类型的约束(UNIQUE 和 EXCLUDE)不会在分区表上自动重新创建, 因为底层索引将不存在。
  • 引用原始非分区表的外键应更新为引用 分区表。这在 PostgreSQL 11 中不支持。
  • 引用原始表的视图不会自动更新为引用分区表。
# frozen_string_literal: true

class SwapPartitionedAuditEvents < ActiveRecord::Migration[6.0]
  include Gitlab::Database::PartitioningMigrationHelpers

  def up
    replace_with_partitioned_table :audit_events
  end

  def down
    rollback_replace_with_partitioned_table :audit_events
  end
end

此迁移完成后:

  • 分区表替换了非分区(原始)表。
  • 之前创建的同步触发器被删除。

分区表现在已准备好供应用程序使用。