O blog da AWS

Orquestrando Migrações de Objetos em Escala com AWS DataSync e Amazon S3

Por Mateus Pereira, Arquiteto de Soluções Sênior na AWS.

Introdução

Migrar grandes volumes de objetos de outros provedores de nuvem para o Amazon Simple Storage Service (Amazon S3) é uma das cargas de trabalho mais comuns (e mais críticas) em projetos de consolidação de armazenamento. Os motivos variam (otimização de custos, padronização de plataforma, consolidação mais ampla), mas os cenários mais frequentes envolvem mover dados do Microsoft Azure Blob Storage ou do Google Cloud Storage para o Amazon S3.

Na prática, essa migração é mais complexa do que parece. Cada provedor expõe APIs e modelos de autenticação distintos, não existe uma operação nativa de cópia entre nuvens, e abordagens baseadas em agentes intermediários se tornam um gargalo operacional quando o volume chega a centenas de milhares de containers.

O AWS DataSync resolve esses desafios diretamente: em seu modo Enhanced, ele se conecta ao armazenamento de origem por HTTPS, sem exigir que você implante ou gerencie qualquer infraestrutura intermediária, e processa transferências em paralelo com suporte a um número praticamente ilimitado de objetos por tarefa.

Neste post, você vai aprender como o DataSync se conecta a cada provedor de nuvem, como automatizar a criação de recursos para centenas de milhares de buckets e como orquestrar a execução dessas tarefas em lotes.

Pré Requisitos

Para acompanhar os exemplos deste post, você precisa de:

  • Uma conta AWS com permissões para criar recursos do AWS DataSync, Amazon S3 e Amazon DynamoDB.
  • Uma role IAM (ou usuário) com as permissões datasync:*, s3:*, dynamodb:PutItem/GetItem, e secretsmanager:GetSecretValue.
  • Um bucket do Amazon S3 já criado como destino da migração.
  • Uma tabela do Amazon DynamoDB com a chave primária sourceName (String) para persistir o estado da automação.
  • Python 3.9+ com os pacotes boto3, azure-storage-blob (para Azure) e/ou google-cloud-storage (para GCS) instalados.
  • Para migração do Azure: Um token SAS em nível de conta com permissões Read + List, armazenado no AWS Secrets Manager.
  • Para migração do GCS: Uma chave HMAC vinculada a uma conta de serviço com acesso de leitura, armazenada no AWS Secrets Manager como JSON ({“access_key”: “…”, “secret_key”: “…”}).

Visão geral da arquitetura

A arquitetura de uma migração entre nuvens sem agente segue um fluxo direto: o AWS DataSync em modo Enhanced transfere os dados de containers do Azure Blob Storage ou buckets do Google Cloud Storage para buckets ou prefixos do Amazon S3.

O DataSync oferece dois modos de tarefa, Basic e Enhanced, e a escolha entre eles é decisiva nesse tipo de migração.

Por que o modo Enhanced para essa carga de trabalho

A diferença entre os dois modos está em como o trabalho é executado e em que escala.

O modo Basic depende de uma máquina virtual de agente, implantada na nuvem de origem, que atua como intermediário para examinar e transferir os dados. Esse agente processa as etapas (listagem, preparação, transferência e verificação) de forma sequencial e está sujeito a cotas que limitam o número de objetos por tarefa.

O modo Enhanced não requer esse agente. O DataSync se conecta direto à origem por HTTPS e executa todas as etapas em paralelo, com suporte a um número praticamente ilimitado de objetos por tarefa.

Sem infraestrutura para gerenciar e sem o teto de escala do Basic. É essa combinação que torna o Enhanced a opção certa para cenários com centenas de milhares de objetos por container.

Conectando o DataSync ao Azure Blob Storage

Autenticação: tokens SAS

O DataSync se autentica no Azure Blob Storage usando um token Shared Access Signature (SAS). Os tokens SAS codificam permissões e um tempo de expiração, e vêm em dois escopos:

  • Token SAS em nível de container: Concede acesso a um único container.
  • Token SAS em nível de conta: Concede acesso a todos os containers de uma conta de armazenamento.

Para migrações em larga escala, use um token SAS em nível de conta para evitar gerar e gerenciar milhares de tokens individuais.

