You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何以最低停机时间将DynamoDB表迁移至Global DynamoDB表?

无停机迁移DynamoDB到全局表的实操经验与工具建议

我之前参与过好几次大规模DynamoDB到全局表的无停机迁移,你的方案其实已经踩中了AWS推荐的最佳实践路径(双写+渐进式流量切换),我来分享一些实操细节、简化双读的技巧和工具建议:

你的迁移方案的优化细节

先给你的方案点个赞,这是最稳妥的无停机迁移模式,补充几个关键注意点:

  • 创建全局表时:必须保证和原表的Schema完全一致——包括GSI/LSI、加密配置、TTL设置、读写容量模式(建议用按需模式避免迁移期间吞吐量瓶颈),全局表创建后会自动同步后续写入,但历史数据需要单独迁移。
  • 双写双读阶段:你的读写逻辑非常合理:写操作只指向全局表,读操作优先全局表、无数据则降级到原表。这个逻辑能避免迁移期间的新旧数据不一致,同时保证业务无感知。

简化双表读取操作的封装方案

基于boto3的通用封装

可以写一个全局的读取工具函数,把双读逻辑封装起来,业务代码不用重复写判断:

import boto3
from botocore.exceptions import ClientError

def get_dual_table_item(global_table_name, original_table_name, key, region="us-east-1"):
    dynamodb = boto3.client("dynamodb", region_name=region)
    
    # 优先读取全局表
    try:
        response = dynamodb.get_item(TableName=global_table_name, Key=key)
        if "Item" in response:
            return response["Item"]
    except ClientError as e:
        # 全局表读取失败(比如暂未同步到该区域),直接降级
        pass
    
    # 降级读取原表
    response = dynamodb.get_item(TableName=original_table_name, Key=key)
    return response.get("Item")

# 使用示例
user_item = get_dual_table_item(
    "Global-Users", 
    "Original-Users", 
    {"user_id": {"S": "user_123"}}
)

如果需要批量读取,可以封装batch_get_item的双表处理逻辑,注意合并两个表的返回结果。

基于PynamoDB的Mixin封装

如果用PynamoDB做ORM,可以写一个Mixin类来实现自动双读,业务代码几乎不用修改:

from pynamodb.models import Model
from pynamodb.exceptions import DoesNotExist

class DualReadModelMixin:
    @classmethod
    def get(cls, hash_key, range_key=None, consistent_read=False, original_model=None):
        try:
            # 先尝试全局表模型
            return super().get(hash_key, range_key, consistent_read)
        except DoesNotExist:
            # 无数据则降级到原表模型
            if original_model:
                return original_model.get(hash_key, range_key, consistent_read)
            raise

# 定义全局表模型
class GlobalUserModel(DualReadModelMixin, Model):
    class Meta:
        table_name = "Global-Users"
        region = "us-east-1"
    user_id = UnicodeAttribute(hash_key=True)
    username = UnicodeAttribute()

# 定义原表模型
class OriginalUserModel(Model):
    class Meta:
        table_name = "Original-Users"
        region = "us-east-1"
    user_id = UnicodeAttribute(hash_key=True)
    username = UnicodeAttribute()

# 业务代码调用(完全不用关心双读逻辑)
try:
    user = GlobalUserModel.get("user_123", original_model=OriginalUserModel)
except DoesNotExist:
    # 处理真正的不存在逻辑
    pass

高效迁移原表历史数据的工具

针对大数据量的表,推荐几个比手动导出导入更高效的方案:

  • AWS Glue ETL任务:适合TB级别的超大表,支持并行扫描和写入,能自动处理吞吐量限制,还可以过滤不需要迁移的数据。只需要配置源表为原DynamoDB表,目标表为全局表,运行任务即可。
  • 自定义批量复制脚本:用boto3的scan+batch_write_item,配合重试机制避免限流:
import boto3
from tenacity import retry, stop_after_attempt, wait_exponential

dynamodb = boto3.resource("dynamodb")
original_table = dynamodb.Table("Original-Users")
global_table = dynamodb.Table("Global-Users")

@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=10))
def batch_write_items(items):
    with global_table.batch_writer() as batch:
        for item in items:
            batch.put_item(Item=item)

def copy_historical_data():
    last_evaluated_key = None
    while True:
        scan_params = {"ExclusiveStartKey": last_evaluated_key} if last_evaluated_key else {}
        response = original_table.scan(**scan_params)
        items = response.get("Items", [])
        if items:
            batch_write_items(items)
        last_evaluated_key = response.get("LastEvaluatedKey")
        if not last_evaluated_key:
            break

注意迁移期间要开启原表和全局表的自动扩缩容,避免出现限流导致迁移停滞。

其他可选迁移思路

  • DynamoDB Streams补漏:如果原表已经开启了Streams,可以在双写阶段用Streams同步遗漏数据——比如写全局表失败的请求,可以通过Streams重新写入全局表,保证数据一致性。
  • 蓝绿部署式切换:如果你的应用是微服务架构,可以先部署一批只读写全局表的实例,逐步把流量切过去,最后下线读写原表的实例。这种方式适合能做流量灰度的场景。

迁移后的验证与收尾

不要急着删除原表:

  1. 监控全局表的CloudWatch指标,确认所有读写流量都已经切换到全局表。
  2. 抽样对比原表和全局表的数据,或者用describe_table的ItemCount做近似校验。
  3. 保留原表1-2周作为备份,确认业务完全稳定后再删除。

内容的提问来源于stack exchange,提问作者Raf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:38:40