Ch 31 遗留系统迁移:SQL Server → Redshift(10TB)¶
面包屑
本书主页 › Part V 平台演进 › Ch 31
项目第 2 年 · 扩展与迁移期——10TB 迁移攻坚战
本章你将学到¶
- 10TB/千表大规模离线迁移的挑战与架构
- 数据库恢复与 schema 自动转换的设计(含 DDL before/after)
- 迁移性能基线(24h 窗口四阶段时间分配、并发连接、压缩比、断点续传、三方对账)
- 流式导出 + 分片压缩 + 对象存储中转 + 批量加载的管线设计
- 任务状态持久化、断点续传与并发资源治理(含分片状态 schema)
- 回滚方案(单表/多表/全量三级)与存储过程→ETL 转换案例
平台运行到第二年,一个绕不开的挑战浮出水面:Aurora 中国区有一个运行了多年的 SQL Server 数仓,约 10TB、千张表,承载着历史分析数据。新平台建好了,但这个遗留数仓不能直接废弃——里面有历史报表、有下游依赖、有合规审计需要的归档数据。必须把它迁移到 Redshift,而且不能停业务。
这是整个项目里工程难度最大的一块。10TB 不是"导出再导入"那么简单——它涉及 schema 转换(SQL Server 语法不等于 Redshift 语法)、海量数据传输(不能压垮源库)、断点续传(10TB 不可能一次跑完)、并发控制(同时迁多张表但不能把源库搞挂)。
我在企业征信项目里做过类似的事——把一批 MySQL 业务库数据迁到数据仓库。但那次只有几百 GB,而且允许停机窗口。Aurora 这次是 10TB 级、不允许停业务、且 schema 差异更大。得既大胆又精细。
31.1 迁移挑战:10TB/千表的大规模离线迁移¶
Aurora 中国区有一个运行了多年的 SQL Server 数仓,约 10TB、千张表。平台建成后,这个遗留数仓需要迁到 Redshift。
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
subgraph 迁移挑战["迁移挑战"]
C1[ 数据量大:10TB<br/>不能简单导出导入]
C2[ 表结构复杂:千张表<br/>含存储过程/视图/函数]
C3[停机窗口短<br/>业务不能长时间中断]
C4[ schema 差异<br/>SQL Server ≠ Redshift 语法]
C5[ 断点续传<br/>10TB 不可能一次跑完]
end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class C1 bpError
class C2 bpError
class C3 bpError
class C4 bpError
class C5 bpError
linkStyle default stroke:#697077,stroke-width:2px
图 31-1 迁移挑战:10TB/千表的大规模离线迁移
为什么不用 AWS DMS¶
Trade-off
AWS DMS(Database Migration Service)是托管的数据库迁移工具。但当时评估后选择了自研管线,原因: 1. DMS 对 SQL Server→Redshift 的 schema 转换不完全(存储过程/函数不支持) 2. DMS 的 CDC 模式需要源库开启事务日志,遗留 SQL Server 权限受限 3. 10TB 全量迁移需要精细的分片控制和断点续传,DMS 的批量控制不够灵活
如果今天重新评估,DMS 的全量+CDC 能力已大幅增强,对于标准迁移场景值得优先考虑。自研管线适合"非标准、需精细控制"的场景。
31.2 数据库恢复与 schema 自动转换¶
数据库恢复子系统¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
BACKUP[SQL Server 备份文件] -->|恢复|RDS[RDS 临时实例<br/>完整恢复源库]
RDS -->|作为迁移源|MIGRATION[迁移管线]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class BACKUP bpData
class MIGRATION bpProcess
class RDS bpData
linkStyle default stroke:#697077,stroke-width:2px
图 31-2 数据库恢复子系统
为什么不直接连生产 SQL Server 迁移?生产库压力不能加。先恢复到 RDS 临时实例,在副本上做迁移。
Schema 自动转换¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
SRC[SQL Server DDL] --> SCT[Schema 转换引擎<br/>基于 AWS SCT + 自定义规则]
SCT --> TARGET[Redshift DDL]
subgraph 转换内容["转换内容"]
T1[数据类型映射<br/>NVARCHAR → VARCHAR]
T2[语法转换<br/>GETDATE → CURRENT_DATE]
T3[存储过程→ELT<br/>存储过程逻辑转为 ETL 作业]
T4[索引策略<br/>聚集索引 → 分布键+排序键]
end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class SCT bpProcess
class SRC bpData
class T1 bpInfo
class T2 bpInfo
class T3 bpInfo
class T4 bpInfo
class TARGET bpData
linkStyle default stroke:#697077,stroke-width:2px
图 31-3 Schema 自动转换
| 转换维度 | SQL Server | Redshift |
|---|---|---|
| 数据类型 | NVARCHAR/NTEXT | VARCHAR(Redshift 无 N 前缀) |
| 函数 | GETDATE()/ISNULL() | CURRENT_DATE/COALESCE() |
| 索引 | 聚集/非聚集索引 | 分布键 + 排序键 |
| 存储过程 | T-SQL 存储过程 | 转为 ETL 作业( Python/SQL) |
| 视图 | 视图 | 视图(语法适配) |
表 31-1 Schema 自动转换
引申
schema 转换不只是语法替换——更重要的是"架构范式转换"。SQL Server 是行式 OLTP 数据库,索引策略以 B-Tree 为主;Redshift 是列式 OLAP 数据库,索引策略变为"分布键(数据如何分布到节点)+ 排序键(数据如何排序存储)"。好的迁移不是"把 SQL Server 的表照搬到 Redshift",而是"重新设计适合列式 MPP 的表结构"。
把转换表里的维度落到具体 DDL,就是 SQL Server 的 OLTP 表结构改写为 Redshift 的列式 MPP 表结构——聚集索引变分布键+排序键,N 前缀类型去掉,函数替换:
-- 示意:SQL Server → Redshift DDL before/after
-- 【before】SQL Server(行式 OLTP,聚集索引)
CREATE TABLE fact_prescription (
prescription_id NVARCHAR(64) NOT NULL,
hospital_id NVARCHAR(32),
product_id NVARCHAR(32),
qty INT,
created_at DATETIME DEFAULT GETDATE(),
CONSTRAINT pk_fact PRIMARY KEY (prescription_id)
);
CREATE CLUSTERED INDEX idx_hospital ON fact_prescription(hospital_id); -- 聚集索引
-- 【after】Redshift(列式 MPP,分布键+排序键)
CREATE TABLE fact_prescription (
prescription_id VARCHAR(64) NOT NULL, -- NVARCHAR→VARCHAR(Redshift 无 N 前缀)
hospital_id VARCHAR(32) ENCODE az64, -- 列式压缩编码
product_id VARCHAR(32) ENCODE az64,
qty INT ENCODE az64,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- GETDATE()→CURRENT_TIMESTAMP
)
DISTKEY(hospital_id) -- 核心意图:聚集索引→分布键,按 join 键分布减少广播
SORTKEY(created_at); -- 核心意图:按查询常用过滤列排序,加速范围扫描
迁移性能基线¶
以下迁移性能数据为基于行业合理推演的量级,10TB/4000+ 表规模的典型表现,非 Aurora 真实迁移数据。
10TB 全量迁移在一个 24 小时窗口内完成一轮,四阶段的时间分配大致如下:
| 阶段 | 时间占比 | 说明 |
|---|---|---|
| ① 流式导出 | ~40% | 16-32 并发 JDBC 连接从 RDS 副本分片读取,大表独占连接、小表共享连接池 |
| ② 分片压缩+上传 | ~20% | LZOP 压缩比约 3:1(10TB 源 → ~3.3TB S3 中转),压缩与上传重叠 |
| ③ S3 中转 | ~10% | S3 暂存,MANIFEST 记录分片清单供 COPY 校验 |
| ④ Redshift COPY | ~30% | 并行从 S3 加载,利用 MPP 多节点 |
表 31-2 迁移性能基线
| 指标 | 数值 | 说明 |
|---|---|---|
| 全量迁移周期 | 24 小时内完成一轮,支撑 T+1 测试 | 分片压缩后 S3 中转 → Redshift COPY 批量加载 |
| 并发 JDBC 导出连接数 | 16-32 | 按表大小分组,大表独占,小表共享连接池 |
| 压缩比 | ~3:1(Parquet+Snappy) | 10TB 源 → ~3.3TB S3 中转 |
| 断点续传触发频率 | 日均 1-2 次 | 长尾大表或网络抖动,检查点基于源表主键区间,重传仅增量区间 |
| 最终对账一致性 | 行级 100% 对账通过 | 源表 COUNT → S3 Parquet COUNT → Redshift COUNT 三方对账 |
表 31-3 迁移性能基线
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
gantt
title 24h 全量迁移窗口时间分配
dateFormat HH:mm
axisFormat %H:%M
section 导出
流式导出 40%% :a1, 00:00, 9h
section 压缩上传
分片压缩+上传 20%% :a2, after a1, 5h
section S3中转
S3 暂存+MANIFEST 10%% :a3, after a2, 2h
section COPY
Redshift COPY 30%% :a4, after a3, 8h
图 31-4 迁移性能基线
31.3 流式导出 + 分片压缩 + 对象存储中转 + 批量加载¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
subgraph 迁移管线["四阶段迁移管线"]
S1[① 流式导出<br/>从源库流式读取数据]
S2[② 分片压缩<br/>按行数分片 + LZOP 压缩]
S3[③ 对象存储中转<br/>上传 S3 暂存]
S4[④ 批量加载<br/>Redshift COPY 从 S3 加载]
end
SRC[源数据库] --> S1
S1 --> S2
S2 --> S3
S3 --> S4
S4 --> RS[Redshift 目标表]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class RS bpData
class S1 bpProcess
class S2 bpProcess
class S3 bpData
class S4 bpProcess
class SRC bpData
linkStyle default stroke:#697077,stroke-width:2px
图 31-5 流式导出 + 分片压缩 + 对象存储中转 + 批量加载
设计要点¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
subgraph 管线设计要点["四阶段管线设计要点"]
D1[流式导出<br/>命名管道流式读取<br/>避免全量加载到内存]
D2[分片压缩<br/>按行数分片(如每 100 万行)<br/>LZOP 压缩减小传输量]
D3[ 对象存储中转<br/>S3 作为中转站<br/>解耦导出与加载]
D4[ 批量加载<br/>Redshift COPY 并行加载<br/>利用 MPP 并行]
end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class D1 bpInfo
class D2 bpInfo
class D3 bpInfo
class D4 bpInfo
linkStyle default stroke:#697077,stroke-width:2px
图 31-6 设计要点
| 阶段 | 设计 | 解决的问题 |
|---|---|---|
| 流式导出 | 命名管道(named pipe)流式读取 | 10TB 不可能全加载到内存 |
| 分片压缩 | 按行数分片 + LZOP 压缩 | 大文件难管理;压缩减网络传输 |
| S3 中转 | 导出和加载通过 S3 解耦 | 导出慢不阻塞加载,加载失败可从 S3 重试 |
| 批量加载 | Redshift COPY 并行加载分片 | MPP 并行加载,利用多节点 |
表 31-4 设计要点
Trade-off
四阶段管线比"直接 JDBC 导到 Redshift"复杂很多,但它解决了三个实际问题:①内存(流式不全量加载)②网络(压缩减传输)③容错(S3 中转可重试)。10TB 级别下,这些是必须的。
31.4 任务状态持久化、断点续传与并发资源治理¶
断点续传¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
START[开始迁移] --> CHECK{检查状态存储}
CHECK -->|有记录|RESUME[从断点恢复<br/>跳过已完成分片]
CHECK -->|无记录|BEGIN[从头开始]
RESUME --> MIGRATE[迁移分片]
BEGIN --> MIGRATE
MIGRATE --> SAVE[持久化状态<br/>记录已完成分片]
SAVE --> NEXT{还有分片?}
NEXT -->|是|MIGRATE
NEXT -->|否|DONE[迁移完成]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class BEGIN bpProcess
class CHECK bpDecision
class DONE bpSuccess
class MIGRATE bpProcess
class NEXT bpDecision
class RESUME bpProcess
class SAVE bpProcess
class START bpProcess
linkStyle default stroke:#697077,stroke-width:2px
图 31-7 断点续传
状态持久化¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
subgraph 状态存储["任务状态持久化"]
DB[本地状态数据库<br/>记录每表每分片状态]
DB --> S1[表名 + 分片号]
DB --> S2[状态:pending/running/done/failed]
DB --> S3[行数 + 校验和]
DB --> S4[时间戳]
end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class DB bpData
class S1 bpProcess
class S2 bpProcess
class S3 bpData
class S4 bpProcess
linkStyle default stroke:#697077,stroke-width:2px
图 31-8 状态持久化
| 设计要点 | 说明 |
|---|---|
| 分片级状态 | 每个分片独立记录状态,可精确恢复 |
| 校验和 | 记录行数+校验和,加载后验证完整性 |
| 本地存储 | 状态存本地轻量数据库,避免依赖远程服务 |
表 31-5 状态持久化
分片状态落到一个轻量表里,每个分片一行,断点续传时查这张表跳过已完成分片:
# 示意:分片状态 schema(断点续传的依据)
# CREATE TABLE shard_state (
# table_name VARCHAR(128),
# shard_id INT,
# status VARCHAR(16), -- pending/running/done/failed
# row_count BIGINT,
# checksum VARCHAR(64),
# updated_at TIMESTAMP,
# PRIMARY KEY (table_name, shard_id));
def resume_migration(table: str, shards: list) -> list:
# 核心意图:查状态表,跳过已完成分片,只跑 pending/failed
done = {s.shard_id for s in shard_state.query(table=table, status="done")}
return [s for s in shards if s.id not in done] # 仅重跑未完成分片
回滚方案¶
迁移不可能一次成功——某张表 COPY 失败、对账不一致、下游报表报错,都需要回滚到迁移前的状态。回滚方案要在迁移前就设计好,而不是出事再想:
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TD
FAIL[迁移失败/对账不一致] --> DECIDE{失败范围?}
DECIDE -->|单表|ROLL1[单表回滚<br/>Redshift 该表 TRUNCATE<br/>从 S3 中转重跑 COPY]
DECIDE -->|多表|ROLL2[多表回滚<br/>回退到上一轮成功快照]
DECIDE -->|全量|ROLL3[全量回滚<br/>Redshift 保留旧数据<br/>业务切回 SQL Server]
ROLL1 --> VERIFY[对账验证]
ROLL2 --> VERIFY
ROLL3 --> VERIFY
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class DECIDE bpDecision
class FAIL bpError
class ROLL1 bpProcess
class ROLL2 bpProcess
class ROLL3 bpProcess
class VERIFY bpSuccess
linkStyle default stroke:#697077,stroke-width:2px
图 31-9 回滚方案
| 回滚级别 | 触发条件 | 做法 | 恢复时间 |
|---|---|---|---|
| 单表 | 某表 COPY 失败/对账不一致 | Redshift 该表 TRUNCATE + 从 S3 中转重跑 COPY | 分钟级 |
| 多表 | 多表对账失败,但有上一轮快照 | 回退到上一轮成功的 Redshift 快照 | 小时级 |
| 全量 | 全量迁移数据质量问题 | Redshift 保留旧数据,业务切回 SQL Server | 需协调停机窗口 |
表 31-6 回滚方案
Trade-off
全量回滚是"最后手段"——业务要切回 SQL Server,可能需要停机窗口。因此迁移采用"灰度切换"而非"一刀切":先迁非关键表验证、再分批迁关键表、最后双跑对账一致后切换。任何一批失败,只回滚该批,不影响已成功的批。这是"既大胆又精细"的具体体现。
存储过程 → ETL 转换案例¶
迁移最费力的不是搬表,而是把 SQL Server 里的存储过程(SP)转成 Redshift/ETL 作业——SP 里往往是多年的业务逻辑沉淀,T-SQL 语法与 Redshift SQL 差异大。以"月度处方汇总"存储过程为例:
| 维度 | SQL Server 存储过程 | CDP ETL 作业 |
|---|---|---|
| 载体 | T-SQL 存储过程,定时 Job 调度 | PySpark batch job(Ch 17) |
| 逻辑位置 | 过程内临时表 + 游标循环 | DataFrame 转换链(声明式) |
| 调度 | SQL Server Agent | Step Functions + EventBridge(Ch 10) |
| 重跑 | 手动改参数重跑 | 配置驱动,按 biz_date 分区幂等重跑 |
| 可观测 | 过程内 PRINT 日志 | 审计日志 + 行数对账(Ch 20) |
表 31-7 存储过程 → ETL 转换案例
-- 示意:SP → ETL 的逻辑迁移(月度处方汇总)
-- 【before】SQL Server 存储过程(游标循环 + 临时表)
CREATE PROCEDURE sp_monthly_prescription AS
BEGIN
DECLARE cur CURSOR FOR SELECT DISTINCT hospital_id FROM dim_hospital;
-- 逐医院游标循环,累加处方量到 #temp,最后 INSERT 到汇总表
...
END;
-- 【after】Redshift SQL / PySpark(声明式,无游标,MPP 并行)
INSERT INTO monthly_prescription_summary
SELECT hospital_id, product_id, DATE_TRUNC('month', biz_date) AS month,
SUM(qty) AS total_qty -- 核心意图:游标循环→集合式聚合,MPP 自动并行
FROM fact_prescription
WHERE biz_date >= DATE_TRUNC('month', CURRENT_DATE) - INTERVAL '1 month'
GROUP BY hospital_id, product_id, DATE_TRUNC('month', biz_date);
引申
SP→ETL 转换的精髓是"过程式→声明式"——游标循环是 OLTP 思维(逐行处理),GROUP BY 聚合是 OLAP 思维(集合式、MPP 并行)。同样的业务逻辑,声明式 SQL 在 Redshift 上比游标循环快几个数量级。迁移不是"翻译语法",是"重写逻辑"——把 OLTP 的过程式思维彻底换成 OLAP 的集合式思维。
并发资源治理¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
POOL[并发池] -->|控制|PARALLEL[并行迁移多表]
PARALLEL --> LIMIT{资源限制}
LIMIT -->|CPU/内存/网络|THROTTLE[动态限流]
LIMIT -->|源库压力|THROTTLE
THROTTLE --> ADJUST[调整并发度]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class ADJUST bpProcess
class LIMIT bpDecision
class PARALLEL bpProcess
class POOL bpProcess
class THROTTLE bpProcess
linkStyle default stroke:#697077,stroke-width:2px
图 31-10 并发资源治理
引申
10TB 迁移最怕的不是"跑不完",是"跑的时候把源库压垮"。并发太高会压垮源库,太低则耗时太长——要在两者之间找平衡。通过监控源库 CPU/IO 指标,动态调整并发度,这是最稳妥的做法。
31.5 引申:托管迁移服务 vs 自研管线¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
subgraph 两种方案["迁移方案对比"]
DMS[AWS DMS<br/>托管迁移服务]
CUSTOM[自研管线<br/>本书方案]
end
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class CUSTOM bpProcess
class DMS bpExternal
linkStyle default stroke:#697077,stroke-width:2px
图 31-11 引申:托管迁移服务 vs 自研管线
| 维度 | AWS DMS | 自研管线 |
|---|---|---|
| 上手速度 | 快(配置即用) | 慢(需开发) |
| schema 转换 | 有限(需 SCT 辅助) | 完全可控 |
| 断点续传 | 内置 | 需自建 |
| 精细控制 | 弱 | 强(分片/并发/压缩) |
| 维护成本 | 低(托管) | 高(自维护) |
| 适合场景 | 标准迁移 | 非标准/大规模/需精细控制 |
表 31-8 引申:托管迁移服务 vs 自研管线
Trade-off
自研管线的价值是"完全可控"——分片大小、压缩算法、并发度、断点策略全部可调。代价是开发维护成本高。一次性迁移选 DMS 更经济;需要反复迁移或特殊需求的场景,自研管线更划算。
本章小结¶
- 10TB 迁移挑战:数据量大/表结构复杂/停机窗口短/schema 差异/断点续传
- 数据库恢复子系统:先恢复到 RDS 副本,在副本上迁移避免压垮生产库
- Schema 自动转换:不只是语法替换(DDL before/after),更是"OLTP→OLAP 的架构范式转换"(聚集索引→分布键+排序键)
- 迁移性能基线:24h 窗口四阶段(导出 40%/压缩上传 20%/S3 中转 10%/COPY 30%),16-32 并发连接,压缩比 3:1,断点续传日均 1-2 次,三方对账行级 100%
- 四阶段管线:流式导出→分片压缩→S3 中转→批量加载,解决内存/网络/容错三大问题
- 断点续传 + 并发治理:分片级状态持久化(shard_state schema)+ 动态限流保护源库
- 回滚方案:单表(分钟级)/多表(快照)/全量(切回 SQL Server)三级,采用灰度切换避免一刀切
- 存储过程→ETL 转换:过程式(游标循环)→声明式(GROUP BY 集合式聚合),是"重写逻辑"而非"翻译语法"
- DMS vs 自研:标准迁移选 DMS,非标准/大规模选自研
下一章
Ch 32 跨账号批量同步:双桶桥接架构 —— 迁移讲完了,接下来看跨 AWS 账号的数据同步——一个更棘手的工程难题。