As armadilhas do Spark 101

O Apache Spark é, sem dúvidas, a engine de computação distribuída mais popular da atualidade (via Github e Stack Overflow). Conforme entramos de vez na era do Big Data, a tendência é que cada vez mais pessoas (Data Scientists/Data Engineers/Data Analysts/Developers) se interessem e aprendam Spark. O problema é que muitos dos tutoriais ensinam práticas ruins ou esquecem de falar sobre conceitos importantes que serão problemáticos em situações reais. Vamos listar alguns dos erros mais comuns e desmistificá-los.

Métodos collect() e toPandas()

Presente em praticamente todos os tutoriais básicos de Spark, os métodos collect() e toPandas() são ações básicas usadas para visualizar os dados do seu DataFrame/RDD, o problema é que quase nunca se fala sobre o risco de usar um desses métodos em uma quantidade de dados que não pode ser armazenada em uma máquina. A documentação oficial do Spark alerta:

note: This method should only be used if the resulting array is expected to be small, as all the data is loaded into the driver’s memory.

A mistura destes métodos e muitos dados faz com que a máquina master do cluster morra por falta de memória. Uma ideia melhor é usar o take(n)/show(n) sendo n o número de registros a serem exibidos, o que permite o usuário controlar o output. Se por algum motivo representar uma fração dos dados em um DataFrame pandas for primordial, existe uma solução mais segura:

# Retrieves 10 records of the original DataFrame and turns it into a PandasDF
pandas_df = df.limit(10).toPandas()
# Retrieves 1% of the original DataFrame and turns it into a PandasDF
pandas_df = df.sample(fraction=0.01).toPandas()

Inferência de Schema

A API do Spark nos permite inferir o schema dos dados usando o método read(), o que pode ser útil em algumas situações. Contudo, para que o Spark seja capaz de inferir o schema, ele precisa percorrer todos os dados, ou seja, se sua fonte de dados tem 1 terabyte em arquivos JSON com uma estrutura não trivial, seu read() pode demorar mais desnecessariamente. Um truque para obter o schema dos dados é ler somente um arquivo e extrair o schema inferido para um objeto, colocando-o no código de maneira imutável para que as próximas leituras possam ter um schema definido:

# Reads a file in data source inferring schema
df = spark.read.csv(file)
# Retrieves df schema as a StructType object
print(df.schema)

Após obtermos o schema, podemos passar esse schema na leitura, fazendo com que o Spark não precise ler todos os dados:

#StructType way to read a DataFrame
schema = st.StructType([
st.StructField("foo", st.LongType(), True),
st.StructField("bar", st.StringType(), True)])
df = spark.read.csv(source, schema=schema)

SparkSQL

A API do Spark permite implementar queries SQL registrando uma view temporária com base em um DataFrame. A ideia que pode parecer boa de início e ter seus casos de uso, geralmente causa problemas a médio prazo. Transformar uma query SQL em um plano de execução físico otimizado exige o uso do Catalyst. A performance final do código contra uma implementação de RDD nem sempre é mais lenta mas a complexidade da execução e número de operações de otimização interna aumentam. Por último, a manutenção do código de uma query SQL grande (o que não é difícil dado a verbosidade do SQL) é bem mais complicada por dois motivos: uma alteração pequena pode causar várias linhas de mudança (aumentando a complexidade de um futuro pull request). Outro problema é que quando a query quebra (e acredite, elas quebram) o stack trace geralmente não mostra intuitivamente qual pedaço da query lançou a exceção. Optar pelo uso dos métodos da API de DataFrame exige uma curva de aprendizado maior mas traz benefícios a médio prazo.

Performance PySpark

O ponto mais polêmico do Spark é a performance do Python contra Scala. Uma busca rápida de “spark performance python vs scala” retorna dezenas de benchmarks realizados em diversos cenários onde todos apontam a mesma coisa, Python performa pior que Scala. Isso significa que você deve imediatamente migrar pra Scala? Depende. Se seu time domina Scala, pode ser uma boa opção, senão essa ideia pode não sair como planejado. Dar manutenção em código de uma linguagem que seu time não se sente confortável, além de diminuir a velocidade de desenvolvimento da equipe, limita a capacidade de entender a causa raiz de falhas em produção.

Conclusão

Agora que você já conhece alguns problemas omitidos por muitos tutoriais, chegou a hora de se aprofundar e treinar. Lembre-se que tão importante quanto saber programar Spark é entender o universo da computação distribuída e seus principais componentes. Negligenciar esse conhecimento te trará frustrações no futuro com relação a performance, estabilidade e custo de seus produtos. Para se aprofundar em Spark, nossa dica de referência é Holden Karau. Integrante do PMC do Spark (grupo que norteia o desenvolvimento do produto) e uma das principais committers do repositório, Holden tem uma playlist de mais de sessenta palestras sobre diferentes aspectos do Spark.