☁️ Capítulo 12 · Nivel Avanzado

Cloud Platforms para Data Engineering

AWS, Google Cloud y Azure son los tres grandes proveedores cloud. Cada uno tiene su ecosistema de servicios de datos. Conocer los equivalentes entre plataformas y las mejores prácticas de cada una es fundamental para el mercado laboral de 2026.

⏱️ Lectura: ~60 min
🎯 Nivel: Avanzado
📌 Foco: Práctico y comparativo

AWS: El Ecosistema de Datos

Amazon Web Services es el cloud líder con la mayor cuota de mercado. Su ecosistema de datos es el más maduro y extenso.

☁️ Amazon Web Services

Stack de Datos AWS 2026

S3

Object storage. El Data Lake base. Duradero 11 nines, infinitamente escalable.

Redshift

Data Warehouse columnar. Redshift Serverless para pago por uso.

Glue

ETL serverless + Data Catalog. PySpark/Python sin gestionar clusters.

Athena

Query SQL sobre S3 sin infraestructura. Pago por query (5$/TB).

EMR

Hadoop/Spark managed. EMR Serverless para escalar automáticamente.

Kinesis

Streaming equivalente a Kafka. Kinesis Data Streams + Firehose.

MSK

Apache Kafka managed. Para equipos que prefieren Kafka sobre Kinesis.

Lambda

Funciones serverless. Para transformaciones ligeras event-driven.

Step Functions

Orquestación serverless de workflows. Alternativa a Airflow para AWS-native.

Lake Formation

Data Lake governance y permisos fine-grained sobre S3 + Glue Catalog.

AWS S3: Mejores Prácticas

"""
Trabajar con S3 eficientemente desde Python
boto3 + s3fs + pandas
"""
import boto3
import s3fs
import pandas as pd
from pathlib import Path

# ── BOTO3: AWS SDK ──────────────────────────────────────────────────────────
s3 = boto3.client('s3',
    region_name='us-east-1',
    # Usar IAM roles en producción, nunca credenciales hardcoded
)

# Subir archivo
s3.upload_file('local_file.parquet', 'my-data-lake', 'raw/orders/2026/01/01/data.parquet')

# Listar objetos con paginación
paginator = s3.get_paginator('list_objects_v2')
pages = paginator.paginate(Bucket='my-data-lake', Prefix='raw/orders/2026/')

all_keys = []
for page in pages:
    if 'Contents' in page:
        all_keys.extend([obj['Key'] for obj in page['Contents']])

# Leer Parquet directamente desde S3 (sin descargar)
fs = s3fs.S3FileSystem()
df = pd.read_parquet('s3://my-data-lake/silver/orders/year=2026/month=1/', 
                      filesystem=fs,
                      columns=['order_id', 'customer_id', 'amount'])

# ── BEST PRACTICES para S3 Data Lake ─────────────────────────────────────
# 1. Particionamiento Hive-style: year=YYYY/month=MM/day=DD/
# 2. Usar prefixes para distribución de carga (key prefix sharding)
# 3. Tamaño ideal de archivos: 128MB - 1GB
# 4. Habilitar S3 Versioning para tablas críticas
# 5. S3 Lifecycle policies para archivar datos antiguos a Glacier
# 6. Encryption at rest: S3-SSE o KMS para datos sensibles
# 7. VPC Endpoints: acceso a S3 sin internet (seguridad)

# ── AWS GLUE: ETL Serverless ───────────────────────────────────────────────
# Glue Job en PySpark - se ejecuta en un cluster managed por AWS
import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job

args = getResolvedOptions(sys.argv, ['JOB_NAME', 'run_date'])
sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args['JOB_NAME'], args)

# Leer desde Glue Data Catalog
orders_dyf = glueContext.create_dynamic_frame.from_catalog(
    database='raw_data',
    table_name='orders',
    push_down_predicate=f"run_date = '{args['run_date']}'",
)

# Convertir a DataFrame Spark para transformaciones complejas
orders_df = orders_dyf.toDF()
# ... transformaciones ...

# Escribir de vuelta al Data Lake con Glue
output_dyf = DynamicFrame.fromDF(orders_df, glueContext, "output")
glueContext.write_dynamic_frame.from_options(
    frame=output_dyf,
    connection_type='s3',
    connection_options={
        'path': 's3://my-data-lake/silver/orders/',
        'partitionKeys': ['year', 'month'],
    },
    format='glueparquet',
)

job.commit()

# ── AMAZON ATHENA: SQL sobre S3 ────────────────────────────────────────────
import boto3
import time
import pandas as pd

athena = boto3.client('athena', region_name='us-east-1')