Permissões necessárias:

  • Leitura do Azure Blob (origem): Read e List.
  • Gravação no Azure Blob (destino): Read, List,Write e Delete

Importante: Verifique se a expiração do seu token SAS está definida bem além da duração esperada da migração. Se o token expirar durante a transferência, o DataSync retornará o erro ‘Failed to open directory’. Se isso acontecer, atualize a location com um novo token SAS e reinicie a tarefa.

A URL do container

Cada location do DataSync para o Azure Blob Storage mapeia para um único container, identificado por sua URL:

https://.blob.core.windows.net/

Por exemplo:

https://mystorageaccount.blob.core.windows.net/mycontainer

Você pode encontrar essa URL no Portal do Azure em Storage Account → Containers → [nome do container] → Properties.

Considerações sobre tiers de acesso

  • Ao ler do Azure Blob Storage, o DataSync pode copiar blobs apenas nos tiers hot e cool. Blobs no tier archive precisam ser reidratados para hot ou cold antes da transferência.
  • Ao gravar no Azure Blob Storage, o DataSync pode gravar nos tiers hot, cool e archive. (Observação: ao gravar no tier archive, o DataSync não consegue verificar a transferência se você optar por verificar todos os dados no destino.)
  • O DataSync não oferece suporte ao tier de acesso cool do Azure.

Conectando o DataSync ao Google Cloud Storage

Autenticação: chaves HMAC

O Google Cloud Storage (GCS) não possui um tipo de location dedicado no DataSync. Em vez disso, o DataSync acessa o GCS por meio da API XML compatível com o Amazon S3 que o GCS expõe, e se autentica usando uma chave HMAC (Hash-based Message Authentication Code) vinculada a uma conta de serviço do GCP.

Para configurar:

1. No Google Cloud Console, navegue até Cloud Storage / Settings / Interoperability.
2. Crie uma chave HMAC para uma conta de serviço que tenha acesso de leitura aos seus buckets do GCS.
3. Anote o Access Key ID e a Secret Access Key — esses valores são fornecidos ao DataSync ao criar a location.

Uma única chave HMAC vinculada a uma conta de serviço com acesso a todo o projeto pode cobrir todos os buckets de um projeto do GCP, simplificando o gerenciamento de credenciais em escala.

Limitação conhecida: tags de objetos (e por que isso importa mais no modo Enhanced)

Como o DataSync se comunica com o GCS por meio da API compatível com S3, as tags de objetos do GCS não são compatíveis com o comportamento de cópia de tags do DataSync. A forma como o DataSync reage depende do modo da tarefa, e essa é uma distinção importante:

  • Modo Enhanced (usado neste post): Se a location de origem não oferece suporte a tags de objetos e a opção de tags da tarefa estiver não especificada ou definida como ‘PRESERVE’, a execução da tarefa e a transferência de dados são interrompidas imediatamente.
  • Modo Basic: A tarefa é executada, mas reporta erros por objeto para objetos com tags, exibindo um aviso no Amazon CloudWatch semelhante a:
    [WARN] Failed to read metadata for file /your-bucket/your-object: S3 GetObjectTagging

Como este passo a passo usa o modo Enhanced, desativar as tags de objetos é obrigatório, não opcional. Defina a opção de tags de objetos como NONE para origens GCS — desmarque a opção Copy object tags no console, ou defina Options={‘ObjectTags’: ‘NONE’} ao criar a tarefa com a API/SDK. Se você não fizer isso, a tarefa é interrompida no momento em que você a inicia.

Tratamento de metadados

O DataSync copia atributos de metadados como ContentType, ContentEncoding, ContentLanguage e CacheControl do GCS em uma abordagem de melhor esforço (best-effort). Se o destino não oferecer suporte a esses atributos, eles são ignorados durante a verificação da tarefa.

O desafio de escala: centenas de milhares de buckets ou containers

Há um aspecto arquitetural determinante a considerar desde o início: o AWS DataSync opera exclusivamente no nível de um bucket ou container individual. Não existe funcionalidade nativa para inventariar a totalidade de um projeto do GCP, tampouco para migrar uma conta de armazenamento completa do Azure por meio de uma única tarefa. Na prática, isso significa que cada origem de dados deve ser configurada de forma independente — bucket a bucket, container a container.

