Mutação de Transformações para Teste de
Programas Spark
Natal-RN
2020
Mutação de Transformações para Teste de Programas
Spark
Tese de Doutorado apresentada ao Programa de Pós-Graduação em Sistemas e Computa-ção do Departamento de Informática e Ma-temática Aplicada da Universidade Federal do Rio Grande do Norte como requisito par-cial para a obtenção do título de Doutor em Ciência da Computação.
PPgSC – Programa de Pós-Graduação em Sistemas e Computação DIMAp – Departamento de Informática e Matemática Aplicada
CCET – Centro de Ciências Exatas e da Terra UFRN – Universidade Federal do Rio Grande do Norte
Orientador: Martin A. Musicante
Coorientadora: Anamaria Martins Moreira
Coorientadora: Genoveva Vargas-Solar
Natal-RN
2020
Souza Neto, João Batista de.
Mutação de transformações para teste de programas Spark / João Batista de Souza Neto. - 2020.
244f.: il.
Tese (Doutorado) - Universidade Federal do Rio Grande do Norte, Centro de Ciências Exatas e da Terra, Programa de Pós-Graduação em Sistemas e Computação. Natal, 2020.
Orientador: Martin Alejandro Musicante. Coorientadora: Anamaria Martins Moreira. Coorientadora: Genoveva Vargas-Solar.
1. Computação Tese. 2. Big Data Tese. 3. Apache Spark Tese. 4. Teste de mutação Tese. 5. Operadores de mutação -Tese. 6. TRANSMUT-Spark - -Tese. I. Musicante, Martin Alejandro. II. Moreira, Anamaria Martins. III. Vargas-Solar, Genoveva. IV. Título.
RN/UF/CCET CDU 004
Catalogação de Publicação na Fonte. UFRN - Biblioteca Setorial Prof. Ronaldo Xavier de Arruda - CCET
a minha irmã, Carol, e a minha namorada, Silvana, por todo carinho, apoio e incentivo.
Primeiramente, gostaria de agradecer a toda a minha família, em especial meus pais, Junior e Maria, e minha irmã, Carol, por todo apoio e incentivo nos momentos mais importantes de minha vida. Agradeço a minha namorada Silvana por todo o carinho, incentivo e confiança, além de toda a paciência e compreensão com minhas ausências por causa deste trabalho. Obrigado por terem acreditado no meu potencial e terem incentivado a dar o meu melhor!
Agradeço ao meu orientador Martin Musicante por toda a sua dedicação, paciên-cia, confiança e contribuição. Seu suporte e direcionamento foram fundamentais para que esse trabalho pudesse ter sido realizado, e para meu desenvolvimento como pesquisador.
Agradeço a minha coorientadora Anamaria Martins Moreira por todos os en-sinamentos, ajudas e contribuições que foram essenciais para o desenvolvimento deste trabalho. Desde o mestrado, seu apoio e confiança tem sido fundamentais para meu crescimento pessoal e profissional.
Agradeço a minha coorientadora Genoveva Vargas-Solar por toda dedicação e contribuições ao meu trabalho, além de todo o seu suporte durante meus estágios no México e na França. Seu apoio foi fundamental para o desenvolvimento deste trabalho e para que eu pudesse ter tido todas essas grandes experiências. Um agradecimento especial a Guadalupe Solar Quiroz e José Luis Zechinelli pelo acolhimento amigável e todo apoio nos meus dias em Puebla.
Agradeço ao Laboratoire d’Informatique de Grenoble (LIG) e todas as pessoas do grupo HADAS por terem me acolhido durante um ano em Grenoble e por todas as contribuições e experiências proporcionadas.
Agradeço ao Professor Umberto Costa e aos colegas Denis Albuquerque e Roberta Freitas por todas contribuições e colaborações.
Por último, gostaria de agradecer ao Instituto Metrópole Digital (IMD) por todo o suporte técnico, e a Coordenação de Aperfeiçoamento de Pessoal de Nível Superior (CAPES) e Universidade Federal do Rio Grande do Norte (UFRN) pelo suporte financeiro.
from a single-celled organism into the dominant species on the planet.” Professor Charles Xavier
O crescimento do volume de dados gerado nos últimos anos, fenômeno conhecido como Big Data, apresentou uma série de desafios para a sua coleta, armazenamento e, sobre-tudo, processamento porque exigem importantes recursos computacionais e ambientes de execução adaptados. Diferentes sistemas de processamento paralelo e distribuído são uti-lizados para o processamento de Big Data. Alguns sistemas adotam um modelo de fluxo de controle, como o sistema Hadoop que aplica o modelo MapReduce, e outros adotam um modelo de fluxo de dados, como o Apache Spark. A confiabilidade de programas de processamento de grandes volumes de dados se torna importante devido à grande quan-tidade de recursos computacionais necessários para sua execução, fazendo com que seja importante testá-los antes que sejam executados em produção em uma infraestrutura cus-tosa de computação distribuída. Esta tese propõe uma abordagem de teste de mutação para programas que seguem um modelo de fluxo de dados como o Apache Spark. O teste de mutação é uma técnica de teste que se baseia na simulação de defeitos através de modificações no programa para criar versões defeituosas chamadas mutantes. A geração dos mutantes é realizada por operadores de mutação que são capazes de simular defeitos específicos no programa. Mutantes são utilizados no processo de projeto e avaliação de testes de modo a ter um conjunto de testes capaz de identificar os defeitos simulados pelos mutantes. Para aplicar o processo de teste de mutação em programas de proces-samento de Big Data é importante ter conhecimento dos tipos de defeitos que podem ser encontrados nesse contexto para, então, projetar operadores de mutação que possam simulá-los. Dessa forma, realizamos um estudo para caracterizar defeitos e problemas que podem surgir em programas Spark. Baseado nesse estudo, projetamos um conjunto de operadores de mutação para programas que seguem um modelo de fluxo de dados. Esses operadores simulam defeitos no programa através de mudanças no seu fluxo de dados e nas suas operações. Os operadores de mutação foram formalizados com um modelo que propomos para representar programas de processamento de dados baseados em fluxo de dados. Para dar suporte à aplicação dos nossos operadores de mutação, desenvolvemos a ferramenta TRANSMUT-Spark que automatiza as principais etapas do processo de teste de mutação em programas Spark. Realizamos experimentos para avaliar os opera-dores de mutação e ferramenta em termos de custos e efetividade. Os resultados desses experimentos mostraram a viabilidade da aplicação do processo de teste de mutação em programas Spark e sua contribuição no processo de teste com o intuito de desenvolver programas mais confiáveis.
Palavras-chaves: Big Data; Apache Spark; Teste de Mutação; Operadores de Mutação;
The growth in the volume of data generated in the last years, a phenomenon known as Big Data, presented a series of challenges for its collection, storage and, especially, processing because they require important computational resources and adapted execution environments. Different parallel and distributed processing systems are used for Big Data processing. Some systems adopt a control flow model, such as Hadoop, that applies the MapReduce programming style, while others adopt a data flow model, such as Apache Spark. The reliability of large-scale data processing programs becomes important due to the large amount of computational resources required for their execution, making it important to test them before they run in production in an expensive distributed computing infrastructure. This thesis proposes a mutation testing approach for programs that follow a data flow model like Apache Spark. Mutation testing is a testing technique that relies on simulating faults by modifying a program to create faulty versions called mutants. The generation of mutants is carried by mutation operators that are able to simulate specific faults in the program. Mutants are used in the test design and evaluation process in order to have a test set capable of identifying the faults simulated by the mutants. In order to apply the mutation testing process to Big Data processing programs, it is important to be aware of the types of faults that can be found in this context to design mutation operators that can simulate them. Thus, we conducted a study to characterize faults and problems that can appear in Spark programs. Based on this study, we designed a set of mutation operators for programs that follow a data flow model. These operators simulate faults in the program through changes in its data flow and operations. The mutation operators were formalized with a model we propose to represent data processing programs based on data flow. To support the application of our mutation operators, we developed the tool TRANSMUT-Spark that automates the main steps of the mutation testing process in Spark programs. We conducted experiments to evaluate the mutation operators and tool in terms of costs and effectiveness. The results of these experiments showed the feasibility of applying the mutation testing process in Spark programs and their contribution to the testing process in order to develop more reliable programs.
Keywords: Big Data; Apache Spark; Mutation Testing; Mutation Operators; TRANSMUT-Spark.
Figura 1.1 – Metodologia. . . 24 Figura 2.1 – Representação de transformação estreita (narrow) e ampla (wide). . . . 38 Figura 2.2 – Exemplo de programa Spark de contagem de palavras. . . 39 Figura 2.3 – Visão geral do cluster no Apache Spark. . . 40 Figura 2.4 – Processo do Teste de Mutação (caixas em negrito são etapas
automa-tizadas e caixas tracejadas são etapas manuais). . . 50 Figura 3.1 – Exemplo de programa de contagem de palavras em Spark. . . 65 Figura 3.2 – Representação gráfica do fluxo de dados do programa apresentado na
Figura 3.1 . . . 66 Figura 3.3 – Representação gráfica do fluxo de dados de um programa MapReduce
representada com o modelo. . . 84 Figura 4.1 – Representação da diferença entre utilizar ou não variáveis de transmissão. 90 Figura 4.2 – Representação da diferença de um RDD ser pré-particionado ou não
antes de uma operação chave/valor. . . 92 Figura 4.3 – Representação da diferença entre uma junção com dois RDDs
particio-nados com o mesmo particionador e uma com dois RDDs particioparticio-nados com particionadores diferentes. . . 93 Figura 4.4 – Exemplo de agregação utilizando groupByKey. . . . 94 Figura 4.5 – Exemplo de agregação utilizando reduceByKey. . . . 94 Figura 4.6 – Exemplo de agregação que envolve mudança de tipo utilizando
redu-ceByKey. . . . 95 Figura 4.7 – Exemplo de agregação que envolve mudança de tipo utilizando
aggre-gateByKey. . . . 95 Figura 4.8 – Representação da diferença entre repartition e coalesce. . . . 96 Figura 4.9 – Speedups dos experimentos de alocação de recursos - número de
exe-cutores. . . 107 Figura 4.10–Speedups dos experimentos de alocação de recursos - núcleos de CPU
por executor. . . 107 Figura 4.11–Speedups dos experimentos de alocação de recursos - memória RAM
por executor. . . 108 Figura 4.12–Speedups dos experimentos de persistência de dados. . . 110 Figura 4.13–Speedups dos experimentos de transmissão de dados. . . 111 Figura 4.14–Speedups dos experimentos de particionamento de dados relacionados
ao número de partições de RDD. . . 112 Figura 4.15–Speedups dos experimentos de particionamento de dados relacionados
a aplicar uma junção em dois RDDs particionados de forma diferente. . 113
Figura 4.17–Speedups dos experimentos de operações com redistribuição de dados - groupByKey versus reduceByKey. . . 114
Figura 4.18–Speedups dos experimentos de operações com redistribuição de dados - reduceByKey versus aggregateByKey. . . 115
Figura 4.19–Speedups dos experimentos de operações com redistribuição de dados - repartition versus coalesce. . . 116
Figura 4.20–Taxonomia de problemas de desempenho para programas Spark. . . 118
Figura 5.1 – Exemplo de programa de análise de log. . . . 125
Figura 5.2 – Exemplo de programa Spark com agregação e junção entre dois con-juntos de dados. . . 127
Figura 5.3 – Exemplo de programa Spark de análise de mensagens. . . 128
Figura 5.4 – Exemplo de implementação e uso de um acumulador personalizado. . . 130
Figura 5.5 – Taxonomia de Defeitos Funcionais para Apache Spark. . . 132
Figura 5.6 – Taxonomia de Tópicos de Interesse sobre o Apache Spark em postagens no Stack Overflow. . . 137
Figura 5.7 – Quantidade de postagens do Stack Overflow classificadas por cada de-feito da taxonomia de dede-feitos funcionais para Apache Spark. . . 138
Figura 5.8 – Quantidade de postagens do Stack Overflow classificadas por cada grupo de defeito da taxonomia de defeitos funcionais para Apache Spark.139 Figura 6.1 – Representação gráfica do fluxo de dados de um programa contendo transformações unárias. . . 145
Figura 6.2 – Mutante gerado com o operador de mutação UTS. . . 145
Figura 6.3 – Primeiro mutante gerado com o operador de mutação UTR. . . 146
Figura 6.4 – Segundo mutante gerado com o operador de mutação UTR. . . 146
Figura 6.5 – Mutante gerado com o operador de mutação UTD. . . 147
Figura 6.6 – Representação gráfica do fluxo de dados de um programa contendo transformações binárias. . . 148
Figura 6.7 – Mutante gerado com o operador de mutação BTS. . . 148
Figura 6.8 – Primeiro mutante gerado com o operador de mutação BTR. . . 148
Figura 6.9 – Segundo mutante gerado com o operador de mutação BTR. . . 149
Figura 6.10–Representação gráfica do fluxo de dados de um programa. . . 153
Figura 6.11–Mutante gerado com o operador de mutação DTI. . . 153
Figura 6.12–Exemplo de parte de um programa Spark. . . 156
Figura 7.1 – Visão geral do fluxo de trabalho da ferramenta TRANSMUT-Spark. 168 Figura 7.2 – Visão geral da arquitetura do TRANSMUT-Spark. . . 172
Figura 7.3 – Exemplo de programa Spark. . . 174
Figura 7.6 – Parte do relatório HTML gerado pelo TRANSMUT-Spark com
mé-tricas sobre os programas. . . 177
Figura 7.7 – Parte do relatório HTML gerado pelo TRANSMUT-Spark com a representação gráfica do fluxo de dados do programa. . . 177
Figura 7.8 – Parte do relatório HTML gerado pelo TRANSMUT-Spark com in-formações sobre os mutantes gerados. . . 178
Figura 7.9 – Parte do relatório HTML gerado pelo TRANSMUT-Spark com os detalhes e resultados de um mutante específico. . . 178
Figura 7.10–Parte do relatório HTML gerado pelo TRANSMUT-Spark com mé-tricas sobre os operadores de mutação. . . 179
Figura 8.1 – Comparação dos resultados agregados por programa das duas avaliações.195 Figura 8.2 – Comparação dos resultados agregados por operador de mutação das duas avaliações. . . 196
Figura A.1 – Visão geral do processo de execução do modelos MapReduce no Apache Hadoop. . . 223
Figura A.2 – Exemplo de programa de contagem de palavras no Hadoop MapReduce. 224 Figura A.3 – Exemplo de programa de contagem de palavras no DryadLINQ. . . 230
Figura A.4 – Visão geral da execução de um programa DryadLINQ. . . 230
Figura A.5 – Processo de compilação e execução no Nephele/PACTs. . . 232
Figura A.6 – Componentes de um PACT. . . 233
Figura A.7 – Representação dos cinco Contratos de Paralelização de Entrada do Nephele/PACTs. . . 235
Figura A.8 – Exemplo de programa Flink de contagem de palavras. . . 240
Tabela 2.1 – Comparação das interface de programação de sistemas de processa-mento de grandes volumes de dados. . . 33 Tabela 2.2 – Exemplos de operadores de mutação. . . 49 Tabela 3.1 – Descrição das representações do fluxo de dados de um programa de
processamento de grandes volumes de dados. . . 69 Tabela 3.2 – Comparação entre as operações no modelo e as operações nos sistemas
de processamento de grandes volumes de dados estudados. . . 83 Tabela 4.1 – Perfis de configuração do cluster. . . 100 Tabela 4.2 – Programas executados nos experimentos. . . 101 Tabela 4.3 – Correlação entre os programas e os aspectos que impactam no
desem-penho de execução. . . 102 Tabela 4.4 – Resultados de desempenho agregados para cada perfil de configuração. 104 Tabela 4.5 – Resultados de tempo de desempenho para cada programa com a
con-figuração conf1. . . 105 Tabela 4.6 – Resultados de entradas e saídas para cada programa com a configuração
conf1. . . 106 Tabela 6.1 – Relação entre os operadores de mutação e os defeitos da taxonomia de
defeitos funcionais para Apache Spark. . . 144 Tabela 6.2 – Valores de mapeamento para tipos básicos e coleções. . . 150 Tabela 6.3 – Mutantes gerados com os operadores de mutação de fluxo de dados. . . 156 Tabela 6.4 – Mutantes gerados com os operadores de mutação para transformações. 158 Tabela 7.1 – Regras de redução de mutantes aplicadas no módulo MutantReducer.169 Tabela 7.2 – Mutantes gerados com o operador de mutação STR para o programa
da Figura 7.3. . . 174 Tabela 8.1 – Resultados da primeira avaliação agregados por programa. . . 187 Tabela 8.2 – Resultados da primeira avaliação agregados por operador de mutação. . 187 Tabela 8.3 – Resultados da segunda avaliação agregados por programa. . . 192 Tabela 8.4 – Resultados da segunda avaliação agregados por operador de mutação. . 193 Tabela 8.5 – Resultados da segunda avaliação agregados por programa com a
ferra-menta TRANSMUT-Spark utilizando o módulo redutor de mutantes.193 Tabela 8.6 – Resultados da segunda avaliação agregados por operador de mutação
com a ferramenta TRANSMUT-Spark utilizando o módulo redutor de mutantes. . . 194 Tabela 8.7 – Resultados do programa MovieLensExploration agregados por
dor de mutação com a ferramenta TRANSMUT-Spark utilizando o módulo redutor de mutantes. . . 195
1 INTRODUÇÃO . . . 18
1.1 Descrição do Problema e Motivação . . . 19
1.2 Objetivos e Questões de Pesquisa . . . 22
1.3 Metodologia . . . 23
2 FUNDAMENTAÇÃO TEÓRICA E ESTADO DA ARTE . . . 27
2.1 Sistemas de Processamento de Grandes Volumes de Dados . . . 27
2.1.1 Classificação de Sistemas de Processamento de Dados . . . 28
2.1.2 Comparações . . . 30 2.1.3 Apache Spark . . . 34 2.1.3.1 Operações . . . 35 2.1.3.2 Variáveis Compartilhadas . . . 39 2.1.3.3 Cluster Spark . . . 40 2.1.3.4 Execução . . . 41 2.2 Teste de Software . . . 41 2.2.1 Fundamentos . . . 42 2.2.2 Atividades de Teste . . . 45 2.2.3 Critérios de Cobertura . . . 45 2.2.4 Teste de Mutação . . . 46
2.3 Teste de Programas de Processamento de Grandes Volumes de Dados 53 2.3.1 Teste de Programas MapReduce . . . 54
2.3.2 Verificação Estática de Programas MapReduce . . . 55
2.3.3 Teste de Programas de Fluxo de Dados . . . 56
2.4 Considerações Finais . . . 57
3 UM MODELO PARA PROGRAMAS DE PROCESSAMENTO DE DADOS . . . 59
3.1 Princípios Gerais e Fundamentos . . . 59
3.1.1 Redes de Petri . . . 60
3.1.2 Álgebra de Monoides . . . 61
3.2 Modelando Programas de Processamento de Dados . . . 64
3.2.1 Fluxo de Dados . . . 64
3.2.2 Conjunto de Dados e Transformações . . . 69
3.2.2.1 Conjuntos de Dados Distribuídos . . . 69
rentes Sistemas de Processamento de Dados . . . 82
3.4 Considerações Finais . . . 84
4 UMA TAXONOMIA DE PROBLEMAS DE DESEMPENHO PARA O APACHE SPARK . . . 86
4.1 Problemas de Desempenho em Programas Spark . . . 86
4.1.1 Alocação de Recursos no Cluster . . . 87
4.1.2 Persistência de Dados . . . 89
4.1.3 Transmissão de Dados . . . 90
4.1.4 Particionamento de Dados . . . 91
4.1.5 Operações com Redistribuição de Dados . . . 93
4.2 Experimentos . . . 97 4.2.1 Ambiente de Execução . . . 97 4.2.2 Conjuntos de Dados . . . 98 4.2.3 Metodologia . . . 99 4.2.4 Programas . . . 100 4.2.5 Resultados . . . 103 4.3 Discussões . . . 104
4.3.1 Alocação de Recursos no Cluster . . . 105
4.3.2 Persistência de Dados . . . 109
4.3.3 Transmissão de Dados . . . 110
4.3.4 Particionamento de Dados . . . 111
4.3.5 Operações com Redistribuição de Dados . . . 114
4.4 Taxonomia de Problemas de Desempenho para Apache Spark . . . 117
4.5 Considerações Finais . . . 121
5 UMA TAXONOMIA DE DEFEITOS FUNCIONAIS PARA O APA-CHE SPARK . . . 123
5.1 Defeitos Funcionais em Programas Spark . . . 123
5.1.1 Fluxo de Dados . . . 124
5.1.2 Operações . . . 125
5.1.3 Acumuladores . . . 129
5.2 Taxonomia de Defeitos Funcionais para Apache Spark . . . 131
5.3 Uma Análise da Taxonomia de Defeitos Funcionais para Apache Spark em Postagens no Stack Overflow . . . 135
5.4 Discussões . . . 138
CESSAMENTO DE DADOS . . . 142
6.1 Projeto e Classificação dos Operadores de Mutação . . . 142
6.2 Operadores de Mutação de Fluxo de Dados . . . 144
6.3 Operadores de Mutação para Transformações . . . 149
6.4 Exemplos de Aplicação dos Operadores de Mutação . . . 156
6.5 Discussões . . . 160
6.6 Considerações Finais . . . 164
7 TRANSMUT-SPARK: UMA FERRAMENTA PARA TESTE DE MUTAÇÃO DE PROGRAMAS SPARK . . . 166
7.1 Funcionalidades e Fluxo de Trabalho . . . 166
7.2 Detalhes de Implementação . . . 170
7.3 Utilização da Ferramenta . . . 176
7.4 Discussões . . . 179
7.5 Considerações Finais . . . 181
8 AVALIANDO A ABORDAGEM DE MUTAÇÃO DE TRANSFOR-MAÇÕES PARA PROGRAMAS SPARK . . . 182
8.1 Objetivos e Metodologia . . . 182
8.1.1 Programas . . . 184
8.2 Primeira Avaliação . . . 185
8.2.1 Processo . . . 186
8.2.2 Resultados . . . 186
8.2.3 Análise dos Resultados e Discussões . . . 187
8.3 Segunda Avaliação . . . 191
8.3.1 Processo . . . 191
8.3.2 Resultados . . . 192
8.3.3 Análise dos Resultados e Discussões . . . 194
8.4 Ameaças à Validade . . . 201
8.5 Considerações Finais . . . 202
9 CONCLUSÕES E TRABALHOS FUTUROS . . . 203
9.1 Publicações . . . 206
9.2 Trabalhos Futuros . . . 207
REFERÊNCIAS . . . 211
APÊNDICE A – SISTEMAS DE PROCESSAMENTO DE GRAN-DES VOLUMES DE DADOS . . . 222
A.2 Dryad e DryadLINQ . . . 226
A.2.1 Dryad . . . 226
A.2.2 DryadLINQ . . . 227
A.2.3 Execução . . . 230
A.3 Nephele/PACTs e Apache Flink . . . 231
A.3.1 Nephele/PACTs . . . 232
A.3.1.1 PACTs . . . 233
A.3.1.2 Compilação e Execução . . . 236
A.3.2 Apache Flink . . . 237
A.3.2.1 Conceitos . . . 238
A.3.2.2 Transformações . . . 238
A.4 FlumeJava e Apache Beam . . . 240
A.4.1 Conceitos . . . 241
A.4.2 Transformações . . . 241
1 Introdução
A popularização das redes sociais e o crescente uso de dispositivos embarcados, aliados com diferentes fontes da indústria e pesquisas na academia, têm provocado um enorme crescimento no volume de dados que são produzidos todos os dias. A captação e análise destes dados representa uma oportunidade para empresas e organizações interes-sadas em estudar o comportamento das entidades que produzem e consomem esses dados, incluindo o seu uso nos mais diversos fins. O crescimento exponencial do volume de dados, sua produção contínua e em larga escala e sua heterogeneidade levaram ao desenvolvi-mento do conceito de Big Data. Esse termo refere-se a conjuntos de dados que cresceram tanto que se tornou inviável tratá-los utilizando métodos tradicionais de armazenamento e processamento, como bancos de dados relacionais tradicionais que costumavam ser a principal solução da indústria (ALBALA, 2011). Os desafios da área consistem em ter técnicas e ferramentas que sejam capazes de acompanhar a grande variação no volume e velocidade de produção dos dados, considerando também a variedade dos mesmos, uma vez que esses podem vir de diversas fontes e em diferentes formatos, e a sua veracidade, visto que dados podem ter inconsistências. Essas quatro características, aliadas ao possí-vel valor que pode ser obtido com as informações extraídas desse grande volume de dados constituem os chamados 5 V’s do Big Data (MARR, 2015). Isso impulsionou toda uma área de pesquisa na Ciência da Computação que busca propor novas técnicas e ferramentas que consigam atender aos desafios impostos pelos 5 V’s.
Nesse contexto, diferentes modelos de programação paralela e distribuída, assim como sistemas, foram propostos para atender os desafios de processar e analisar grandes volumes de dados. O MapReduce (DEAN; GHEMAWAT, 2004) foi proposto pela Goo-gle como um modelo de programação para processamento paralelo e distribuído em uma arquitetura de cluster (grupo) de computadores que pode ser aplicado no processamento de grandes volumes de dados. Este modelo ganhou muito destaque por ter sido um dos primeiros a prover um modelo simplificado e escalável para o processamento de Big Data, de modo que detalhes complexos inerentes ao ambiente distribuído, como paralelização, distribuição dos dados, tolerância a falhas e balanceamento de carga, podiam ser abs-traídos pelo desenvolvedor (DEAN; GHEMAWAT, 2004). O modelo MapReduce ganhou popularidade com sua implementação de código aberto no sistema Apache Hadoop (HA-DOOP, 2019), que se tornou uma infraestrutura para uma série de sistemas para Big Data.
Apesar das vantagens do MapReduce, este também possui uma série de limita-ções em relação ao seu desempenho de execução e ao seu modelo de programação rígido que não é adequado para uma série de análises e processamentos comuns no contexto
de Big Data (KALAVRI; VLASSOV, 2013). Dessa forma, outros modelos e sistemas foram desenvolvidos com o propósito de resolver essas limitações, entregando modelos de programação baseado em fluxo de dados que são mais flexíveis que o MapReduce e apresentam melhor desempenho de execução. Entre esses sistemas podemos destacar: o Dryad (ISARD et al., 2007) e DryadLINQ (YU et al., 2008), ambos desenvolvidos pela Microsoft; o Nephele/PACTs (WARNEKE; KAO, 2009; BATTRÉ et al., 2010), que deu origem ao Apache Flink (CARBONE et al., 2015); o FlumeJava (CHAMBERS et al., 2010), também desenvolvido pela Google, e a sua implementação de código aberto no Apache Beam (BEAM, 2016); e, por último, o Apache Spark (ZAHARIA et al., 2010). Todos eles oferecem ambientes de programação paralela e distribuída em cluster de com-putadores que ocultam dificuldades técnicas associadas com o ambiente, como tolerância a falhas e distribuição de dados, por exemplo, permitindo que desenvolvedores foquem nos aspectos algorítmicos do processamento de dados.
1.1
Descrição do Problema e Motivação
Os desafios que envolvem o processamento e análise de grandes volumes de dados e a quantidade significativa de recursos que são necessários para a sua viabilidade, como a alocação de computadores para armazenamento e processamento em um cluster, fazem com que problemas em um programa possam gerar grandes prejuízos (GARG; SINGLA; JANGRA, 2016). Isso faz com que a confiabilidade e qualidade de programas de proces-samento de Big Data se torne cada vez mais importante (MEEKER; HONG, 2014; LIU et al., 2016). Por esse motivo, é essencial que um programa de processamento de Big Data seja verificado e validado antes de ser executado em produção, uma vez que falhas e defeitos nessa aplicação podem implicar em grandes prejuízos. Uma importante técnica de verificação e validação e a mais aplicada na indústria é o teste de software.
O processo de testar um software consiste em verificá-lo dinamicamente com a intenção de ver se este se comporta da maneira esperada (ABRAN et al., 2004). A principal finalidade desse processo é procurar por defeitos no software (MYERS et al., 2004), fazendo com que o teste esteja diretamente ligado à qualidade do mesmo. Testar programas de processamento de grandes volumes de dados esbarra não só nos desafios ine-rentes da área, mas também na falta de experiência de desenvolvedores e engenheiros de teste em testar de forma apropriada programas deste tipo (MITTAL, 2013; NACHIYAP-PAN; JUSTUS, 2013). Isso cria uma série de oportunidades e desafios para a pesquisa na área de teste de Big Data, que deve levar em consideração todas as suas etapas: pré-processamento, que envolve o carregamento dos dados a partir de diferentes fontes; processamento, que envolve o tratamento, transformação e análise dos dados; e, por úl-timo, extração e entrega dos dados, que envolve validar o processamento feito nos dados e distribuir os resultados para todas as partes interessadas (GUDIPATI et al., 2013).
Nesse sentido, o teste de programas de processamento de grandes volumes de da-dos é uma área de pesquisa que vem ganhando interesse nos últimos anos. Entretanto, esta é, ainda, uma área de pesquisa nova e que possui um número relativamente pequeno de trabalhos, como apontam (CAMARGO; VERGILIO, 2013b) e (MORÁN; RIVA; TUYA, 2019). Além disso, a maioria dos trabalhos tem se focado no teste de desempenho de execução visto que programas de processamento de grandes volumes de dados demandam muitos recursos computacionais (MORÁN; RIVA; TUYA, 2019). Com relação ao teste funcional, aquele que busca verificar o comportamento funcional do programa, poucos são os trabalhos que aplicam técnicas e critérios sistemáticos de teste (CAMARGO; VERGI-LIO, 2013a) e em sua maioria tem se concentrado em programas que seguem o modelo MapReduce (MORÁN; RIVA; TUYA, 2019).
Trabalhos como (CSALLNER; FEGARAS; LI, 2011) e (LI et al., 2013) utiliza-ram execução simbólica para procurar casos de teste para progutiliza-ramas MapReduce. Outros trabalhos utilizaram técnicas tradicionais de teste para gerar casos de teste para progra-mas MapReduce, como o trabalho apresentado em (MORÁN; RIVA; TUYA, 2015), que utiliza técnicas de teste baseado em grafos, e (LI et al., 2015), que utiliza técnicas de particionamento do espaço de entrada (AMMANN; OFFUTT, 2017). (MATTOS, 2011) utilizou técnicas de busca meta-heurística para gerar dados de teste. Outros trabalhos como (MARYNOWSKI; SANTIN; PIMENTEL, 2015) e (FAGHRI et al., 2012) simula-ram problemas no sistema e ambiente de execução de progsimula-ramas MapReduce para per-mitir que esses sejam verificados em um ambiente propício a falhas. Por fim, trabalhos como (CHEN et al., 2015), (ONO et al., 2011) e (DÖRRE; APEL; LENGAUER, 2011) optaram por utilizar métodos formais e métodos estáticos para verificar programas Ma-pReduce, não envolvendo a execução direta do programa, como ocorre no processo de teste tradicionalmente.
Com relação ao teste de programas que seguem outros modelos e sistemas, como o Apache Spark, Apache Flink, Apache Beam e DryadLINQ, é possível encontrar bibli-otecas que possibilitam a implementação de testes de unidade (KARAU, 2015; OTTO GROUP, 2016). Entretanto, essas não oferecem suporte ao projeto de casos de teste, que é uma parte crítica no processo de teste de software. Com relação a técnicas de teste, os trabalhos (RIESCO; RODRÍGUEZ-HORTALÁ, 2015) e (ESPINOSA et al., 2019) apli-caram o teste baseado em propriedades (CLAESSEN; HUGHES, 2000) em programas no Apache Spark e Apache Flink. Nessa técnica são utilizados dados aleatórios para verificar propriedades nos resultados de um programa. Considerando o limite do nosso conheci-mento, esses são os únicos trabalhos que abordam o teste sistemático de programas que não são focados apenas no modelo MapReduce. Nesse contexto, o limitado número de trabalhos, além do fato de a maioria se concentrar no teste de programas MapReduce, evidencia que o teste de programas de processamento de grandes volumes de dados é uma área de pesquisa em aberto e que ainda necessita de novas técnicas e ferramentas de teste,
principalmente para programas com outros modelos e sistemas, como o Apache Spark, Apache Flink, Apache Beam e DryadLINQ.
Uma importante técnica de teste de software é o teste de mutação (DEMILLO; LIPTON; SAYWARD, 1978), que é uma técnica de teste baseado em defeitos. Seu pro-cesso consiste em criar variantes de um programa, chamados de mutantes, a partir da simulação de defeitos comuns feita através de pequenas mudanças no código do programa, e projetar testes que consigam distinguir os mutantes do programa original (AMMANN; OFFUTT, 2017). Nesta abordagem, um teste deve identificar que o resultado obtido com um mutante é diferente do resultado obtido com o programa original. Quando isso ocorre, é dito que o teste matou o mutante, o que na prática significa que o teste foi capaz de identificar o defeito que era simulado pelo mutante. Os mutantes são criados a partir de pequenas modificações sintáticas e variações no código do programa. Essas mudanças são definidas a partir de regras com padrões de modificação chamadas de operadores de mu-tação. Esses operadores são projetados para simular defeitos e enganos de programação comuns, formando a base do teste de mutação.
O teste de mutação pode ser utilizado como um critério para projetar testes, de modo que casos de teste são projetados para distinguir os mutantes do programa original, ou seja, são projetados para matar mutantes (AMMANN; OFFUTT, 2017). Uma vez que teste de mutação possui a característica de simular defeitos em um software, é espe-rado que testes que atendam o critério também consigam identificar defeitos reais quando estes ocorrerem e forem similares aos defeitos simulados. Diferentes trabalhos comprova-ram a efetividade do teste de mutação ao compará-lo com outros critérios e técnicas de teste (OFFUTT; MA; KWON, 2004). Os trabalhos apresentados em (FRANKL; WEISS; HU, 1997), (OFFUTT et al., 1996) e (WALSH, 1985) mostraram empiricamente que o teste de mutação é mais eficiente para detectar defeitos do que outras técnicas tradici-onais de teste, como a cobertura de declarações e ramificações no código, e técnicas de teste baseadas em grafos (AMMANN; OFFUTT, 2017). Com isso, o teste de mutação se estabeleceu como uma referência na área de teste de software e é frequentemente utili-zado como um padrão para medir a qualidade de conjuntos de teste e para avaliar outros critérios e técnicas de teste (OFFUTT; MA; KWON, 2004).
O teste de mutação tem sido aplicado em diferentes contextos, desde programas em linguagens de programação tradicionais como C (RICHARD et al., 1989) e Java (MA; KWON; OFFUTT, 2002), a contextos mais específicos como no teste de programas orientado a aspectos (FERRARI; MALDONADO; RASHID, 2008), especificações for-mais (HASSINE, 2013), serviços web (LEE; OFFUTT, 2001) e SQL (SHAHRIAR; ZUL-KERNINE, 2008), além de vários outros, como é apontado em (JIA; HARMAN, 2011). No contexto de programas de processamento de grandes volumes de dados, apenas (MAT-TOS, 2011) abordou o teste de mutação. Ainda assim, este o fez de forma indireta, pois o
principal objetivo do trabalho era aplicar algoritmos de busca meta-heurística para gerar dados de teste para programas MapReduce, então o teste de mutação foi aplicado como uma forma de analisar a qualidade da sua técnica e não era o foco principal. Também podemos mencionar os trabalhos apresentados em (SALEH; NAGI, 2014) e (MERKEL; GEORGESON, 2016) que fizeram uso do MapReduce para reduzir os custos do teste de mutação. O teste de mutação muitas vezes demanda a geração e execução de uma grande quantidade de mutantes, fazendo com que a técnica se torne cara devido ao esforço e tempo necessários para executar o seu processo. Os dois trabalhos mencionados fizeram o uso do modelo MapReduce para paralelizar e acelerar a execução do processo. Entretanto, o foco deles continuou sendo aplicar o teste de mutação em programas tradicionais, não tendo o teste de programas MapReduce como alvo.
Assim sendo, é perceptível que o teste de mutação foi muito pouco explorado nesse contexto e que são necessárias mais pesquisas na área de teste para contribuir com o desenvolvimento de programas de processamento de grandes volumes de dados mais confiáveis e menos propensos a erros.
1.2
Objetivos e Questões de Pesquisa
Levando em consideração a lacuna existente na área de teste de programas de processamento de grandes volumes de dados e as vantagens do teste de mutação em relação a outras técnicas de teste, além do fato deste ser considerado um padrão de referência na área de teste de software e de não ter sido bem explorado no contexto apresentado, este trabalho tem como objetivo investigar a hipótese de que é possível e viável aplicar o teste de mutação em programas de processamento de grandes volumes de dados. Para avaliar essa hipótese, precisamos responder as seguintes questões de pesquisa:
Questão de Pesquisa 1: Programas de processamento de grandes volumes de dados em
diferentes sistemas possuem características em comum?
Com esta questão de pesquisa procuramos identificar se programas em diferentes sistemas para processamento de grandes volumes de dados possuem características em comum, de modo que é possível investigar a aplicação do teste de mutação de uma maneira geral.
Questão de Pesquisa 2: É possível simular defeitos que podem surgir em programas de
processamento de grandes volumes de dados?
Com esta questão de pesquisa procuramos identificar quais são os tipos de defeitos que podem surgir em programas de processamento de grandes volumes de dados e se é possível simulá-los, de modo a verificar se é possível ter operadores de mutação que podem simular os defeitos que podem aparecer em programas do tipo.
Questão de Pesquisa 3: É possível automatizar o teste de mutação em programas de
processamento de grandes volumes de dados?
Com esta questão de pesquisa procuramos verificar se é possível automatizar o processo de teste de mutação em programas de processamento de grandes volumes de dados, o que inclui a geração dos mutantes, execução dos testes com os mutantes e a análise dos mutantes.
Questão de Pesquisa 4: É viável aplicar o teste de mutação em programas de
proces-samento de grandes volumes de dados?
Por último, com esta questão procuramos verificar se é viável aplicar o processo de teste de mutação em programas de processamento de grandes volumes de dados e se este contribui com o processo de teste de programas do tipo.
1.3
Metodologia
Para atingir o objetivo e responder as questões de pesquisa, levamos em con-sideração os requisitos necessários para se criar uma abordagem de teste de mutação. Segundo (DELAMARO; OFFUTT; AMMANN, 2014), o teste de mutação deve (i) ser baseado em uma caracterização bem fundamentada de defeitos e (ii) na estrutura de pro-gramas no contexto alvo. Dessa forma, operadores de mutação podem ser projetados para simular defeitos e enganos comuns de programação que são passíveis de acontecer em um cenário real (AMMANN; OFFUTT, 2017), algo que caracteriza o teste de mutação como uma técnica de teste baseado em defeitos. Além disso, ter um maior entendimento sobre os tipos de defeitos que podem aparecer em um software ajuda a mitigar os seus efeitos e a estabelecer estratégias para evitá-los ou minimizá-los. Com base nisso, realizamos as seguintes tarefas para atingir nosso objetivo:
∙ Caracterização e modelagem de programas de processamento de grandes volumes de dados: fizemos uma análise das principais características e modelos de programação dos sistemas para processamento de grandes volumes de dados que foram abordados neste trabalho. Com base nisso, definimos um modelo para representar programas com base nas características em comum que foram identificadas nos sistemas anali-sados, como o seu fluxo de dados e principais operações;
∙ Caracterização de problemas e defeitos no contexto de programas de processamento de grandes volumes de dados: investigamos uma série de defeitos e problemas que podem aparecer no contexto de programas de processamento de grandes volumes de dados. Nessa investigação, analisamos aspectos relacionados com requisitos não-funcionais, que afetam o desempenho de execução de programas, e aspectos
relaci-onados com requisitos funcionais, que influenciam no comportamento e resultados do programa. Dessa forma, caracterizamos e classificamos os problemas e defeitos identificados para formar uma base de conhecimento. Nesse estudo, escolhemos como alvo o Apache Spark por ser um dos sistemas com maior destaque na área, e por apresentar um modelo de programação baseado em fluxo de dados que é similar ao modelo de vários outros sistemas;
∙ Projeto de operadores de mutação para programas de processamento de grandes vo-lumes de dados: com base nos defeitos que foram identificados e no modelo de-senvolvido, projetamos um conjunto de operadores de mutação para programas no contexto, formando a base da nossa abordagem de teste de mutação;
∙ Automação do processo do teste de mutação de programas de processamento de gran-des volumes de dados: uma vez que o teste de mutação é dependente de automação para ser viável, desenvolvemos uma ferramenta que automatiza o processo de teste de mutação em programas Spark aplicando os operadores de mutação que foram projetados;
∙ Avaliação dos operadores de mutação e ferramenta para teste de mutação de pro-gramas de processamento de grandes volumes de dados: por último, aplicamos os operadores de mutação e ferramenta desenvolvidos em experimentos para avaliar os custos e dificuldades de sua aplicação, assim como avaliar sua qualidade e contri-buição para o teste de programas de processamento de grandes volumes de dados.
A Figura 1.1 apresenta uma síntese da metodologia aplicada neste trabalho. Cada etapa principal (representadas através de setas em azul) resultou em alguma contribuição ou contribuiu para atingirmos os objetivos deste trabalho. Os resultados de cada etapa (representados através de retângulos em cinza) estão refletidos nos capítulos desta tese.
Resumo das Contribuições
As principais contribuições desta tese são:
∙ Modelo para Programas de Processamento de Dados: propomos um modelo para representar programas de processamento de grandes volumes de dados baseados em fluxo de dados. O modelo foi definido utilizando os formalismos de Redes de Petri e Álgebra de Monoides;
∙ Taxonomia de Problemas de Desempenho para o Apache Spark: uma taxonomia que agrupa e caracteriza problemas que afetam o desempenho de execução de programas Spark. Demonstramos o impacto desses problemas no desempenho de programas Spark através de experimentos em um cluster de computadores;
∙ Taxonomia de Defeitos Funcionais para o Apache Spark: uma taxonomia que agrupa e caracteriza uma série de defeitos que podem emergir em programas Spark, afetando seu comportamento e resultados. Fizemos uma análise para identificar o relato desses defeitos em postagens no Stack Overflow;
∙ Operadores de Mutação para Programas de Processamento de Dados: projetamos um conjunto de 15 operadores de mutação para a aplicação do teste de mutação em programas de processamento de grandes volumes de dados. Os operadores de mutação foram baseados nos defeitos da taxonomia de defeitos funcionais para o Apache Spark, e foram formalizados com o modelo proposto neste trabalho;
∙ Ferramenta TRANSMUT-Spark: uma ferramenta que dá suporte para a aplica-ção do processo de teste de mutaaplica-ção em programas Spark. A ferramenta automatiza as principais etapas do processo, o que inclui a geração dos mutantes, aplicando os operadores de mutação que foram propostos neste trabalho, a execução dos testes e a análise dos resultados, gerando métricas e relatórios do processo;
∙ Avaliação dos Operadores de Mutação e da Ferramenta TRANSMUT-Spark: re-alizamos dois estudos experimentais para avaliar os operadores de mutação e fer-ramenta desenvolvidos neste trabalho. Os resultados desses estudos mostraram a viabilidade da nossa abordagem e contribuição para o teste de programas Spark.
Organização do Trabalho
O restante desta tese está organizado da seguinte forma:
Capítulo 2 – Fundamentação Teórica e Estado da Arte: este capítulo faz uma
este capítulo faz uma introdução a sistemas de processamento de grandes volumes de dados, apresentando alguns dos principais sistemas e fazendo uma comparação dos seus modelos de programação, além de apresentar em mais detalhes o sistema Apache Spark. Além disso, o capítulo apresenta os principais conceitos sobre teste de software e teste de mutação. Por último, este capítulo também apresenta o estado da arte do teste de programas de processamento de Big Data.
Capítulo 3 – Um Modelo para Programas de Processamento de Dados: este
capítulo apresenta um modelo para programas de processamento de grandes volumes de dados desenvolvido a partir das características em comum dos sistemas estudados.
Capítulo 4 – Uma Taxonomia de Problemas de Desempenho para o Apache Spark: este capítulo apresenta a taxonomia de problemas de desempenho para programas
Spark desenvolvida a partir de um estudo sobre defeitos e problemas no contexto do Apache Spark. Este capítulo também apresenta os resultados de um experimento que corrobora a taxonomia.
Capítulo 5 – Uma Taxonomia de Defeitos Funcionais para o Apache Spark:
este capítulo apresenta a taxonomia de defeitos funcionais para Apache Spark. Os defeitos apresentados na taxonomia são ilustrados através de exemplos. Além disso, este capítulo também apresenta os resultados de uma análise dos defeitos descritos na taxonomia em postagens no Stack Oveflow.
Capítulo 6 – Operadores de Mutação para Programas de Processamento de Dados: este capítulo apresenta os operadores de mutação para programas de
proces-samento de grandes volumes de dados que foram projetados a partir da taxonomia de defeitos funcionais para o Apache Spark.
Capítulo 7 – TRANSMUT-Spark: Uma Ferramenta para Teste de Mutação de Programas Spark: este capítulo apresenta a ferramenta TRANSMUT-Spark que
foi desenvolvida para automatizar o processo do teste de mutação em programas Spark com a aplicação dos operadores de mutação propostos neste trabalho.
Capítulo 8 – Avaliando a Abordagem de Mutação de Transformações para Programas Spark: este capítulo apresenta os experimentos de avaliação dos operadores
de mutação propostos e da ferramenta desenvolvida.
Capítulo 9 – Conclusões e Trabalhos Futuros: este capítulo apresenta as
conside-rações finais sobre esta tese de doutorado, resumindo as suas principais contribuições e apresentando os trabalhos futuros.
Apêndice A – Sistemas de Processamento de Grandes Volumes de Dados:
este apêndice apresenta os sistemas de processamento de grandes volumes de dados que não foram detalhados no Capítulo 2.
2 Fundamentação Teórica e Estado da Arte
Este capítulo apresenta uma revisão literária dos principais assuntos que funda-mentam os trabalhos desenvolvidos nesta tese. O capítulo é iniciado com uma introdução a sistemas de processamento paralelo e distribuídos que são utilizados no contexto de pro-cessamento de grandes volumes de dados na Seção 2.1. Fazemos a apresentação de alguns dos principais sistemas utilizados no contexto e discutimos suas semelhanças. Em se-guida, apresentamos na Seção 2.2 os principais conceitos relacionados ao teste de software e ao teste de mutação. Na Seção 2.3, fazemos uma correlação entre os dois assuntos ao apresentar o estado da arte do teste de programas de processamento de grandes volumes de dados. Por último, a Seção 2.4 conclui o capítulo apresentando considerações finais sobre o que foi apresentado e fazendo um direcionamento para o que vai ser apresentado nos capítulos seguintes.
2.1
Sistemas de Processamento de Grandes Volumes de Dados
O crescimento na geração de dados nos últimos anos, ocasionado, principalmente, pelo uso crescente de redes sociais e dispositivos embarcados, impulsionou toda uma área de pesquisa para lidar com os desafios de armazenar e processar esse grande volume de dados (Big Data) (ALBALA, 2011). Métodos tradicionais de armazenamento e proces-samento de dados, como sistemas de gerenciamento de bancos de dados tradicionais, não eram escaláveis o suficiente para lidar com essa grande quantidade de dados, o que fez com que novas soluções tivessem que ser desenvolvidas pela academia e indústria (POLATO et al., 2014). Nesse contexto, diversos sistemas para armazenamento e processamento de grandes volumes de dados vem sendo propostos ao longo dos anos (ZHANG et al., 2016; BAJABER et al., 2016).
Esta seção faz uma introdução a sistemas de processamento paralelo e distribuído em cluster (grupos) de computadores que são utilizados para o processamento de grandes volumes de dados (Big Data). Nosso objetivo é apresentar as principais características desses sistemas e discutir suas semelhanças e diferenças em relação ao modelo de progra-mação, fluxo de dados, representação dos dados e interface de programação. Primeiro, vamos apresentar uma classificação para sistemas de processamento de grandes volumes de dados. Em seguida, comparamos os modelos e interface de programação de diferentes sistemas. Por último, apresentamos em detalhes o sistema Apache Spark (ZAHARIA et al., 2010), que foi o sistema explorado no nosso trabalho.
2.1.1
Classificação de Sistemas de Processamento de Dados
Em (ZHANG et al., 2016), sistemas para processamento de grandes volumes de dados são classificados de acordo com o tipo de entrada de processamento que é feito: processamento em lotes (batch processing), fluxo contínuo de dados (stream processing), grafos (graph processing) e aprendizagem de máquina (machine learning). Uma classifi-cação semelhante é apresentada em (BAJABER et al., 2016), que classifica os sistemas de processamento de Big Data como sistemas de propósito geral, que de maneira geral processam dados em lotes (batch processing), sistemas SQL, sistemas de processamento de grafos e sistemas de processamento de fluxo contínuo de dados.
Cada tipo de sistema tem que lidar com suas particularidades de como os dados são representados e processados. Por exemplo, sistemas SQL precisam lidar com dados estruturados de forma distribuída, enquanto que sistemas de processamento de fluxo con-tínuo de dados precisam lidar com o recebimento e processamento de dados em tempo real. Isso reflete na forma em que programas são desenvolvidos uma vez que o progra-mador também precisa lidar com essas particularidades, assim como na forma em que eles são validados e verificados, já que diferentes tipos de enganos podem ser cometidos durante o desenvolvimento do programa dependendo do tipo do sistema. Por isso, cada tipo de sistema de processamento de grandes volumes de dados requer um estudo especí-fico. Neste trabalho, abordamos sistemas de propósito geral para processamento de dados em lotes. No processamento em lotes, os dados são coletados, distribuídos e processados em grupos (ZHANG et al., 2016). Entre os principais sistemas desse tipo, podemos citar o Apache Hadoop (HADOOP, 2019), Dryad (ISARD et al., 2007), Apache Flink (CAR-BONE et al., 2015), Apache Beam (BEAM, 2016) e o Apache Spark (ZAHARIA et al., 2010).
O Apache Hadoop é um sistema de código aberto para armazenamento e pro-cessamento de dados distribuídos que implementa o modelo MapReduce (DEAN; GHE-MAWAT, 2004). Nesse modelo, que foi proposto pela Google, o processamento de dados é dividido em duas etapas principais: mapeamento (Map) e redução (Reduce). Essas etapas são executadas de forma paralela e distribuída no cluster. Para cada uma, o desenvol-vedor escreve funções adotando um modelo tradicional de fluxo de controle que recebem pares chave/valor como entrada e produzem pares chave/valor como saída. Na etapa de mapeamento, cada elemento do conjunto de dados é processado por uma função que pode produzir zero ou mais pares do tipo chave/valor como saída. Então, esses pares passam por uma etapa de redistribuição (chamada de data shuffling) para que dados que possuem uma mesma chave sejam agrupados em um mesmo nó no cluster. Na etapa de redução, esses pares chave/valor agrupados pela chave são processados através de uma função que produz zero ou mais pares chave/valor como saída.
programa-ção simples e escalável para o processamento paralelo e distribuído de dados. O sistema Hadoop também contribuiu para a sua popularização ao fornecer uma infraestrutura com-pleta para o armazenamento, através do seus sistemas de arquivos distribuídos Hadoop Distributed File System (HDFS) (SHVACHKO et al., 2010), e processamento de grandes volumes de dados. Com o Hadoop, detalhes complexos do ambiente distribuído, como pa-ralelização, distribuição dos dados, tolerância a falhas e balanceamento de carga, podem ser abstraídos pelo desenvolvedor, fazendo com que esse possa se concentrar na lógica do programa. Isso fez com que o Hadoop se tornasse um dos sistemas mais utilizados pela indústria, se tornando uma infraestrutura para vários outros projetos no contexto de Big Data.
Apesar das vantagens, o sistema Hadoop e o modelo MapReduce possuem algu-mas limitações. Segundo (KALAVRI; VLASSOV, 2013), prograalgu-mas MapReduce sofrem com problemas de desempenho devido ao tempo gasto pelo sistema com o gerenciamento de cada etapa de processamento e ao fato de que cada etapa do modelo demanda lei-tura e escrita de dados em disco, o que deixa o processamento mais lento. Além disso, o modelo simplificado do MapReduce se mostra limitado para uma série de aplicações, como operações de junção ou algoritmos de aprendizagem de máquina, por exemplo, que não são facilmente expressadas com apenas as duas operações que o MapReduce ofe-rece. Isso faz com que essas aplicações tenham que ser implementadas através de vários programas MapReduce para que o seu comportamento possa ser expressado, tornando seu desenvolvimento mais complexo. Esses problemas impulsionaram uma série de tra-balhos que buscaram abordar as limitações do modelo MapReduce e sistema Hadoop, dando origem a outros sistemas como o Dryad (ISARD et al., 2007) e DryadLINQ (YU et al., 2008), Nephele/PACTs (BATTRÉ et al., 2010) e o Apache Flink (CARBONE et al., 2015), FlumeJava (CHAMBERS et al., 2010) e o Apache Beam (BEAM, 2016), e o Apache Spark (ZAHARIA et al., 2010).
O Dryad (ISARD et al., 2007) é um sistema e modelo para programação paralela e distribuída que foi proposto pela Microsoft. O Dryad ofereceu um modelo de progra-mação mais flexível ao representar um programa através de um Grafo Acíclico Orientado (Directed Acyclic Graph ou DAG, em inglês), em que os vértices são operações de proces-samento e as arestas são canais de comunicação por onde os dados são transferidos. Com esse modelo, um programa não fica limitado a apenas duas operações como no MapRe-duce. As operações de um programa no Dryad são implementadas de forma similar ao MapReduce, de modo que cada operação é implementada através de uma função que adota um fluxo de controle. O Dryad foi expandido através do DryadLINQ (YU et al., 2008), uma interface de alto nível que introduziu uma abstração para representar conjuntos de dados distribuídos (DryadTable) e ofereceu um conjunto abrangente de operações.
de dados em larga escala que fornece Contratos de Paralelização (PACTs) como mo-delo de programação e estes são automaticamente otimizados para serem executados no Nephele (WARNEKE; KAO, 2009), um motor de execução de sistemas distribuídos em nuvem. O Nephele adota um modelo bastante similar ao Dryad, em que um programa é representado como um DAG e seus vértices representam operações de processamento. O modelo PACTs pode ser visto como uma generalização do modelo MapReduce, uma vez que esse também oferece as operações Map e Reduce, além de outras. Essas operações são definidas através de contratos e sua aplicação não é limitada a apenas duas operações como no modelo MapReduce. O Nephele/PACTs deu origem ao Apache Flink (CARBONE et al., 2015), um sistema para processamento de dados distribuídos em lotes (batch proces-sing) e em tempo real (stream procesproces-sing). Esse sistema também fornece uma interface de alto nível para o desenvolvimento de programas de fluxo de dados, possuindo uma abstração para conjuntos de dados distribuídos (DataSet) e fornecendo várias operações para processar esses conjuntos.
O FlumeJava (CHAMBERS et al., 2010) é um sistema proposto pela Google que oferece uma interface de alto nível para o desenvolvimento de aplicações de processamento em paralelo de dados. O FlumeJava foi criado como uma alternativa ao MapReduce de modo a fornecer um modelo de programação mais flexível. O FlumeJava é centrado no conceito de coleções paralelas, que oferecem uma abstração para representar conjuntos de dados distribuídos, permitindo que detalhes de baixo nível, como a distribuição fí-sica dos dados, particionamento e formatação, possam ser abstraídos. Essas coleções são processadas através de operações paralelas que podem ser compostas para criar opera-ções mais complexas e desenvolver programas que requerem mais operaopera-ções. O Apache Beam (BEAM, 2016) é uma implementação de código aberto do FlumeJava. Nele, foram criadas as classes PCollection, para representar as coleções paralelas, e PTransform, para representar as operações paralelas.
O Apache Spark (ZAHARIA et al., 2010) é um sistema de propósito geral para processamento de dados em memória. O Spark é centrado no conceito de RDDs (Resilient Distributed Datasets), que são conjuntos de dados distribuídos que podem ser processados em paralelo no cluster. Programas Spark são representados através de um DAG que define o fluxo de dados do programa, onde RDDs são processados através da aplicação de várias operações. O Spark oferece dois tipos de operações, as transformações, que processam os dados em um RDD e geram um novo RDD como saída, e as ações, que salvam o conteúdo do RDD ou geram um resultado diferente de um RDD.
2.1.2
Comparações
Os modelos e sistemas para processamento de grandes volumes de dados apre-sentados possuem uma série de diferenças e semelhanças. Essas podem estar na forma
em que os sistemas lidam com a execução dos programas, assim como nos seus mode-los de programação. Uma vez que nosso trabalho aborda a validação e verificação de programas de processamento de Big Data com foco no seu comportamento, não vamos comparar questões sobre a arquitetura, otimizações e execução que têm impacto direto no desempenho de cada sistema. Comparações desse tipo podem ser encontradas em diver-sos trabalhos, como (VEIGA et al., 2016), (MARCU et al., 2016), (HAZARIKA; RAM; JAIN, 2017) e (GARCÍA-GIL et al., 2017). Nessa seção, vamos comparar os modelos de programação de cada sistema, considerando três aspectos principais: fluxo de dados, abstração de dados e interface de programação.
O MapReduce que é implementado pelo Apache Hadoop apresenta o modelo mais simples de todos ao sintetizar o programa em duas operações fixas de Map e Reduce. Como dito anteriormente, esse modelo se apresenta muito limitado para representar cargas de trabalho mais complexas que podem exigir uma quantidade maior de operações que não podem ser expressas em um único programa MapReduce. Dessa forma, todos os outros sistemas apresentados neste trabalho possuem modelos mais flexíveis que não limitam a quantidade de operações em uma aplicação.
No Dryad, programas são representados como DAGs em que cada vértice exe-cuta uma função definida pelo desenvolvedor. Cada vértice pode receber um número arbitrário de entradas e produzir um número arbitrário de saídas. Essa característica faz com que o Dryad tenha o modelo mais flexível de todos. Apesar disso, esse modelo aumenta a complexidade do desenvolvimento porque exige que todo o fluxo da aplicação seja definido de maneira explícita pelo desenvolvedor. Para simplificar o desenvolvimento nesta plataforma, o DryadLINQ adiciona uma camada de abstração sobre o Dryad. Nele, um conjunto de dados distribuídos é abstraído como uma única coleção (DryadTable) que pode ser manipulada através de uma grande quantidade de operações de alto nível. No DryadLINQ, um programa é definido através de uma sequência de operações sobre coleções que tem origem em alguma fonte de dados, como algum sistema de arquivos dis-tribuídos ou banco de dados SQL. Este programa é, então, compilado de maneira eficiente para um DAG do Dryad, fazendo com que o desenvolvedor não tenha que definir o fluxo manualmente.
O sistema Nephele opera de forma semelhante ao Dryad em termos de modelo de programação, de forma que seus programas também são representados como DAGs e seus vértices são funções definidas pelo usuário. Os PACTs adicionam uma camada de abstração sobre o Nephele ao expandir o fluxo de dados e operações do modelo MapRe-duce. Um PACT é definido por um contrato de entrada, que define o tipo de operação que será realizada ao indicar como os dados de entrada são representados, uma função definida pelo desenvolvedor que processa esses dados, e um contrato opcional de saída, que define propriedades sobre os resultados da função que podem ser utilizados no
pro-cesso de otimização. Um programa é definido como uma sequência de PACTs que operam sobre conjuntos de dados do tipo chave/valor. Um programa PACTs é, então, compilado para um DAG otimizado para ser executado no sistema Nephele. O Nephele/PACTs, posteriormente, evoluiu para o Apache Flink, que incorporou internamente o sistema de execução e modelo do Nephele/PACTs e adicionou uma nova camada de abstração para o processamento de dados em lotes. Nessa nova abstração, conjuntos de dados distri-buídos são representados através da classe DataSet e programas são definidos através da aplicação de várias operações de transformação em conjuntos de dados. Assim como no Nephele/PACTs, esse programa é representado como um DAG e otimizado para ser processado em paralelo.
O FlumeJava foi proposto como uma interface de alto nível que simplificava o desenvolvimento de pipelines (sequências de operações) no modelo MapReduce. Este deu origem ao Apache Beam, que expandiu os conceitos do FlumeJava para uma inter-face unificada que permite o desenvolvimento de pipelines em diferentes sistemas para processamento de grandes volumes de dados. No Apache Beam, um conjunto de dados distribuídos é abstraído como uma única coleção (PCollection). Essa coleção é proces-sada através de operações que transformam uma coleção em outra (PTransform). Um programa é definido a partir de uma coleção de dados iniciais gerados a partir de alguma fonte de dados, uma sequência de transformações sobre essas coleções e uma operação final que salva os resultados do processamento em alguma fonte externa. O plano de execução de um programa no Apache Beam forma um DAG que é definido a partir das dependência entre transformações que precisam ser executadas para computar uma coleção.
O Apache Spark tem como principal abstração o RDD. Este representa um con-junto de dados particionados que podem ser processados em paralelo. RDDs são proces-sados através de dois tipos de operações: transformações e ações. Transformações são operações que geram novos RDDs a partir de outros. Já ações são operações que geram algum resultado diferente de um RDD, como resultar em algum valor agregado ou salvar o conteúdo do RDD em alguma fonte externa. Um programa Spark é definido a partir de um conjunto de RDDs iniciais, que são criados a partir de alguma fonte de dados, uma sequência de transformações em RDDs e uma ação que finaliza o processamento. Essa sequência de operações é representada como um DAG que forma um plano de execução baseado na dependência entre transformações.
Com exceção do modelo MapReduce, todos os modelos apresentados têm seus programas representados através de DAGs. De maneira geral, os vértices representam operações que fazem algum tipo de processamento no conjunto de dados. Os resultados do processamento de um vértice são passados para os seguintes até chegar ao último que finaliza o processamento. A forma em que os dados são transportados entre os vérti-ces (operações) pode variar de acordo com o sistema. No Dryad e Nephele/PACTs, os
dados são transportados através de canais de comunicação, que são formas de armazena-mento intermediário para o qual um vértice lê os dados de entrada e escreve os dados de saída. Já o DryadLINQ, Apache Flink, Apache Beam e Apache Spark, optaram por criar uma abstração para conjuntos de dados distribuídos e representar um programa através de operações que vão fazendo transformações nesses conjuntos de dados. Dessa forma, é possível dizer que o DryadTable (DryadLINQ), DataSet (Apache Flink), PCollection (Apache Beam) e RDD (Apache Spark) possuem conceitos análogos.
Com relação à interface de programação, os sistemas Dryad e o Nephele ofere-cem um modelo flexível ao permitir que operações possam ser definidas sem nenhuma interface fixa. Dessa forma, operações definidas pelo desenvolvedor podem receber um número arbitrário de entradas e produzir um número arbitrário de saídas. Além disso, a comunicação entre operações deve ser definida de maneira explícita, o que pode dificultar a programação. Os outros sistemas optaram por fornecer interfaces de alto nível onde as operações possuem um tipo de entrada, processamento e saída bem definidos. Esse tipo de interface simplifica o desenvolvimento porque permite que o desenvolvedor se foque em operações mais específicas e não exige que ele administre detalhes de baixo nível, como adequar o tipo de saída de uma operação com a entrada de outra.
Ao analisar as operações disponíveis em cada sistema, é possível ver que existem várias semelhanças em relação aos tipos de operações. A Tabela 2.1 apresenta uma com-paração entre os tipos de operações disponíveis nos sistemas. Na tabela, as operações são classificadas como: Mapeamento, que representa operações que mapeiam um valor para outros a partir de alguma função de transformação; Filtragem, que representa operações que removem valores de uma coleção com base em algum predicado; Agrupamento, que representa operações que agrupam valores com base em uma chave; Agregação, que repre-senta operações que sintetizam um grupo de valores em um único valor; Conjuntos, que representa operações que são inspiradas nas operações de conjuntos matemáticos; Junção, que representa operações que fazem uma junção relacional entre dois conjuntos de dados; e Ordenação, que representa operações que ordenam os valores do conjunto de dados. Tabela 2.1 – Comparação das interface de programação de sistemas de processamento de
grandes volumes de dados.
MapReduce DryadLINQ Nephele/PACTs Apache Flink Apache Beam Apache Spark Mapeamento Map Select,
Select-Many
Map map, flatMap ParDo map, flatMap
Filtragem ∼ Where ∼ filter ∼ filter
Agrupamento ∼ GroupBy ∼ groupBy GroupByKey groupByKey
Agregação Reduce Aggregate Reduce, CoGroup reduce, aggregate Combine reduce, re-duceByKey, aggregateByKey Conjuntos ∼ Union, Intersect,
Except, Distinct
Cross union, distinct Flatten union, intersec-tion, subtract, distinct
Junção ∼ Join Match join CoGroupByKey join
Ordenação ∼ OrderBy ∼ sortPartition ∼ sortByKey
equivalen-tes às operações Map (Mapeamento) e Reduce (Agregação) do modelo MapReduce, de modo que se torna fácil escrever um programa no estilo MapReduce em qualquer um dos outros sistemas. Os sistemas que fornecem a maior quantidade de operações são o Drya-dLINQ, Apache Flink e o Apache Spark, possuindo operações em todos os tipos seguindo a classificação utilizada na tabela. Já o Nephele/PACTs e Apache Beam possuem uma quantidade menor de operações, mas que podem ser compostas para criar operações mais complexas que não são definidas nativamente.
A partir dessas comparações podemos destacar que os sistemas apresentados pos-suem uma série de semelhanças nos seus modelos de programação. De maneira geral, os programas são representados como DAGs em que seus vértices representam operações sobre dados; os conjuntos de dados distribuídos são abstraídos e representados de forma simplificada como um único objeto, fazendo com que o programador não tenha que li-dar com questões de baixo nível, como a paralelização, por exemplo; e suas interfaces de programação oferecem uma quantidade abrangente de operações sobre dados que permi-tem diferentes tipos de processamento. Essas semelhanças reflepermi-tem na forma em que os programas são definidos, permitindo que programas que possuem um mesmo propósito possam ser desenvolvidos nesses sistemas utilizando uma mesma lógica e aplicando os mesmos tipos de operações.
Neste trabalho, nossos estudos se concentraram no Apache Spark por esse ser, atualmente, um dos principais e mais populares sistemas utilizados no processamento de grandes volumes de dados (ZAHARIA et al., 2015). Mesmo que estejamos limitando nosso estudo a apenas um sistema, suas semelhanças com os outros sistemas, como a representação de programas em DAGs e um conjunto similar de operações sobre dados, por exemplo, podem permitir que técnicas definidas para programas Spark possam ser facilmente adaptadas para programas nos outros sistemas. A seguir, faremos uma apre-sentação mais detalhada do Apache Spark para apresentar conceitos, operações e recursos que são importantes para o entendimento dos capítulos seguintes. Uma apresentação mais detalhada dos outros sistemas abordados nesta seção pode ser encontrada no Apêndice A.
2.1.3
Apache Spark
O Apache Spark é um sistema de propósito geral para processamento de dados em larga escala em cluster de computadores (SPARK, 2019). Spark foi criado como uma alternativa ao modelo MapReduce ao oferecer processamento em memória (ZAHARIA et al., 2010). Este é mais adequado para aplicações iterativas, como algoritmos de apren-dizagem de máquina, e análises interativas, como consultas exploratórias em conjuntos de dados, que são limitações no modelo MapReduce uma vez que esse escreve os resulta-dos de cada etapa em disco. Spark permite que programas de processamento de grandes volumes de dados sejam escritos nas linguagens de programação Scala, Java, Python e