def run_athena_query(sql: str, database: str, output_bucket: str) -> pd.DataFrame:
    """Ejecuta una query en Athena y retorna un DataFrame."""
    response = athena.start_query_execution(
        QueryString=sql,
        QueryExecutionContext={'Database': database},
        ResultConfiguration={
            'OutputLocation': f's3://{output_bucket}/athena-results/',
            'EncryptionConfiguration': {'EncryptionOption': 'SSE_S3'}
        },
        WorkGroup='primary'
    )
    
    execution_id = response['QueryExecutionId']
    
    # Esperar a que termine
    while True:
        result = athena.get_query_execution(QueryExecutionId=execution_id)
        state = result['QueryExecution']['Status']['State']
        
        if state == 'SUCCEEDED':
            break
        elif state in ['FAILED', 'CANCELLED']:
            raise Exception(f"Query failed: {result['QueryExecution']['Status']}")
        
        time.sleep(2)
    
    # Leer resultados desde S3
    output_path = result['QueryExecution']['ResultConfiguration']['OutputLocation']
    return pd.read_csv(output_path)

# Uso
df = run_athena_query(
    "SELECT customer_id, SUM(amount) FROM silver.orders WHERE year='2026' GROUP BY 1",
    database='silver',
    output_bucket='my-query-results'
)

Google Cloud: El Ecosistema de Datos

☁️ Google Cloud Platform

Stack de Datos GCP 2026

Cloud Storage (GCS)

Equivalente a S3. Object storage para Data Lakes.

BigQuery

DWH serverless líder. Analítica a escala de petabytes con SQL.

Dataflow

Apache Beam managed. Batch + Stream en una sola API.

Pub/Sub

Messaging serverless. Equivalente a Kinesis para streaming.

Dataproc

Hadoop/Spark managed. Equivalente a EMR de AWS.

Cloud Composer

Apache Airflow managed. Orquestación de workflows.

Vertex AI

ML Platform. AutoML, Workbench, Feature Store, MLOps.

Dataplex

Data Mesh y governance. Cataloga y gestiona activos de datos.

# ── GOOGLE CLOUD STORAGE ─────────────────────────────────────────────────
from google.cloud import storage

storage_client = storage.Client()
bucket = storage_client.bucket('my-data-lake-gcs')

# Upload
blob = bucket.blob('raw/orders/2026/01/01/data.parquet')
blob.upload_from_filename('local_data.parquet')

# Con metadata y content type
blob.content_type = 'application/octet-stream'
blob.metadata = {'source': 'orders-api', 'version': '2'}
blob.patch()

# Leer Parquet directamente con GCSFileSystem
import gcsfs
fs = gcsfs.GCSFileSystem()
df = pd.read_parquet('gcs://my-data-lake-gcs/silver/orders/', filesystem=fs)

# ── BIGQUERY: Analytics Serverless ────────────────────────────────────────
from google.cloud import bigquery

bq = bigquery.Client()

# Insertar desde DataFrame
job_config = bigquery.LoadJobConfig(
    schema=[
        bigquery.SchemaField("order_id", "INTEGER"),
        bigquery.SchemaField("amount", "FLOAT"),
        bigquery.SchemaField("created_at", "TIMESTAMP"),
    ],
    write_disposition="WRITE_APPEND",
    time_partitioning=bigquery.TimePartitioning(
        type_=bigquery.TimePartitioningType.DAY,
        field="created_at"
    ),
    clustering_fields=["customer_id", "status"]
)

job = bq.load_table_from_dataframe(df, "project.dataset.orders", job_config=job_config)
job.result()  # Esperar que termine

# Ejecutar query y obtener resultado
query = """
    SELECT customer_id, SUM(amount) AS total
    FROM `project.dataset.orders`
    WHERE DATE(created_at) = @run_date
    GROUP BY customer_id
"""
job_config_q = bigquery.QueryJobConfig(
    query_parameters=[bigquery.ScalarQueryParameter("run_date", "DATE", "2026-01-15")]
)
df_result = bq.query(query, job_config=job_config_q).to_dataframe()

# ── APACHE BEAM con Dataflow ───────────────────────────────────────────────
import apache_beam as beam
from apache_beam.options.pipeline_options import PipelineOptions, GoogleCloudOptions

options = PipelineOptions()
google_cloud_options = options.view_as(GoogleCloudOptions)
google_cloud_options.project = 'my-project'
google_cloud_options.region = 'us-central1'
google_cloud_options.staging_location = 'gs://my-bucket/staging'
google_cloud_options.temp_location = 'gs://my-bucket/temp'
options.view_as(beam.options.pipeline_options.StandardOptions).runner = 'DataflowRunner'

with beam.Pipeline(options=options) as p:
    orders = (
        p
        | 'ReadFromPubSub' >> beam.io.ReadFromPubSub(topic='projects/myproject/topics/orders')
        | 'ParseJSON' >> beam.Map(json.loads)
        | 'FilterValid' >> beam.Filter(lambda x: x.get('amount', 0) > 0)
        | 'TransformOrders' >> beam.Map(lambda x: {**x, 'revenue_cat': 'large' if x['amount'] > 500 else 'small'})
        | 'WriteToBigQuery' >> beam.io.WriteToBigQuery(
            'my-project:analytics.orders_stream',
            schema='order_id:INTEGER,amount:FLOAT,revenue_cat:STRING',
            write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND,
        )
    )