Cada bucket ou container de origem requer:

1. Sua própria location de origem do DataSync.
2. Sua própria tarefa do DataSync.

Com centenas de milhares de containers ou buckets, a configuração manual pelo console se torna inviável. A solução é automatizar a criação de locations e tarefas usando o AWS SDK (Boto3) ou a AWS CLI em um loop programado. Essa automação precisa sobreviver a throttling, erros transitórios e reexecuções.

Considerações de produção para a camada de automação

Antes do código, vale destacar o que separa um script de demonstração de algo pronto para produção em larga escala.

O primeiro princípio é nunca deixar credenciais no código. Armazene o token SAS do Azure e a chave HMAC do GCS no AWS Secrets Manager e recupere-os em tempo de execução — nunca os deixe fixos no script nem os envie para o controle de versão.

O segundo ponto é esperar throttling. A criação de dezenas de milhares de locations e tarefas simultaneamente resultará no atingimento dos limites de taxa da API. Use o modo de retry adaptativo do Boto3 combinado com backoff próprio para absorver esses picos sem interromper o fluxo.

Igualmente importante é tornar o script idempotente. Você vai reexecutá-lo — seja por erro parcial, seja por novos containers adicionados à origem. Persista um registro do que já foi criado (aqui usamos o Amazon DynamoDB) para que reexecuções pulem o trabalho concluído em vez de criar duplicatas. Esse mesmo mapeamento (origem → ARN da location → ARN da tarefa) serve como entrada para a camada de orquestração e para o cleanup posterior.

Por fim, uma observação sobre paginação: as chamadas de listagem dos SDKs do Azure (list_containers()) e do GCS (list_buckets()) retornam iteradores paginados que buscam páginas adicionais de forma transparente, então um simples loop for já resolve o lado da origem. No lado da AWS, as chamadas list_* do DataSync exigem paginação explícita — use os paginators do Boto3 (mostrados na seção de limpeza).

Script de automação: Azure Blob Storage

import logging
import time

import boto3
from botocore.config import Config
from botocore.exceptions import ClientError
from azure.storage.blob import BlobServiceClient

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("azure-migration")

REGION = "us-east-1"
ACCOUNT_NAME = "mystorageaccount"
S3_DESTINATION_ARN = "arn:aws:datasync:us-east-1:123456789012:location/loc-0abc123s3destination"
SAS_SECRET_ID = "datasync/azure/account-sas-token"
STATE_TABLE = "datasync-migration-state"

# Retries adaptativos ajudam a absorver o throttling da API do DataSync em escala.
boto_config = Config(retries={"max_attempts": 10, "mode": "adaptive"})
datasync = boto3.client("datasync", region_name=REGION, config=boto_config)
secrets = boto3.client("secretsmanager", region_name=REGION)
state_table = boto3.resource("dynamodb", region_name=REGION).Table(STATE_TABLE)

def get_secret(secret_id: str) -> str:
"""Recupera uma credencial do AWS Secrets Manager em tempo de execução."""
return secrets.get_secret_value(SecretId=secret_id)["SecretString"]

def already_done(source_name: str) -> bool:
"""Verificação de idempotência para que reexecuções pulem containers concluídos."""
resp = state_table.get_item(Key={"sourceName": source_name})
return "Item" in resp and resp["Item"].get("status") == "TASK_CREATED"

def record_state(source_name: str, location_arn: str, task_arn: str) -> None:
state_table.put_item(
Item={
"sourceName": source_name,
"locationArn": location_arn,
"taskArn": task_arn,
"status": "TASK_CREATED",
}
)

def migrate_container(sas_token: str, container_name: str) -> None:
if already_done(container_name):
logger.info("Pulando %s (já criado)", container_name)
return

try:
# Cria a location de origem do Azure Blob (modo Enhanced, sem ARN de agente).
location = datasync.create_location_azure_blob(
ContainerUrl=f"https://{ACCOUNT_NAME}.blob.core.windows.net/{container_name}",
AuthenticationType="SAS",
SasConfiguration={"Token": sas_token},
)
source_location_arn = location["LocationArn"]

