AWS Glue PySpark vs DynamicFrame

Cuidados ao utilizar o AWS Glue: PySpark vs Glue DynamicFrame

A conveniência das abstrações do Glue pode custar caro em performance e dinheiro. Entenda quando usar Spark puro e quando confiar no DynamicFrame.

Você já passou horas debugando um Job do AWS Glue que deveria levar 10 minutos, mas levou 2 horas? Ou viu a fatura da AWS no final do mês e se perguntou "por que esses jobs custaram tanto?".

A verdade é que o AWS Glue é uma ferramenta poderosa quando você entende suas nuances. Mas existe uma armadilha silenciosa que pega até engenheiros experientes: a escolha entre usar DynamicFrames (a abstração nativa do Glue) ou DataFrames do Spark puro.

Essa escolha parece técnica e irrelevante no início. Mas ela pode significar a diferença entre um pipeline que processa petabytes eficientemente e um que queima orçamento enquanto arrasta processamento.

Neste artigo, vou abrir a caixa preta do AWS Glue. Vamos entender exatamente o que acontece por baixo dos panos, quando as abstrações ajudam, quando elas atrapalham, e como construir pipelines que sejam rápidos e econômicos.

Spoiler: A resposta não é "use sempre Spark puro" nem "confie cegamente no Glue". Como em tudo na engenharia, é sobre saber quando usar cada ferramenta.

1O Dilema do Engenheiro de Dados

Quando você abre um Job do AWS Glue pela primeira vez, se depara com duas escolhas aparentemente simples: usar o DynamicFrame nativo do Glue ou trabalhar com DataFrames do Spark puro. Essa decisão, frequentemente ignorada, pode significar a diferença entre um pipeline que processa terabytes em minutos e um que custa 5 vezes mais e leva horas para falhar.

O AWS Glue promete abstrair a complexidade do Spark, lidando automaticamente com esquemas variáveis, evolução de dados e gerenciamento de estado. Mas essa "mágica" tem um preço: performance degradada, perda de otimizações críticas e custos operacionais elevados.

A questão central: Quando a conveniência das abstrações do Glue justifica o custo computacional? E quando você deve "sujar as mãos" com Spark puro para obter desempenho máximo?

2A Arquitetura por Trás: DynamicFrame vs DataFrame

2.1 Spark DataFrame: Performance Bruta

O Apache Spark é um mestre em otimização. Quando você cria um DataFrame Spark, o esquema é fixado antecipadamente. Isso permite que o motor Tungsten armazene dados em formato binário compacto na memória, utilizando instruções SIMD (Single Instruction, Multiple Data) da CPU para processar múltiplos registros simultaneamente.

A mágica está na vetorização: uma única instrução de CPU pode aplicar a mesma operação em dezenas de valores de uma vez. Isso é possível porque o Spark sabe exatamente onde cada campo está na memória e qual seu tipo.

2.2 DynamicFrame: Flexibilidade com Custo

O DynamicFrame foi projetado para a realidade caótica dos Data Lakes: arquivos JSON com esquemas inconsistentes, logs onde o campo id ora é string, ora é inteiro. Cada DynamicRecord é autodescritivo — carrega metadados sobre seu próprio esquema.

Essa flexibilidade impede otimizações de baixo nível. O processador precisa verificar o tipo de cada campo, linha por linha, quebrando a capacidade de vetorização. O resultado? Throughput de CPU significativamente menor para operações simples de filtro ou agregação.

Implicação Real: Em benchmarks, transformações aritméticas simples podem executar até 3-5x mais lentas em DynamicFrames comparadas a DataFrames Spark otimizados, especialmente em datasets com esquema uniforme.

2.3 Comparação Técnica Direta

Característica AWS Glue DynamicFrame Spark DataFrame Nativo
Representação em Memória Objetos baseados em linha com overhead de metadados por registro Formato binário Tungsten (colunar/linha otimizada), off-heap, compacto
Verificação de Tipos Runtime, por registro. Impede vetorização SIMD eficiente Compile-time (Catalyst Analysis). Habilita vetorização total
Gestão de Memória Conservadora; spilling para disco precoce para evitar OOM Agressiva; maximiza uso de RAM. Risco de OOM se mal configurado
Evolução de Esquema Nativa (ResolveChoice), lida com tipos mistos no mesmo dataset Requer normalização prévia ou Schema Merging (custoso)

3O Custo Oculto: toDF() e fromDF()

Um dos anti-padrões mais perigosos no Glue é o "ping-pong" entre DynamicFrame e DataFrame. Desenvolvedores frequentemente fazem:

# ❌ ANTI-PADRÃO: Conversões repetidas
df_dynamic = glueContext.create_dynamic_frame.from_catalog(...)
df_spark = df_dynamic.toDF()  # Conversão 1
df_filtered = df_spark.filter(...)
df_dynamic2 = DynamicFrame.fromDF(df_filtered, glueContext, "df")  # Conversão 2
df_spark2 = df_dynamic2.toDF()  # Conversão 3 😱