Azure: El Ecosistema de Datos

☁️ Microsoft Azure

Stack de Datos Azure 2026

ADLS Gen2

Azure Data Lake Storage. Object storage con filesystem semantics.

Azure Synapse

Analytics unificado: DWH + Spark + Pipelines en una plataforma.

Azure Data Factory

ETL/ELT managed. 90+ conectores. Orquestación visual.

Event Hubs

Kafka-compatible streaming. Ingestión de millones de eventos/seg.

Databricks on Azure

Spark + Delta Lake. Muy común en el stack Azure-Databricks.

Power BI

BI y visualización. Integración nativa con todo el stack Azure.

Azure Purview

Data Catalog y Data Governance para todo el stack Azure.

Azure ML

MLOps y Feature Store. Comparable a Vertex AI de GCP.

Comparativa Multi-Cloud e IaC con Terraform

Equivalencias entre Clouds

CategoríaAWSGoogle CloudAzure
Object StorageS3Cloud Storage (GCS)ADLS Gen2 / Blob Storage
Data WarehouseRedshiftBigQuerySynapse Analytics
Spark ManagedEMRDataprocDatabricks / HDInsight
Serverless ETLAWS GlueDataflow (Beam)Azure Data Factory
SQL on LakeAthenaBigQuerySynapse Serverless SQL
Event StreamingKinesis / MSKPub/SubEvent Hubs
Workflow OrchestrationMWAA (Airflow)Cloud ComposerManaged Airflow
Data CatalogGlue Catalog / Lake FormationDataplexMicrosoft Purview
ML PlatformSageMakerVertex AIAzure ML

Infrastructure as Code con Terraform

# ── TERRAFORM: Infraestructura como código para Data Engineering ──────────
# Nunca crear infraestructura manualmente en producción. Siempre IaC.

# main.tf - Stack de datos AWS con Terraform
terraform {
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "data-platform/terraform.tfstate"
    region = "us-east-1"
  }
}

# ── S3 Data Lake ────────────────────────────────────────────────────────────
resource "aws_s3_bucket" "data_lake" {
  bucket = "${var.environment}-data-lake-${var.account_id}"
}

resource "aws_s3_bucket_versioning" "data_lake" {
  bucket = aws_s3_bucket.data_lake.id
  versioning_configuration { status = "Enabled" }
}

resource "aws_s3_bucket_lifecycle_configuration" "data_lake" {
  bucket = aws_s3_bucket.data_lake.id

  rule {
    id     = "raw-to-glacier"
    status = "Enabled"
    filter { prefix = "raw/" }
    transition { days = 90; storage_class = "GLACIER" }
    expiration { days = 365 }
  }
}

# ── Redshift Serverless ────────────────────────────────────────────────────
resource "aws_redshiftserverless_namespace" "main" {
  namespace_name = "${var.environment}-data-warehouse"
  admin_username = "admin"
  admin_user_password = var.redshift_password  # Usar Secrets Manager en producción
  db_name        = "analytics"
}

resource "aws_redshiftserverless_workgroup" "main" {
  namespace_name = aws_redshiftserverless_namespace.main.namespace_name
  workgroup_name = "${var.environment}-workgroup"
  base_capacity  = 8  # RPUs
  
  config_parameter {
    parameter_key   = "max_query_execution_time"
    parameter_value = "3600"
  }
}

# ── MSK (Managed Kafka) ───────────────────────────────────────────────────
resource "aws_msk_cluster" "main" {
  cluster_name           = "${var.environment}-kafka"
  kafka_version          = "3.6.0"
  number_of_broker_nodes = 3

  broker_node_group_info {
    instance_type   = "kafka.m5.large"
    client_subnets  = var.private_subnet_ids
    storage_info {
      ebs_storage_info { volume_size = 100 }
    }
  }

  encryption_info {
    encryption_in_transit {
      client_broker = "TLS"
      in_cluster    = true
    }
  }
}

# ── AWS Glue Catalog ──────────────────────────────────────────────────────
resource "aws_glue_catalog_database" "raw" {
  name = "${var.environment}_raw"
}

resource "aws_glue_crawler" "orders" {
  name          = "${var.environment}-orders-crawler"
  role          = aws_iam_role.glue_role.arn
  database_name = aws_glue_catalog_database.raw.name

  s3_target {
    path = "s3://${aws_s3_bucket.data_lake.bucket}/raw/orders/"
  }

  schedule = "cron(0 */6 * * ? *)"  # Cada 6 horas
}

# Aplicar: terraform init, terraform plan, terraform apply
💡 Recomendación de Cloud 2026

Si empiezas de cero: AWS si necesitas el ecosistema más maduro y grande, GCP si tu caso de uso central es analytics/ML (BigQuery es el mejor DWH cloud), Azure si tu empresa ya tiene Microsoft Stack (Office 365, Windows Azure AD). Para entrevistas: Conocer los conceptos de uno en profundidad es más valioso que conocer los tres superficialmente.