# Cria a tarefa do DataSync no modo Enhanced.
# ObjectTags=NONE é obrigatório para origens com API S3, como o GCS; é
# inofensivo para o Azure e mantém um único caminho de código consistente.
task = datasync.create_task(
SourceLocationArn=source_location_arn,
DestinationLocationArn=S3_DESTINATION_ARN,
Name=f"migrate-{container_name}"[:256],
TaskMode="ENHANCED",
Options={"ObjectTags": "NONE"},
)

record_state(container_name, source_location_arn, task["TaskArn"])
logger.info("Tarefa criada para %s", container_name)

except ClientError as exc:
# Registra e continua para que um container com problema não interrompa toda a execução.
logger.error("Falha ao configurar %s: %s", container_name, exc)

def main() -> None:
sas_token = get_secret(SAS_SECRET_ID)
blob_service = BlobServiceClient(
account_url=f"https://{ACCOUNT_NAME}.blob.core.windows.net",
credential=sas_token,
)

# list_containers() retorna um iterador paginado que busca páginas conforme necessário.
for container in blob_service.list_containers():
migrate_container(sas_token, container["name"])
time.sleep(0.05) # leve controle de ritmo no cliente para aliviar a pressão na API

if __name__ == "__main__":
main()

Script de automação: Google Cloud Storage

import json
import logging
import time

import boto3
from botocore.config import Config
from botocore.exceptions import ClientError
from google.cloud import storage as gcs

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("gcs-migration")

REGION = "us-east-1"
GCP_PROJECT = "my-gcp-project"
S3_DESTINATION_ARN = "arn:aws:datasync:us-east-1:123456789012:location/loc-0abc123s3destination"
HMAC_SECRET_ID = "datasync/gcs/hmac-key" # armazenado como {"access_key": "...", "secret_key": "..."}
STATE_TABLE = "datasync-migration-state"

boto_config = Config(retries={"max_attempts": 10, "mode": "adaptive"})
datasync = boto3.client("datasync", region_name=REGION, config=boto_config)
secrets = boto3.client("secretsmanager", region_name=REGION)
state_table = boto3.resource("dynamodb", region_name=REGION).Table(STATE_TABLE)

def get_hmac_credentials(secret_id: str) -> tuple[str, str]:
payload = json.loads(secrets.get_secret_value(SecretId=secret_id)["SecretString"])
return payload["access_key"], payload["secret_key"]

def already_done(source_name: str) -> bool:
resp = state_table.get_item(Key={"sourceName": source_name})
return "Item" in resp and resp["Item"].get("status") == "TASK_CREATED"

def record_state(source_name: str, location_arn: str, task_arn: str) -> None:
state_table.put_item(
Item={
"sourceName": source_name,
"locationArn": location_arn,
"taskArn": task_arn,
"status": "TASK_CREATED",
}
)

def migrate_bucket(access_key: str, secret_key: str, bucket_name: str) -> None:
if already_done(bucket_name):
logger.info("Pulando %s (já criado)", bucket_name)
return

try:
# Cria a location de origem do GCS via API de armazenamento de objetos compatível com S3
# (modo Enhanced, sem ARN de agente).
location = datasync.create_location_object_storage(
ServerHostname="storage.googleapis.com",
ServerProtocol="HTTPS",
ServerPort=443,
BucketName=bucket_name,
AccessKey=access_key,
SecretKey=secret_key,
)
source_location_arn = location["LocationArn"]

# ObjectTags=NONE é OBRIGATÓRIO para o GCS: no modo Enhanced a tarefa falha
# imediatamente se a preservação de tags ficar ativa para uma origem com API S3.
task = datasync.create_task(
SourceLocationArn=source_location_arn,
DestinationLocationArn=S3_DESTINATION_ARN,
Name=f"migrate-gcs-{bucket_name}"[:256],
TaskMode="ENHANCED",
Options={"ObjectTags": "NONE"},
)

record_state(bucket_name, source_location_arn, task["TaskArn"])
logger.info("Tarefa criada para %s", bucket_name)

except ClientError as exc:
logger.error("Falha ao configurar %s: %s", bucket_name, exc)

def main() -> None:
access_key, secret_key = get_hmac_credentials(HMAC_SECRET_ID)
gcs_client = gcs.Client(project=GCP_PROJECT)