Cada conversão toDF() não é gratuita. Ela pode exigir:

Em grandes volumes, uma única chamada toDF() pode adicionar vários minutos de latência. Em loops ou pipelines iterativos, o impacto é exponencial.

3.1 Estratégia Otimizada: "Convert Once, Process Native"

# ✅ PADRÃO RECOMENDADO
# 1. Leia com DynamicFrame (aproveite leitores C++ do Glue 4.0+)
df_dynamic = glueContext.create_dynamic_frame.from_catalog(
    database="db",
    table_name="table",
    push_down_predicate="(ano == '2024')"
)

# 2. Resolva tipos ambíguos uma única vez
df_resolved = df_dynamic.resolveChoice(specs=[('price', 'cast:double')])

# 3. Converta para Spark DataFrame uma única vez
df_spark = df_resolved.toDF()

# 4. Faça TODO o processamento em Spark nativo
df_result = (df_spark
    .filter(col("amount") > 100)
    .groupBy("category")
    .agg(sum("amount").alias("total"))
    .orderBy("total", ascending=False)
)

# 5. Se necessário, converta de volta apenas na escrita
df_dynamic_final = DynamicFrame.fromDF(df_result, glueContext, "final")
glueContext.write_dynamic_frame.from_catalog(df_dynamic_final, ...)

4O Problema da "Caixa Preta": Catalyst Optimizer Bloqueado

O Otimizador Catalyst do Spark é uma das razões pelas quais o Spark é tão rápido. Ele:

Porém, quando você usa transformações nativas do Glue como ApplyMapping, ResolveChoice ou Map, o Catalyst as enxerga como caixas pretas — funções opacas que ele não consegue otimizar.

Exemplo crítico: Se você aplica um ApplyMapping (renomeação de colunas) antes de um filtro, o Catalyst pode não conseguir empurrar esse filtro para a leitura dos dados do S3. Resultado? Ler terabytes desnecessários.

4.1 A Armadilha Mortal: Partition Pruning

Imagine que você tem dados particionados no S3:

s3://bucket/data/
  ano=2022/
  ano=2023/
  ano=2024/mes=01/
  ano=2024/mes=02/
  ...

No Spark nativo, o seguinte código automaticamente lista apenas as partições relevantes:

# ✅ Spark puro: Partition Pruning automático
df = spark.read.parquet("s3://bucket/data/")
df_filtered = df.filter((col("ano") == 2024) & (col("mes") == 1))

No AWS Glue, se você usar create_dynamic_frame e aplicar .filter() depois, o Glue pode listar TODAS as partições antes de filtrar. Em um Data Lake com milhares de partições, isso pode:

4.2 Solução Obrigatória: push_down_predicate

# ✅ CORRETO: Filtro aplicado no Catálogo antes da listagem
df_dynamic = glueContext.create_dynamic_frame.from_catalog(
    database="db",
    table_name="table",
    push_down_predicate="(ano == '2024' and mes == '01')"
)

# ❌ ERRADO: Filtro aplicado após listagem completa
df_dynamic = glueContext.create_dynamic_frame.from_catalog(
    database="db",
    table_name="table"
)
df_filtered = df_dynamic.filter(lambda x: x["ano"] == "2024")  # Tarde demais!

Ignorar o push_down_predicate é a causa raiz #1 de Jobs Glue que falham por timeout ou OOM no Driver.

5ResolveChoice: A "Mágica" que Custa Caro

Uma das grandes vantagens do Glue é lidar com esquemas em evolução. Se uma coluna price aparece como double em alguns arquivos e como string em outros, o Glue cria um tipo Choice.

A transformação ResolveChoice força uma decisão:

Diferente de um cast no Spark SQL (que é vetorizado), ResolveChoice opera inspecionando cada registro individualmente em runtime. Isso é intensivo em CPU e impede otimizações em lote.

Recomendação: Se seu Data Lake tem esquemas inconsistentes, use o Glue e ResolveChoice para sanitizar os dados uma vez e salvá-los em um formato limpo (Parquet com esquema unificado). Todos os processamentos subsequentes devem ler esses dados limpos com Spark puro.

6Job Bookmarks: Estado com Overhead

Para cargas incrementais, os Job Bookmarks do Glue rastreiam quais arquivos já foram processados. Conveniente, mas:

6.1 Alternativa: Checkpointing Manual Otimizado

# ✅ Alternativa mais controlável e performática
# Salve o último timestamp processado no DynamoDB
last_processed = get_last_processed_timestamp_from_dynamodb()

# Injete como filtro de partição
df = spark.read.parquet("s3://bucket/data/")
df_incremental = df.filter(col("event_time") > last_processed)

# Processe...

# Atualize o estado
save_last_processed_timestamp_to_dynamodb(max_timestamp)

7Custos: Performance Ruim = Fatura Alta

O AWS Glue cobra por DPU-hora (Data Processing Unit). Código ineficiente não só roda devagar — custa literalmente mais dinheiro.

