Este é o terceiro de uma série de artigos aqui no Blog da DSA sobre um dos melhores frameworks para processamento de dados de forma distribuída, o Apache Spark e sua utilização na nuvem com Databricks. Se está chegando agora, acesse o primeiro artigo da série aqui.

Essa série de artigos foi produzida por um dos alunos da DSA, Engenheiro de Dados, certificado em Spark e Databricks e matriculado em mais de 50 cursos em nosso portal. As informações de contato você encontra ao final do artigo.

Os artigos são de nível técnico e recomendamos alguma familiaridade com ambiente de processamento de Big Data antes de fazer a leitura. Temos uma introdução geral ao Apache Spark no curso gratuito Big Data Fundamentos e material completo no curso Big Data Real-Time Analytics com Python e Spark e Machine Learning e IA em Ambientes Distribuídos.

Boa leitura.


Principais Características do Apache Spark

Antes de discutir as interfaces do Apache Spark, vamos revisar as suas principais caraterísticas.

O Apache Spark é um framework para análise de dados de código aberto usado para cargas de trabalho de Big Data, ou seja, processamento de grandes volumes de dados. O Spark pode trabalhar com dados em lotes, bem como cargas de trabalho de análise e processamento de dados em tempo real.

O framework surgiu em 2009 como um projeto de pesquisa na Universidade da Califórnia, Berkeley. Os pesquisadores estavam procurando uma maneira de acelerar o processamento de jobs nos sistemas Hadoop. O Spark, portanto, é baseado no Hadoop MapReduce e estende o modelo MapReduce para usá-lo eficientemente para mais tipos de computação, o que inclui consultas interativas e processamento de streaming de dados.

O Spark fornece APIs nativas para as linguagens de programação Java, Scala, Python e R. Além disso, inclui várias bibliotecas para apoiar aplicativos de aprendizado de máquina [MLlib], processamento de fluxo [Spark Streaming] e processamento de grafos [GraphX].

O Apache Spark consiste no Spark Core (o engine principal) e um conjunto de bibliotecas.

Spark Core é o coração do Apache Spark e é responsável por fornecer transmissão distribuída de tarefas, agendamento e funcionalidade de I/O. O Spark Core usa o conceito de Dataset Resiliente e Distribuído (RDD – Resilient Distributed Dataset) como seu tipo básico de dados. O RDD foi projetado para que esconda a maior parte da complexidade computacional de seus usuários.

O Spark é inteligente na forma como opera nos dados. Dados e partições são agregados em um cluster, onde podem então ser computados e/ou movidos para uma armazenamento de dados diferente ou executado através de um modelo analítico.

A principal característica do Spark é o processamento na memória do computador, que aumenta a velocidade de processamento, tornando-o até 100 vezes mais rápido do que o Hadoop MapReduce quando processado na memória, e 10 vezes mais rápido em disco, quando se trata de processamento de dados em larga escala. O Spark torna isso possível reduzindo o número de operações de leitura/escrita no disco.

O Apache Spark pode lidar com streaming em tempo real junto com a integração de outras estruturas. O Spark ingere dados em mini-lotes e realiza transformações de RDD nesses mini-lotes de dados.

A capacidade de suportar várias linguagens de programação torna-o dinâmico. Ele permite que você escreva rapidamente aplicativos em Java, Scala, Python e R, trazendo assim uma variedade de linguagens para construir as aplicações.

O Spark suporta consultas SQL, aprendizado de máquina, processamento em stream e processamento de gráficos.

Interfaces Spark

Existem três interfaces principais do Apache Spark que você deve conhecer:

Resilient Distributed Dataset: A primeira abstração do Apache Spark foi o Resilient Distributed Dataset (RDD). É uma interface para uma sequência de objetos de dados que consiste em um ou mais tipos localizados em uma coleção de máquinas (um cluster). Os RDDs podem ser criados de várias maneiras e são a API de “nível mais baixo” disponível. Embora esta seja a estrutura de dados original do Apache Spark, você deve se concentrar na API DataFrame, que é um superconjunto da funcionalidade RDD. A API RDD está disponível nas linguagens Java, Python e Scala.

Dataframe: São semelhantes no conceito ao Dataframe que você pode estar familiarizado utilizando a biblioteca pandas no Python e na linguagem R. A API do Dataframe está disponível nas línguas Java, Python, R e Scala.

Dataset: É uma combinação do Dataframe e RDD. Ele fornece a interface que está disponível em RDDs ao mesmo tempo em que fornece a conveniência do Dataframe. A API Dataset está disponível nas linguagens Java e Scala.

Em muitos cenários, especialmente com as otimizações de desempenho incorporadas em Dataframes e Datasets, não será necessário trabalhar com RDDs. Mas é importante entender RDD porque:

O RDD é a infraestrutura que permite que o Spark seja executado com rapidez e forneça a linhagem de dados. Se você estiver mergulhando em componentes mais avançados do Spark, pode ser necessário usar RDDs.

Operação e Manipulação das Interfaces Spark

Abaixo listo as principais tarefas de operação e manipulação das interfaces Spark.

A) Caching

Podemos armazenar explicitamente dados em cache. Considere por exemplo:

Pedimos explicitamente ao Spark para armazenar em cache o Dataframe resultante de um select(..) após o embaralhamento feito com GroupBy.

Como resultado, nunca chegamos à parte da linhagem que envolve o embaralhamento, muito menos o Estágio #1 (ou seja, pulamos tudo, tornando nosso trabalho mais rápido).