# list_buckets() retorna um iterador paginado que busca páginas conforme necessário.
for bucket in gcs_client.list_buckets():
migrate_bucket(access_key, secret_key, bucket.name)
time.sleep(0.05)

if __name__ == "__main__":
main()

Estratégia de destino no S3

Você tem duas opções principais para mapear buckets/containers de origem para o S3:

  • Um bucket do S3 por bucket/container de origem: Isolamento completo, mas observe que contas AWS têm um limite padrão de 100 buckets do S3 (aumentável por meio de uma solicitação de aumento de cota de serviço). Com grandes quantidades de containers, essa abordagem exige um aumento de cota significativo.
  • Um bucket do S3 com prefixos (recomendado): Mapeie cada container de origem para um prefixo dentro de um único bucket do S3, por exemplo, ‘s3://my-migration-bucket//’. Isso é mais escalável e evita preocupações com a cota de buckets.

Orquestrando a migração em escala

Criar grandes quantidades de tarefas é apenas metade do desafio — é também necessário gerenciar a execução delas.

Execução em batches

O DataSync tem cotas de serviço sobre o número de execuções de tarefa simultâneas. Em vez de iniciar todas as tarefas simultaneamente, execute-as em batches (por exemplo, de 100 a 500 por vez), aguardando a conclusão de cada lote antes de iniciar o próximo.

Orquestração com AWS Step Functions

O AWS Step Functions é o serviço para orquestrar a execução de migração em ondas (wave-based). Os ARNs das tarefas que você persistiu no DynamoDB durante a criação se tornam a entrada:

1. Um estado Map itera sobre sua lista de ARNs de tarefas.
2. Cada iteração inicia uma execução de tarefa e aguarda a conclusão.
3. Limites de concorrência no estado Map controlam o tamanho do lote.
4. Tarefas com erro podem ser repetidas automaticamente ou roteadas para uma fila de mensagens não entregues (dead-letter queue) para investigação.

Como alternativa, o Amazon EventBridge Scheduler pode acionar batches de execuções de tarefa em uma programação, distribuindo a migração ao longo do tempo.

Monitoramento

O AWS DataSync publica métricas automaticamente no Amazon CloudWatch sob o namespace AWS/DataSync. As três métricas mais relevantes para acompanhar uma migração em lotes são BytesTransferred (volume acumulado movido), FilesTransferred (contagem de objetos copiados) e TasksExecutionsSucceeded / TasksExecutionsFailed (resultado de cada execução). Filtre por TaskId para inspecionar uma tarefa individual ou visualize o agregado para ter uma visão do progresso global.

Para ser notificado proativamente quando algo sair do esperado, crie um alarme do CloudWatch na métrica TasksExecutionsFailed com threshold ≥ 1 em um período de 5 minutos. Associe esse alarme a um tópico do Amazon SNS que envie notificações para o e-mail ou canal de chat da equipe de migração. Isso garante que você saiba de um lote com problemas em minutos, não horas.

Além das métricas agregadas, habilite os relatórios de tarefa (task reports) do DataSync. Esses relatórios geram um arquivo JSON no Amazon S3 ao final de cada execução, listando cada objeto transferido, ignorado ou com erro — incluindo o motivo. Para migrações com milhões de objetos, o relatório de tarefa é a forma mais eficiente de identificar exatamente quais objetos precisam de atenção sem percorrer logs linha a linha.

Azure Blob vs. GCS: comparação lado a lado

