4. Sumário
● Introdução ao Apache Spark
● Nova API Structured Streaming
● Caso de uso de uma aplicação de
processamento em "tempo real"
● Lições aprendidas
6. Introdução ao Apache Spark
Apache Spark™ is a fast and general engine
for large-scale data processing.
7. Em que o Apache Spark é usado?
● Processos de ETL
● Consultas Interativas (SQL)
● Análise de dados avançada
● Machine Learning/Deep Learning
● Streaming sobre grandes datasets
11. Structured Stream
API de alto nível para desenvolvimento
de aplicações contínuas de
processamento de stream de dados,
integrando com storages de forma
consistente e tolerante a falhas.
12. Apache Spark Structured Stream
• Nova API de alto nível
• Junção de dados contínua com conteúdo estático
• Integrações com diversas fontes de dados (File, Kafka)
• Tolerante a falhas (checkpoints)
• Tratamento de eventos desordenados (watermark)
13. Processamento Batch com DataFrames
input = spark.read
.format("csv")
.load("source-path")
result = input
.select("device", "signal")
.where("signal > 15")
result.write
.format("parquet")
.save("dest-path")
Leitura de um arquivo CSV
Aplicação de filtros e seleção
Escrita em formato parquet
14. Processamento Streaming com DataFrames
input = spark.readStream
.format("csv")
.load("source-path")
result = input
.select("device", "signal")
.where("signal > 15")
result.writeStream
.format("parquet")
.start("dest-path")
Leitura de um arquivo CSV Stream
Aplicação de filtros e seleção
Escrita em formato parquet Stream
Substitui read por readStream
Código não muda!
Substitui write por writeStream
e save por start
16. Premissas de desenvolvimento Stream
input data source (S3, HDFS, Kafka, Dir)
processing batch (query,
functions, expressions)
output data (append, complete, update)
triggers (when to update the results)
17.
18.
19.
20.
21. Tipos de Entrada de Dados (Input Sources)
● File: arquivos em um diretório (parquet, json, csv)
● Kafka: obtém dados de e um tópico do Kafka (versão
>= 0.10.0)
● Socket: lê as informações de uma conexão via Socket,
usado para testes.
INPUT
22. Modos de Output, saída de dados
● Complete: todas as linhas resultantes do
processamento são direcionadas para saída de dados
● Update: somente as linhas que sofreram alterações ao
longo da última execução
● Append: somente as novas linhas geradas no
processamento
OUTPUT
23. Tipos de Saída de Dados (Output Sinks)
● File: armazena a saída em um diretório (parquet, orc,
json, csv)
● Kafka: armazena a saída e um ou mais tópicos do
Kafka (Apache Spark 2.3)
● Foreach: executa um processamento de forma
arbitrária nos registros de saída (personalizável)
● Memory/Console: registra as alterações e imprime no
console (usado para debug)
24. Tolerância a Falhas com Checkpoints
Checkpointing: gravação de
metadados (ex: offsets) em
write ahead logs em disco
(S3/HDFS) para recuperação
em caso de falhas.
34. Amostra de Dados CSV (Gzipped)
838,2,5500000000,100015,"{""authCode"":""3215"",""transactionIdAuth"":""101170622042837
4938""}",SUBSCRIPTION_050,0.5,11,0,1,14,Subscription renew.,2017-07-18
13:22:59.518,,,19,PRE,false,Charge Fail. CTN[31984771092][PRE][GSM]: Without
Credit.,,,,0,458,,engine2dc2,23,3,5,2017-07-18
13:22:59.544,,FE1952B0-571D-11E7-8A17-CA2EE9B22EAB,NT0359
838,2,5500000000,100008,"{""authCode"":""9496"",""transactionIdAuth"":""117170703192540
9718""}",SUBSCRIPTION_099,0.99,11,0,1,14,Subscription renew.,2017-07-18
13:22:58.893,,,19,PRE,false,Charge Fail. CTN[21976504467][PRE][GSM]: Without
Credit.,,,,0,1074,,engine2dc2,24,3,5,2017-07-18
13:22:58.928,,3ADF36D0-6040-11E7-9619-A2D6E78E4511,NT0360
703,2,5500000000,100004,"{""authCode"":""6838"",""transactionIdAuth"":""118170706120694
8526""}",SUBSCRIPTION_299,2.99,11,0,1,14,Subscription renew.,2017-07-18
13:22:59.246,,,19,PRE,false,Charge Fail. CTN[84994640470][PRE][GSM]: Without
Credit.,,,,0,748,,engine2dc2,24,3,5,2017-07-18 13:22:59.254, NT0299
35. Fonte de Entrada de Dados
val streamReader = spark.readStream
.format("csv")
.option("header", false)
.option("latestFirst", "true")
.option("maxFilesPerTrigger", "10")
.option("charset", "UTF-8")
.option("mode", "DROPMALFORMED")
.schema(ReadSchemas.csvTransactionSchema)
.load("hdfs://YOUR_PATH/20*/*/*/*.gz")
val conf = new SparkConf().setAppName("Structured Streaming")
val spark = SparkSession.builder()
.config(conf).getOrCreate()
fragmento de código Scala
INPUT
36. Estrutura na definição de um Schema
StructField("origin_id", IntegerType, true)
Nome do campo
Tipo do campo
pode ser nulo?
37. Estrutura na definição de um Schema
StructField("origin_id", IntegerType, true),
StructType(Array(
...
))
38. Definição de Schema de Leitura de Dados
// the csv data schema
def csvTransactionLogSchema = StructType {
StructType(Array(
StructField("id", StringType, true),
StructField("application_id", IntegerType, true),
StructField("carrier_id", IntegerType, true),
StructField("phone", StringType, true),
StructField("price", DoubleType, true),
StructField("origin_id", IntegerType, true),
. . .
StructField("transaction_status_id", IntegerType, true),
StructField("transaction_action_id", IntegerType, true),
StructField("returned_code", StringType, true),
StructField("creation_date", TimestampType, true),
StructField("rotate_number", IntegerType, true),
))
}
fragmento de código Scala
39. Processamento (Spark SQL API) v1
val query = streamReader
.withColumn("date", $"creation_date".cast("date"))
.withColumn("successful_charges", when($"transaction_status_id" === 2, 1))
.withColumn("no_credit", when($"transaction_status_id" === 0, 1).otherwise(0))
.withColumn("error", when($"transaction_status_id" === 3).otherwise(0))
.filter("carrier_id IN (1,2,4,5)")
.filter("transaction_status_id NOT IN (5, 6)")
.filter("transaction_action_id IN (0, 1)")
.withWatermark("creation_date", "3 hour")
.groupBy($"carrier_id", window($"creation_date", "5 minutes").as("window"))
.agg($"carrier_id",
avg($"response_time").as("avg_response_time"),
sum($"successful_charges").as("successful_charges"),
sum($"no_credit").as("no_credit"),
sum($"error").as("error"),
count($"carrier_id").as("total_attempts"))
select
case
when
filtering
aggregation
41. Processamento (Spark SQL) v2
val query : DataFrame = spark.sql(
"""
SELECT carrier_id, TO_DATE(creation_date) as record_date, HOUR(creation_date) as hour_of_day,
WINDOW(creation_date, "5 minutes").start as start_date,
AVG(response_time) as avg_response_time ,
SUM(CASE
WHEN transaction_status_id = 2 THEN 1
ELSE 0
END) as successful_charges,
SUM(CASE
WHEN transaction_status_id = 0 THEN 1
ELSE 0
END) as no_credit,
count(carrier_id) as total_attempts
FROM transaction_raw
WHERE carrier_id IN (1,2,4,5)
AND transaction_action_id IN (0, 1)
AND transaction_status_id NOT IN (5, 6)
GROUP BY carrier_id, TO_DATE(creation_date), HOUR(creation_date),
WINDOW(creation_date, "5 minutes").start
""")
SELECT carrier_id, TO_DATE(creation_date) as record_date, HOUR(creation_date) as hour_of_day,
WINDOW(creation_date, "5 minutes").start as start_date,
AVG(response_time) as avg_response_time ,
SUM(CASE
WHEN transaction_status_id = 2 THEN 1
ELSE 0
END) as successful_charges,
SUM(CASE
WHEN transaction_status_id = 0 THEN 1
ELSE 0
END) as no_credit,
count(carrier_id) as total_attempts
FROM transaction_temp_table
WHERE carrier_id IN (1,2,4,5)
AND transaction_action_id IN (0, 1)
AND transaction_status_id NOT IN (5, 6)
GROUP BY carrier_id, TO_DATE(creation_date), HOUR(creation_date),
WINDOW(creation_date, "5 minutes").start
42. Fonte de Saída de Dados
val properties = ConfigFactory.load()
val username = properties.getString("postgres.username")
val password = properties.getString("postgres.password")
val resource = properties.getString("postgres.url")
val jdbcWriter = new JDBCSink(resource, username, password)
val foreachStream = query
.select($"carrier_id", $"date", $"hour_of_day", $"start_date", $"end_date")
.writeStream
.foreach(jdbcWriter)
.outputMode(OutputMode.Update())
.trigger(Trigger.ProcessingTime("2 minute"))
.option("checkpointLocation", "hdfs://YOUR_PATH/checkpoint-complete/")
.start
foreachStream.awaitTermination()
OUTPUT
47. Resiliência no processamento
com Streams
val input = spark.readStream
.option("mode", "DROPMALFORMED")
spark.sqlContext
.setConf("spark.sql.files.ignoreCorruptFiles","true")
48. Operações não Suportadas com Streams (DF/DS)
● Agregação de múltiplos Streams de dados
● Uso de LIMIT ou TAKE das primeiras N linhas
● Uso da instrução DISTINCT
● Operações de ordenação (parcialmente) em Output
Mode Complete
● Operações de Outter Joins também não são suportadas