日期范围分区
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,000 和 2,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此迁移完成后:
- 分区表替换了非分区(原始)表。
- 之前创建的同步触发器被删除。
分区表现在已准备好供应用程序使用。