Exemplo Real: Um job que deveria levar 10 minutos, mas leva 30 minutos devido a conversões toDF() excessivas e falta de Partition Pruning, custará 3x mais em DPUs.

Se você processar 100 Jobs por dia, essa diferença pode significar milhares de dólares extras por mês.

7.1 Glue 4.0 e 5.0: Leitores Vetorizados em C++

As versões mais recentes do Glue introduziram leitores otimizados em C++ para Parquet e CSV, que podem superar o Spark open source. Porém, esses leitores são ativados principalmente ao usar glueContext (DynamicFrames).

Isso cria um paradoxo: para obter a leitura mais rápida, use DynamicFrame. Para obter o processamento mais rápido, use DataFrame. A solução? Híbrido estratégico: ler com Glue, converter imediatamente, processar com Spark.

8Anti-Padrões Mortais em PySpark no Glue

8.1 UDFs em Python

UDFs (User Defined Functions) em Python forçam serialização de dados entre a JVM e o processo Python para cada linha. Isso é um matador de performance.

# ❌ ANTI-PADRÃO: UDF Python
from pyspark.sql.functions import udf
from pyspark.sql.types import IntegerType

def my_function(value):
    return value * 2

my_udf = udf(my_function, IntegerType())
df_result = df.withColumn("doubled", my_udf(col("value")))

# ✅ CORRETO: Função nativa do Spark
from pyspark.sql.functions import col
df_result = df.withColumn("doubled", col("value") * 2)

8.2 toPandas(): O Caminho para o OOM

Converter um DataFrame distribuído para Pandas traz todos os dados para a memória do Driver. Com datasets grandes, isso causa Out of Memory invariável.

# ❌ NUNCA FAÇA ISSO com grandes volumes
df_pandas = df_spark.toPandas()  # Traz tudo para o Driver 💥

# ✅ Use Pandas-on-Spark (Koalas) se precisar da API Pandas
import pyspark.pandas as ps
df_pandas_spark = ps.DataFrame(df_spark)  # Continua distribuído

8.3 Loops em DynamicFrames

Iterar sobre um DynamicFrame com loops Python executa a lógica sequencialmente no Driver, anulando o processamento distribuído.

# ❌ ANTI-PADRÃO: Loop que mata o paralelismo
for record in df_dynamic.toDF().collect():  # collect() traz tudo pro Driver!
    process(record)

# ✅ CORRETO: Use transformações distribuídas
df_result = df_spark.map(lambda row: process_row(row))

9Protocolo Operacional para Engenheiros de Dados

Baseado em toda essa análise, o seguinte protocolo maximiza performance e minimiza custos:

1. Ingestão Híbrida: Use DynamicFrames apenas na leitura (Extract) para alavancar leitores C++ do Glue 4.0+, Job Bookmarks e tolerância a esquemas variáveis.

2. Conversão Imediata: Aplique ResolveChoice estritamente se necessário e converta para Spark DataFrame o mais cedo possível.

3. Processamento Nativo: Realize todas transformações pesadas (filtros, joins, agregações) com a API nativa do Spark DataFrame.

4. Pruning Explícito: Sempre use push_down_predicate ao ler do Catálogo para datasets particionados.

5. Evite Serialização Cíclica: Elimine padrões de código que alternam repetidamente entre DynamicFrame e DataFrame.

6. Monitoramento Ativo: Não confie apenas no sucesso do job. Monitore tempo de CPU e uso de memória (DPU-hours) para identificar ineficiências ocultas.

10Quando Usar DynamicFrame vs Spark Puro

Use DynamicFrame quando:

Use Spark DataFrame puro quando:

A Engenharia Está nos Detalhes

O AWS Glue não é um substituto do Apache Spark — é um framework de ingestão e orquestração construído sobre o Spark. A conveniência de suas abstrações (DynamicFrame, ResolveChoice, Job Bookmarks) tem valor real para dados caóticos e esquemas em evolução.

Porém, essa conveniência cobra seu preço em performance e custo operacional. O domínio das nuances entre essas tecnologias é o que separa pipelines de dados frágeis e dispendiosos de arquiteturas resilientes e de alto desempenho.

A estratégia não é escolher um ou outro — é saber quando usar cada um. Use o Glue onde ele brilha (ingestão resiliente), e o Spark onde ele domina (processamento otimizado). E sempre, sempre, meça e monitore.

Porque no final, performance é feature. E custo operacional é responsabilidade.

Quer dominar Engenharia de Dados na AWS?

Esse nível de conhecimento técnico não surge do acaso. Se você quer construir pipelines de dados eficientes, escaláveis e econômicos, precisa de mentoria estruturada e projetos reais.

Conheça a Mentoria Triade
Wellington Domiciano

Wellington Domiciano (Prof. Well)

Engenheiro Especialista em Dados e IA. Apaixonado por otimização de sistemas distribuídos e arquiteturas Cloud de alto desempenho.