Em vez disso, selecionamos o cache e retomamos a execução a partir daí.

B) Shuffling 

Um Shuffle refere-se a uma operação onde os dados são reparticionados em um Cluster – ou seja, quando os dados precisam ser movidos entre executores.

O join e qualquer operação que termine com bykey acionará um Shuffle. É uma operação cara porque muitos dados podem ser enviados através da rede.

Por exemplo, para agrupar um conjunto de dados por cor, será melhor para nós se:

  • – Todos os vermelhos estiverem em uma partição.
  • – Todos os azuis estiverem em uma segunda partição.
  • – Todos os verdes estiverem em uma terceira partição.
  • – A partir daí, podemos facilmente somar/contar/calcular a média de todos os vermelhos, azuis e verdes.

 

Para realizar a operação de embaralhamento (Shuffle), o Spark precisa converter os dados para UnsafeRow (se ainda não estiver), comumente referido como Tungsten Binary Format.

Tungsten é um novo componente do Spark SQL que fornece operações mais eficientes do Spark trabalhando diretamente no nível do byte. Inclui estruturas de dados especializadas na memória ajustadas para o tipo de operações exigidas pelo Spark com geração de código aprimorada e um protocolo especializado. 

Nota: Algumas ações induzem um embaralhamento. Bons exemplos incluiriam as operações count() e reduce(..).

C) Partitioning (Particionamento)

Uma partição é um pedaço lógico do seu Dataframe. Os dados são divididos em partições para que cada executor possa operar em uma única parte, permitindo a paralelização.

Ele pode ser processado por um único núcleo / thread do Executor .

Por exemplo: Se você tem 4 partições de dados e tem 4 núcleos/threads no executor, você pode processar tudo em paralelo, em uma única passagem.

D) Dataframe Transformations vs. Actions vs. Operations

O Spark permite dois tipos distintos de operações pelo usuário: transformações e ações.

Transformações

Transformações são operações que não serão concluídas no momento em que você escreve e executa o código em uma célula (elas são preguiçosas (LAZY)) – elas só serão executadas depois que você chamar uma ação.

Um exemplo de transformação pode ser: converter um integer em um float ou um filter em um conjunto de valores: ou seja, eles podem ser procrastinados e não precisam ser feitos agora – mas mais tarde, depois de termos uma visão completa da tarefa em questão.

Aqui está uma analogia:

Digamos que você está limpando seu armário, e quer doar roupas que não usa mais e organizar e armazenar o resto por cor antes de armazenar em seu armário.

Se você é ineficiente, você pode classificar todas as roupas por cor (digamos que leva 60 minutos), então a partir daí pegue as que cabem (5 minutos), e então pegue o resto e coloque-a em um grande saco plástico para doação (onde todo esse esforço de triagem que você fez foi para o lixo porque está tudo misturado no mesmo saco plástico agora de qualquer maneira)

Se você for eficiente, primeiro escolheria roupas que se encaixam muito rapidamente (5 minutos), depois classificaria-as em cores (10 minutos), e então pegaria o resto e colocaria em um grande saco plástico para doação (onde não há esforço desperdiçado).

Ações

As ações são comandos que são computados por Spark bem no momento de sua execução (eles estão gananciosos (eager)). Eles consistem em executar todas as transformações anteriores, a fim de obter de volta um resultado real. Uma ação é composta por um ou mais jobs que consistem em tasks que serão executadas pelos slots do executor em paralelo – ou seja, uma etapa – sempre que possível.

Isso significa que certos cálculos podem ser todos realizados de uma só vez (como um map e um filter) em vez de ter que fazer uma operação para todos os dados e, em seguida, a operação seguinte.

As Transformações sempre retornam um Dataframe. Em contraste, as Ações retornam um resultado ou gravam no disco.

DataFrames são imutáveis – ou seja, cada instância de a DataFrame não pode ser alterada depois de instanciada.

Isso significa que outras otimizações são possíveis – como o uso de arquivos aleatórios (veja abaixo)
São classificados como uma transformação ampla (wide) ou estreita (narrow)

Nota: A lista de transformações varia significativamente entre cada linguagem – porque Java e Scala são linguagens estritamente tipadas em comparação com Python e R, que são mal (lossely) tipadas

Operações de Pipelining

Pipelining é a ideia de executar o maior número possível de operações em uma única partição de dados. Uma vez que uma única partição de dados é lida em memória RAM, o Spark combinará o máximo de operações que puder em uma única tarefa. Grandes operações forçam um embaralhado, concluem, um estágio e terminam um pipeline.

Ao evitar toda a rede extra e I/O de disco, o Spark pode facilmente superar os aplicativos tradicionais de MapReduce.

E) Transformações Wide (amplas) vs. Narrow (estreitas)

Independentemente da linguagem, as transformações se dividem em duas grandes categorias: ampla e estreita.

Transformações Estreitas: Os dados necessários para calcular os registros em uma única partição residem no máximo em uma partição do RDD.

Transformações Amplas: Os dados necessários para calcular os registros em uma única partição podem residir em muitas partições do RDD.

Continuaremos no próximo artigo da série.

Artigo Produzido por: Thomaz Antonio Rossito Neto

Databricks Engineer with Apache Spark™ 3.0
MBA em Ciência de Dados com Big Data
MCSE: Data Management and Analytics Microsoft
MCSA: Data Engineering with Azure Microsoft

Contatos:
Site Pessoal: www.thomazrossito.com.br

Referências:

Apache Spark Cluster Overview

Apache Spark Application Overview

JVM Overview