Apache Spark
Structured Stream
Um moedor de dados em tempo real quase real
Eiti Kimura
Datafest Out/2018
Apache Spark
Structured Stream
● IT Coordinator and Software Architect at Movile
● Msc. in Electrical Engineering
● Apache Cassandra MVP (2014/2015 e 2015/2016)
● Apache Cassandra Contributor (2015)
● Cassandra Summit Speaker (2014 e 2015)
● Strata Hadoop World Singapore Speaker (2016)
● Spark Summit Speaker (2017)
● RedisConf Speaker (2018)
Eiti Kimura
eitikimura
a Movile company
building strong relations
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
By Alexander Savchuk
Introdução ao Apache Spark
Apache Spark™ is a fast and general engine
for large-scale data processing.
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
Funcionamento Apache Spark
Arquitetura do Apache Spark
Stream de Dados
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.
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)
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
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
Apache Spark Structured Stream
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)
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
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
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)
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.
Tratamento de dados desordenados (Watermarking)
'Moedor' de dados em tempo real
quase real
USE CASE
Processo de ETL tradicional
minutes / seconds
Processo com Structured Streaming
Subscription &
Billing System a.k.a
SBSSBS
Arquitetura do Processador Contínuo
Arquitetura do Processador Contínuo
Arquitetura do Processador Contínuo
Arquitetura do Processador Contínuo
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
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
Estrutura na definição de um Schema
StructField("origin_id", IntegerType, true)
Nome do campo
Tipo do campo
pode ser nulo?
Estrutura na definição de um Schema
StructField("origin_id", IntegerType, true),
StructType(Array(
...
))
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
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
Processamento (Spark SQL API) v2
streamReader
.withWatermark("creation_date", "3 hour")
.createOrReplaceTempView("transaction_temp_table")
val streamReader = spark.readStream
.format("csv")
.schema(ReadSchemas.csvTransactionSchema)
.load("hdfs://YOUR_PATH/20*/*/*/*.gz")
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
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
Result table em consolidação
+--+-----------+-----------+-------------------+-------------------+------------------+---------------+---------+-----+--------------+
|id|record_date|hour_of_day|start_date |end_date |avg_response_time |success_charges|no_credit|error|total_attempts|
+--+-----------+-----------+-------------------+-------------------+------------------+---------------+---------+-----+--------------+
|1 |2017-07-18 |13 |2017-07-18 13:20:00|2017-07-18 13:25:00|618.8061297220243 |4 |2607 |195 |2806 |
|2 |2017-07-18 |13 |2017-07-18 13:20:00|2017-07-18 13:25:00|1456.4242838751206|13 |10912 |1503 |12428 |
|5 |2017-07-18 |13 |2017-07-18 13:20:00|2017-07-18 13:25:00|1161.7308960143841|9 |2796 |532 |3337 |
|4 |2017-07-18 |13 |2017-07-18 13:20:00|2017-07-18 13:25:00|2950.642105263158 |4 |1364 |54 |1425 |
+--+-----------+-----------+-------------------+-------------------+------------------+---------------+---------+-----+--------------+
Resultados obtidos
Structured Streaming
t = 3-7 minutos
Lições Aprendidas
de
Schema
EV ÇÃO
def csvSchema = StructType {
StructType(Array(
StructField("id", StringType, true),
StructField("application_id", IntegerType, true),
StructField("carrier_id", IntegerType, true),
StructField("creation_date", TimestampType, true)
))}
Resiliência no processamento
com Streams
val input = spark.readStream
.option("mode", "DROPMALFORMED")
spark.sqlContext
.setConf("spark.sql.files.ignoreCorruptFiles","true")
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
MUITO OBRIGADO!
eitikimura eiti-kimura-movile eiti.kimura@wavy.global