Dimensão Azure Blob Storage Google Cloud Storage
Tipo de location no DataSync create_location_azure_blob create_location_object_storage
Método de autenticação Token SAS Chave HMAC (Access Key + Secret)
Escopo da credencial SAS em nível de conta cobre todos os containers Uma chave HMAC cobre todos os buckets de um projeto
Agente necessário (Enhanced → S3) Não Não
Granularidade Por container Por bucket
Suporte a tags de objetos Suportado (namespace plano; tags ≤ 2 KB) Limitação conhecida — defina `ObjectTags=NONE
Tratamento do tier archive Deve reidratar blobs do tier archive antes da leitura Tier archive acessível imediatamente (custos de recuperação se aplicam)

Considerações de custo

Com grandes quantidades de buckets ou containers, os custos podem ser significativos e merecem planejamento antecipado. O componente dominante em escala tende a ser a taxa de egress da nuvem de origem — tanto o Azure quanto o GCP cobram pela saída de dados de suas redes, e esse valor cresce linearmente com o volume transferido. Além disso, a AWS cobra por GB transferido pelo DataSync, e os custos padrão de requisições PUT e armazenamento do Amazon S3 também se aplicam.

Para minimizar o custo total, o modo Enhanced já oferece uma vantagem direta: não há instância do Amazon Elastic Compute Cloud (Amazon EC2) para provisionar e manter durante a migração. Quando possível, transfira a partir de uma região do GCP ou Azure geograficamente próxima da sua região de destino na AWS para reduzir a latência — embora as taxas de egress geralmente sejam as mesmas independentemente da região de destino. Para conjuntos de dados muito grandes (petabytes), avalie o AWS Snowball Edge para uma transferência inicial em massa offline, seguida pelo DataSync para sincronização incremental contínua (delta sync).

Cleanup

Após confirmar que todas as execuções de tarefa foram concluídas com sucesso — consultando o status no DynamoDB ou na aba “Task history” do console do DataSync — e validar que a contagem de objetos no bucket de destino corresponde à origem, você pode remover os recursos de migração. As locations e tarefas do DataSync não geram cobranças de armazenamento, mas a limpeza evita desordem e reexecuções acidentais. Observe o uso dos paginators do Boto3 — as APIs list_* do DataSync exigem paginação explícita, ao contrário dos iteradores dos SDKs de nuvem de origem usados anteriormente.

import boto3
from botocore.config import Config

REGION = "us-east-1"
boto_config = Config(retries={"max_attempts": 10, "mode": "adaptive"})
datasync = boto3.client("datasync", region_name=REGION, config=boto_config)

def delete_migration_tasks(name_prefix: str = "migrate-") -> None:
"""Exclui as tarefas (e suas locations de origem) criadas para a migração."""
task_paginator = datasync.get_paginator("list_tasks")
for page in task_paginator.paginate():
for task in page["Tasks"]:
if not task["Name"].startswith(name_prefix):
continue
details = datasync.describe_task(TaskArn=task["TaskArn"])
source_arn = details["SourceLocationArn"]

datasync.delete_task(TaskArn=task["TaskArn"])
datasync.delete_location(LocationArn=source_arn)
print(f"Excluída {task['Name']} e sua location de origem")

if __name__ == "__main__":
delete_migration_tasks()

Execute a limpeza somente após confirmar que todas as tarefas foram concluídas com sucesso e validar os dados no S3. Mantenha a location de destino compartilhada no S3 se você ainda precisar dela.

Conclusão

O modo Enhanced do AWS DataSync torna as migrações entre nuvens do Azure Blob Storage e do Google Cloud Storage para o Amazon S3 simples — sem infraestrutura de agente para implantar ou gerenciar, apenas conectividade HTTPS direta autenticada com um token SAS ou uma chave HMAC.

O principal desafio em escala é que o DataSync opera no nível individual de bucket ou container, exigindo uma location e uma tarefa por origem. Para migrações que envolvem dezenas ou centenas de milhares de buckets, a automação via AWS SDK é essencial, com credenciais no Secrets Manager, retries adaptativos para throttling, idempotência para reexecuções seguras e estado persistido para alimentar uma estratégia de orquestração em lotes usando Step Functions ou EventBridge Scheduler.

Seja migrando do Azure, do GCP ou de ambos simultaneamente, este post fornece um blueprint repetível e escalável para a consolidação de armazenamento de objetos em larga escala na AWS. Em cenários reais, clientes utilizaram essa abordagem para migrar mais de 200.000 containers e dezenas de petabytes em semanas — não meses — com orquestração totalmente automatizada e sem nenhum agente intermediário.

Comece pela documentação oficial:

Sobre o Autor

Mateus Pereira é um Arquiteto de Soluções Sênior de Setor Público na AWS, atuando no segmento de Educação e membro gold do TFC de Storage da AWS. Sua experiência profissional anterior inclui desenvolvimento de software, arquitetura de aplicações em núvem com foco em virtualização, armazenamento e proteção de dados. Bacharel em Sistemas de Informação e MBA em Arquitetura de Soluções pela FIAP.