[Datafest 2018] Apache Spark Structured Stream - Moedor de dados em tempo quase real

  • 1.
    Apache Spark Structured Stream Ummoedor de dados em tempo real quase real Eiti Kimura Datafest Out/2018 Apache Spark Structured Stream
  • 2.
    ● IT Coordinatorand Software Architect at Movile ● Msc. in Electrical Engineering ● Apache Cassandra MVP (2014/2015 e 2015/2016) ● Apache Cassandra Contributor (2015) ● Cassandra Summit Speaker (2014 e 2015) ● Strata Hadoop World Singapore Speaker (2016) ● Spark Summit Speaker (2017) ● RedisConf Speaker (2018) Eiti Kimura eitikimura
  • 3.
    a Movile company buildingstrong relations
  • 4.
    Sumário ● Introdução aoApache Spark ● Nova API Structured Streaming ● Caso de uso de uma aplicação de processamento em "tempo real" ● Lições aprendidas
  • 5.
  • 6.
    Introdução ao ApacheSpark Apache Spark™ is a fast and general engine for large-scale data processing.
  • 7.
    Em que oApache Spark é usado? ● Processos de ETL ● Consultas Interativas (SQL) ● Análise de dados avançada ● Machine Learning/Deep Learning ● Streaming sobre grandes datasets
  • 8.
  • 9.
  • 10.
  • 11.
    Structured Stream API dealto 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 StructuredStream • 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 comDataFrames 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 comDataFrames 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
  • 15.
  • 16.
    Premissas de desenvolvimentoStream input data source (S3, HDFS, Kafka, Dir) processing batch (query, functions, expressions) output data (append, complete, update) triggers (when to update the results)
  • 21.
    Tipos de Entradade 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ídade 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 Falhascom Checkpoints Checkpointing: gravação de metadados (ex: offsets) em write ahead logs em disco (S3/HDFS) para recuperação em caso de falhas.
  • 25.
    Tratamento de dadosdesordenados (Watermarking)
  • 26.
    'Moedor' de dadosem tempo real quase real USE CASE
  • 27.
    Processo de ETLtradicional minutes / seconds Processo com Structured Streaming
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 34.
    Amostra de DadosCSV (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 Entradade 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çãode um Schema StructField("origin_id", IntegerType, true) Nome do campo Tipo do campo pode ser nulo?
  • 37.
    Estrutura na definiçãode um Schema StructField("origin_id", IntegerType, true), StructType(Array( ... ))
  • 38.
    Definição de Schemade 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 SQLAPI) 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
  • 40.
    Processamento (Spark SQLAPI) v2 streamReader .withWatermark("creation_date", "3 hour") .createOrReplaceTempView("transaction_temp_table") val streamReader = spark.readStream .format("csv") .schema(ReadSchemas.csvTransactionSchema) .load("hdfs://YOUR_PATH/20*/*/*/*.gz")
  • 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ídade 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
  • 43.
    Result table emconsolidação +--+-----------+-----------+-------------------+-------------------+------------------+---------------+---------+-----+--------------+ |id|record_date|hour_of_day|start_date |end_date |avg_response_time |success_charges|no_credit|error|total_attempts| +--+-----------+-----------+-------------------+-------------------+------------------+---------------+---------+-----+--------------+ |1 |2017-07-18 |13 |2017-07-18 13:20:00|2017-07-18 13:25:00|618.8061297220243 |4 |2607 |195 |2806 | |2 |2017-07-18 |13 |2017-07-18 13:20:00|2017-07-18 13:25:00|1456.4242838751206|13 |10912 |1503 |12428 | |5 |2017-07-18 |13 |2017-07-18 13:20:00|2017-07-18 13:25:00|1161.7308960143841|9 |2796 |532 |3337 | |4 |2017-07-18 |13 |2017-07-18 13:20:00|2017-07-18 13:25:00|2950.642105263158 |4 |1364 |54 |1425 | +--+-----------+-----------+-------------------+-------------------+------------------+---------------+---------+-----+--------------+
  • 44.
  • 45.
  • 46.
    de Schema EV ÇÃO def csvSchema= StructType { StructType(Array( StructField("id", StringType, true), StructField("application_id", IntegerType, true), StructField("carrier_id", IntegerType, true), StructField("creation_date", TimestampType, true) ))}
  • 47.
    Resiliência no processamento comStreams val input = spark.readStream .option("mode", "DROPMALFORMED") spark.sqlContext .setConf("spark.sql.files.ignoreCorruptFiles","true")
  • 48.
    Operações não Suportadascom 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
  • 49.