SlideShare a Scribd company logo
1 of 53
Download to read offline
Sekretne życie jobów Sparkowych
Marcin Jasiński
Paweł Leszczyński
O nas
● Data Platform Engineers @ Allegro.
● Rozwijamy jeden z większych ekosystemów Big Data w Polsce.
● Piszemy joby Sparkowe i uzdrawiamy joby innych.
● W każdy piątek jemy pizzę bez warzyw.
Agenda
● Trzy słowa o Sparku
● Cztery problemy ze Sparkiem
● Historia pewnego joba
4
Trzy słowa o Sparku
Spark Dataset & Dataset API
A Dataset is a strongly typed collection
of domain-specific objects
that can be transformed in parallel
using functional or relational operations.
// To create Dataset[Row] using SparkSession
val people = spark.read.parquet("...")
val department = spark.read.parquet("...")
Akcje i transformacje
● Wynikiem transformacji jest nowy dataset
● Transformacja jest wykonywana leniwie po wywołaniu akcji
● Akcja wyzwala wykonanie wszystkich transformacji
potrzebnych do uzyskania datasetu wynikowego z danych
wejściowych.
people.filter("age > 30")
.join(department, people("deptId") === department("id"))
.groupBy(department("name"), people("gender"))
.agg(avg(people("salary")), max(people("age")))
Wąskie transformacje Szerokie transformacje
map, filter
`
union
`
groupByKey
join with inputs not
co-opartitioned
join with inputs
co-opartitioned
Worker
pipe
Driver
pipe
Worker
pipe
Worker
pipe
Worker
pipe
Jak działa PySpark?
BlockStorage
Worker
Driver
pipe
Worker
Worker
Worker
Jak działa PySpark?
BlockStorage
11
Cztery najczęstsze
problemy ze Sparkiem
Spark Web UI
● Active / Dead
● GC Time
● Input Size
● Shuffle size
● Disk Used
Problemy z Executorami
- Event Timeline
- Yarn Logs
Problemy z Executorami
Czasy poza Executor Computing Time powinny być możliwie najniższe.
Problemy z Executorami
Uczmy się na błędach
Spark History Server
Spark History Server
Job jest ubijany na klastrze,
yarn logi wskazują problemy z driverem.
Problemy z Driverem
Job jest ubijany na klastrze,
yarn logi wskazują problemy z driverem.
Potencjalne problemy:
● błąd w kodzie
● duży collect
Problemy z Driverem
Job jest ubijany na klastrze,
yarn logi wskazują problemy z driverem.
Potencjalne problemy:
● błąd w kodzie -> fix kodu (testy)
● duży collect -> podbicie pamięci drivera
Problemy z Driverem
Job jest ubijany na klastrze,
yarn logi wskazują problemy z driverem.
Potencjalne problemy:
● błąd w kodzie -> fix kodu (testy)
● duży collect -> podbicie pamięci drivera
spark.driver.memory 1G
spark.driver.memoryOverhead spark.driver.memory * 0.10 + 384M
spark.driver.maxResultSize 1G
spark.driver.cores 1
Problemy z Driverem
Model pamięci - executor
overhead
spark.executor.memory
spark.executor.memoryOverhead
yarn
container
memory
execution
storage
OverHead memory - pyspark
overhead
“... dzieje się więcej w Pythonie
niż na JVM”
Arek O.
Ładujemy plik CSV lub JSON do przetworzeń i ładowanie
danych trwa bardzo długo.
Schema inferring
def inferFromDataset(json: Dataset[String], parsedOptions: JSONOptions): StructType =
{
val sampled: Dataset[String] = JsonUtils.sample(json, parsedOptions)
val rdd: RDD[InternalRow] = sampled.queryExecution.toRdd
val rowParser = parsedOptions.encoding.map { enc =>
CreateJacksonParser.internalRow(enc, _: JsonFactory, _: InternalRow)
}.getOrElse(CreateJacksonParser.internalRow(_: JsonFactory, _: InternalRow))
SQLExecution.withSQLConfPropagated(json.sparkSession) {
JsonInferSchema.infer(rdd, parsedOptions, rowParser)
}
}
Schema inferring - Fragment kodu źródłowego Sparka
def sample(json: Dataset[String], options: JSONOptions): Dataset[String] = {
require(options.samplingRatio > 0,
s"samplingRatio (${options.samplingRatio}) should be greater than 0")
if (options.samplingRatio > 0.99) {
json
} else {
json.sample(withReplacement = false, options.samplingRatio, 1)
}
}
val samplingRatio =
parameters.get("samplingRatio").map(_.toDouble).getOrElse(1.0)
Schema inferring - Fragment kodu źródłowego Sparka:
from pyspark.sql.types import *
schema = StructType([
StructField('thread_name', StringType()),
StructField('app_name', StringType()),
StructField('level', StringType()),
...
StructField('heap_size', LongType()),
StructField('gc_time', LongType())
])
df = spark.read.csv("data.csv", schema=schema, header=True)
Schema inferring
bez schematu
%timeit df = spark.read.csv("data.csv", header=True,
inferSchema=True)
38 s ± 2.52 s per loop
ze schematem
%timeit df = spark.read.csv("data.csv", schema=schema,
header=True)
6.38 s ± 847 µs per loop
Schema inferring
Na każdym elemencie datasetu wykonujemy operację,
która wymaga połączenia do zewnętrznej usługi.
Map vs mapPartitions
Na każdym elemencie datasetu wykonujemy operację,
która wymaga połączenia do zewnętrznej usługi.
● Chcemy:
○ ograniczać ilość połączeń,
○ mieć możliwość wykonania operacji na wielu rekordach.
Map vs mapPartitions
Na każdym elemencie datasetu wykonujemy operację,
która wymaga połączenia do zewnętrznej usługi.
● Chcemy:
○ ograniczać ilość połączeń,
○ mieć możliwość wykonania operacji na wielu rekordach.
Rozwiązaniem jest mapParititions.
Map vs mapPartitions
import time
rdd = sc.parallelize([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)
def connect_mock_1(element):
time.sleep(1)
return type(element)
def connect_mock_2(iterator):
time.sleep(1)
for element in iterator:
yield type(element)
% timeit rdd.map(connect_mock_1).collect()
% timeit rdd.mapPartitions(connect_mock_2).collect()
import time
rdd = sc.parallelize([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)
def connect_mock_1(element):
time.sleep(1)
return type(element)
def connect_mock_2(iterator):
time.sleep(1)
for element in iterator:
yield type(element)
% timeit rdd.map(connect_mock_1).collect()
3.04 s ± 6.34 ms per loop
% timeit rdd.mapPartitions(connect_mock_2).collect()
1.04 s ± 5.73 ms per loop
Korzystam z dynamic resource allocation,
a mimo to mam problem z alokacją zasobów.
Dynamic resource allocation TTL vs cache
● Job uruchomiony poprawnie,
● Dynamic resource allocation włączone,
● Parametry dostosowane do potrzeb,
● Wszystko wygląda idealnie.
Dynamic resource allocation TTL vs cache
● Job uruchomiony poprawnie,
● Dynamic resource allocation włączone,
● Parametry dostosowane do potrzeb,
● Wszystko wygląda idealnie.
Hmmm….?
Dynamic resource allocation TTL vs cache
● Job uruchomiony poprawnie,
● Dynamic resource allocation włączone,
● Parametry dostosowane do potrzeb,
● Wszystko wygląda idealnie,
A może to cache?
Dynamic resource allocation TTL vs cache
● Job uruchomiony poprawnie,
● Dynamic resource allocation włączone,
● Parametry dostosowane do potrzeb,
● Wszystko wygląda idealnie.
A może to cache?
spark.dynamicAllocation.cachedExecutorIdleTimeout
default: infinity
Dynamic resource allocation TTL vs cache
Dynamic resource allocation TTL vs cache - jak to działa
Spark Job
Executor 112
Executor 2
enabled true
executorIdleTimeout 60s
cachedExecutorIdleTimeout infinity
initialExecutors 2
minExecutors 2
maxExecutors 10
Dynamic resource allocation TTL vs cache - jak to działa
Spark
Job
Executor 1
12
Executor 2
Executor 312
Executor 4
Executor 5
executorIdleTimeout 60s
Dynamic resource allocation TTL vs cache - jak to działa
Spark
Job
Executor 1
12
Executor 2
Executor 312
Executor 4
Executor 5
Po 60 sekundach bezczynne executory są zatrzymywane
Dynamic resource allocation TTL vs cache - jak to działa
Spark
Job
Executor 1
12
Executor 2
Executor 312
Executor 4
Executor 5
cachedExecutorIdleTimeout infinity
Cache
Cache
Cache
Cache
Cache
Dynamic resource allocation TTL vs cache - jak to działa
Spark
Job
Executor 1
12
Executor 2
Executor 312
Executor 4
Executor 5
cachedExecutorIdleTimeout 120s
Cache
Cache
Cache
Cache
Cache
42
Historia pewnego joba
Sprytny Join
30/06/201829/06/201828/06/201801/06/2018
Dataset A:
Frazy wyszukiwania
30/06/201829/06/201828/06/201801/06/2018
Dataset B:
Decyzje zakupowe
● Dane na HDFS są partycjonowane dziennie.
● Problem: Jak połączyć decyzję zakupową z ostatnią frazą wyszukania, która została użyta nie
dłużej niż X godzin przed decyzją?
A join B on A.client = B.client and B.timestamp - A.timestamp < X godzin
Sprytny join - cogroup
(A, 1) (D, 2) (D, [2], [])
(C, [1, 4]) cogroup (A, 3) = (C, [1,4], [])
(B, [2, 3]) (B, [1,3] (A, [1], [3])
(B, [2,3], [1,3])
Rozwiązanie naszego problemu:
● A.groupby('clientId') - dostajemy mapę clientId => lista wyszukiwań
● B.groupby('clientId') - dostajemy mapę clientId => lista decyzji zakupowych
● Wykonujemy cogroupa na powyższych i otrzymujemy mapę
○ clientId => [lista elementów zbioru A], [lista elementów zbioru B]
○ łączymy posortowane listy w posortowną listę
○ iterujemy po niej w poszukiwaniu par (wyszukanie, zakup) w odstępie X godzin
Sprytny join - łączenie danych partycjonowanych dziennie
30/06/201829/06/201828/06/201801/06/2018
Dataset A:
Frazy wyszukiwania
30/06/201829/06/201828/06/201801/06/2018
Dataset B:
Decyzje zakupowe
● Join na całym miesiącu był bardzo ciężki, wymagał podniesienia całego
datasetu, a dane nie mieściły się w pamięci.
● Jeśli dane nie mieszczą się w pamięci, to Spark zwalnia!
Sprytne joiny - iteracyjny join
for day in days:
one_day_dataset_A = fetch_one_day(dataset_A).cache()
Sprytne joiny - iteracyjny join
for day in days:
one_day_dataset_A = fetch_one_day(dataset_A).cache()
dailyJoinResult = joinOneDay(
one_day_before_dataset_A.union(one_day_dataset_A),
fetch_one_day(dataset_B)
).cache()
Sprytne joiny - iteracyjny join
for day in days:
one_day_dataset_A = fetch_one_day(dataset_A).cache()
dailyJoinResult = joinOneDay(
one_day_before_dataset_A.union(one_day_dataset_A),
fetch_one_day(dataset_B)
).cache()
result = result.union(dailyJoinResult)
one_day_before_dataset_A = one_day_dataset_A
Job przed i po
I ETAP
● Usunęliśmy niepotrzebnego repartition’a na początku przetwarzań
● Zmniejszyliśmy liczbę executorów z 240 do 100.
● Job nadal wykonywał się w czasie akceptowalnym biznesowo
przed zmianami po zmianach
Ilość executorów 240 100
Job przed i po
II ETAP
● Wczytanie danych JSON z zadanym schematem
● Zmiana joinowania na podejście iteracyjne
Dane z 10 dni przed zmianami po zmianach
Ilość executorów 50 20
Czas wykonania 98 mins 23 sec 24 mins 22 sec
YARN MB-seconds 2,768,187,887 229,664,298
YARN Vcore-seconds 270,235 22,418
Każdy job wymaga
indywidualnego podejścia.
Pytania?
Dziękujemy

More Related Content

Similar to Confitura 2018 - Sekretne życie jobów Sparkowych

[WHUG] Wielki brat patrzy - czyli jak zbieramy dane o użytkownikach allegro
[WHUG] Wielki brat patrzy - czyli jak zbieramy dane o użytkownikach allegro[WHUG] Wielki brat patrzy - czyli jak zbieramy dane o użytkownikach allegro
[WHUG] Wielki brat patrzy - czyli jak zbieramy dane o użytkownikach allegroallegro.tech
 
Michał Dec - Quality in Clouds
Michał Dec - Quality in CloudsMichał Dec - Quality in Clouds
Michał Dec - Quality in Cloudskraqa
 
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...PROIDEA
 
[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariować[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariowaćJakub Marchwicki
 
4Developers 2015: Property-based testing w języku Scala - Paweł Grajewski
4Developers 2015: Property-based testing w języku Scala - Paweł Grajewski4Developers 2015: Property-based testing w języku Scala - Paweł Grajewski
4Developers 2015: Property-based testing w języku Scala - Paweł GrajewskiPROIDEA
 
Mvc frontend-trug-02-2011
Mvc frontend-trug-02-2011Mvc frontend-trug-02-2011
Mvc frontend-trug-02-2011Rafal Piekarski
 
Architektura ngrx w angular 2+
Architektura ngrx w angular 2+Architektura ngrx w angular 2+
Architektura ngrx w angular 2+Paweł Żurowski
 
Modularny JavaScript - meet.js
Modularny JavaScript - meet.jsModularny JavaScript - meet.js
Modularny JavaScript - meet.jsPatryk Jar
 
Metaprogramowanie w JS
Metaprogramowanie w JSMetaprogramowanie w JS
Metaprogramowanie w JSDawid Rusnak
 
tRPC - czy to koniec GraphQL?
tRPC - czy to koniec GraphQL?tRPC - czy to koniec GraphQL?
tRPC - czy to koniec GraphQL?Brainhub
 
DynamoDB – podstawy modelowania danych dla opornych
DynamoDB – podstawy modelowania danych dla opornychDynamoDB – podstawy modelowania danych dla opornych
DynamoDB – podstawy modelowania danych dla opornychThe Software House
 
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...The Software House
 
Jak nie narobić sobie problemów korzystając z EntityFramework
Jak nie narobić sobie problemów korzystając z EntityFrameworkJak nie narobić sobie problemów korzystając z EntityFramework
Jak nie narobić sobie problemów korzystając z EntityFrameworkHighWheelSoftware
 
„Need for speed, czyli jak wycisnąć siódme poty z bazy PostgreSQL” - Wojciech...
„Need for speed, czyli jak wycisnąć siódme poty z bazy PostgreSQL” - Wojciech...„Need for speed, czyli jak wycisnąć siódme poty z bazy PostgreSQL” - Wojciech...
„Need for speed, czyli jak wycisnąć siódme poty z bazy PostgreSQL” - Wojciech...krakspot
 
Python szybki start
Python   szybki startPython   szybki start
Python szybki startSages
 

Similar to Confitura 2018 - Sekretne życie jobów Sparkowych (20)

[WHUG] Wielki brat patrzy - czyli jak zbieramy dane o użytkownikach allegro
[WHUG] Wielki brat patrzy - czyli jak zbieramy dane o użytkownikach allegro[WHUG] Wielki brat patrzy - czyli jak zbieramy dane o użytkownikach allegro
[WHUG] Wielki brat patrzy - czyli jak zbieramy dane o użytkownikach allegro
 
Michał Dec - Quality in Clouds
Michał Dec - Quality in CloudsMichał Dec - Quality in Clouds
Michał Dec - Quality in Clouds
 
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...
 
[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariować[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariować
 
4Developers 2015: Property-based testing w języku Scala - Paweł Grajewski
4Developers 2015: Property-based testing w języku Scala - Paweł Grajewski4Developers 2015: Property-based testing w języku Scala - Paweł Grajewski
4Developers 2015: Property-based testing w języku Scala - Paweł Grajewski
 
Mvc frontend-trug-02-2011
Mvc frontend-trug-02-2011Mvc frontend-trug-02-2011
Mvc frontend-trug-02-2011
 
Architektura ngrx w angular 2+
Architektura ngrx w angular 2+Architektura ngrx w angular 2+
Architektura ngrx w angular 2+
 
Modularny JavaScript - meet.js
Modularny JavaScript - meet.jsModularny JavaScript - meet.js
Modularny JavaScript - meet.js
 
Metaprogramowanie w JS
Metaprogramowanie w JSMetaprogramowanie w JS
Metaprogramowanie w JS
 
Mongodb with Rails
Mongodb with RailsMongodb with Rails
Mongodb with Rails
 
DSL - DYI
DSL - DYIDSL - DYI
DSL - DYI
 
Torquebox
TorqueboxTorquebox
Torquebox
 
tRPC - czy to koniec GraphQL?
tRPC - czy to koniec GraphQL?tRPC - czy to koniec GraphQL?
tRPC - czy to koniec GraphQL?
 
DynamoDB – podstawy modelowania danych dla opornych
DynamoDB – podstawy modelowania danych dla opornychDynamoDB – podstawy modelowania danych dla opornych
DynamoDB – podstawy modelowania danych dla opornych
 
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...
 
Jak nie narobić sobie problemów korzystając z EntityFramework
Jak nie narobić sobie problemów korzystając z EntityFrameworkJak nie narobić sobie problemów korzystając z EntityFramework
Jak nie narobić sobie problemów korzystając z EntityFramework
 
Platforma Kontentowa
Platforma KontentowaPlatforma Kontentowa
Platforma Kontentowa
 
„Need for speed, czyli jak wycisnąć siódme poty z bazy PostgreSQL” - Wojciech...
„Need for speed, czyli jak wycisnąć siódme poty z bazy PostgreSQL” - Wojciech...„Need for speed, czyli jak wycisnąć siódme poty z bazy PostgreSQL” - Wojciech...
„Need for speed, czyli jak wycisnąć siódme poty z bazy PostgreSQL” - Wojciech...
 
test
testtest
test
 
Python szybki start
Python   szybki startPython   szybki start
Python szybki start
 

Confitura 2018 - Sekretne życie jobów Sparkowych

  • 1. Sekretne życie jobów Sparkowych Marcin Jasiński Paweł Leszczyński
  • 2. O nas ● Data Platform Engineers @ Allegro. ● Rozwijamy jeden z większych ekosystemów Big Data w Polsce. ● Piszemy joby Sparkowe i uzdrawiamy joby innych. ● W każdy piątek jemy pizzę bez warzyw.
  • 3. Agenda ● Trzy słowa o Sparku ● Cztery problemy ze Sparkiem ● Historia pewnego joba
  • 5.
  • 6. Spark Dataset & Dataset API A Dataset is a strongly typed collection of domain-specific objects that can be transformed in parallel using functional or relational operations. // To create Dataset[Row] using SparkSession val people = spark.read.parquet("...") val department = spark.read.parquet("...")
  • 7. Akcje i transformacje ● Wynikiem transformacji jest nowy dataset ● Transformacja jest wykonywana leniwie po wywołaniu akcji ● Akcja wyzwala wykonanie wszystkich transformacji potrzebnych do uzyskania datasetu wynikowego z danych wejściowych. people.filter("age > 30") .join(department, people("deptId") === department("id")) .groupBy(department("name"), people("gender")) .agg(avg(people("salary")), max(people("age")))
  • 8. Wąskie transformacje Szerokie transformacje map, filter ` union ` groupByKey join with inputs not co-opartitioned join with inputs co-opartitioned
  • 12. Spark Web UI ● Active / Dead ● GC Time ● Input Size ● Shuffle size ● Disk Used Problemy z Executorami
  • 13. - Event Timeline - Yarn Logs Problemy z Executorami
  • 14. Czasy poza Executor Computing Time powinny być możliwie najniższe. Problemy z Executorami
  • 15. Uczmy się na błędach Spark History Server Spark History Server
  • 16. Job jest ubijany na klastrze, yarn logi wskazują problemy z driverem. Problemy z Driverem
  • 17. Job jest ubijany na klastrze, yarn logi wskazują problemy z driverem. Potencjalne problemy: ● błąd w kodzie ● duży collect Problemy z Driverem
  • 18. Job jest ubijany na klastrze, yarn logi wskazują problemy z driverem. Potencjalne problemy: ● błąd w kodzie -> fix kodu (testy) ● duży collect -> podbicie pamięci drivera Problemy z Driverem
  • 19. Job jest ubijany na klastrze, yarn logi wskazują problemy z driverem. Potencjalne problemy: ● błąd w kodzie -> fix kodu (testy) ● duży collect -> podbicie pamięci drivera spark.driver.memory 1G spark.driver.memoryOverhead spark.driver.memory * 0.10 + 384M spark.driver.maxResultSize 1G spark.driver.cores 1 Problemy z Driverem
  • 20. Model pamięci - executor overhead spark.executor.memory spark.executor.memoryOverhead yarn container memory execution storage
  • 21. OverHead memory - pyspark overhead “... dzieje się więcej w Pythonie niż na JVM” Arek O.
  • 22. Ładujemy plik CSV lub JSON do przetworzeń i ładowanie danych trwa bardzo długo. Schema inferring
  • 23. def inferFromDataset(json: Dataset[String], parsedOptions: JSONOptions): StructType = { val sampled: Dataset[String] = JsonUtils.sample(json, parsedOptions) val rdd: RDD[InternalRow] = sampled.queryExecution.toRdd val rowParser = parsedOptions.encoding.map { enc => CreateJacksonParser.internalRow(enc, _: JsonFactory, _: InternalRow) }.getOrElse(CreateJacksonParser.internalRow(_: JsonFactory, _: InternalRow)) SQLExecution.withSQLConfPropagated(json.sparkSession) { JsonInferSchema.infer(rdd, parsedOptions, rowParser) } } Schema inferring - Fragment kodu źródłowego Sparka
  • 24. def sample(json: Dataset[String], options: JSONOptions): Dataset[String] = { require(options.samplingRatio > 0, s"samplingRatio (${options.samplingRatio}) should be greater than 0") if (options.samplingRatio > 0.99) { json } else { json.sample(withReplacement = false, options.samplingRatio, 1) } } val samplingRatio = parameters.get("samplingRatio").map(_.toDouble).getOrElse(1.0) Schema inferring - Fragment kodu źródłowego Sparka:
  • 25. from pyspark.sql.types import * schema = StructType([ StructField('thread_name', StringType()), StructField('app_name', StringType()), StructField('level', StringType()), ... StructField('heap_size', LongType()), StructField('gc_time', LongType()) ]) df = spark.read.csv("data.csv", schema=schema, header=True) Schema inferring
  • 26. bez schematu %timeit df = spark.read.csv("data.csv", header=True, inferSchema=True) 38 s ± 2.52 s per loop ze schematem %timeit df = spark.read.csv("data.csv", schema=schema, header=True) 6.38 s ± 847 µs per loop Schema inferring
  • 27. Na każdym elemencie datasetu wykonujemy operację, która wymaga połączenia do zewnętrznej usługi. Map vs mapPartitions
  • 28. Na każdym elemencie datasetu wykonujemy operację, która wymaga połączenia do zewnętrznej usługi. ● Chcemy: ○ ograniczać ilość połączeń, ○ mieć możliwość wykonania operacji na wielu rekordach. Map vs mapPartitions
  • 29. Na każdym elemencie datasetu wykonujemy operację, która wymaga połączenia do zewnętrznej usługi. ● Chcemy: ○ ograniczać ilość połączeń, ○ mieć możliwość wykonania operacji na wielu rekordach. Rozwiązaniem jest mapParititions. Map vs mapPartitions
  • 30. import time rdd = sc.parallelize([1, 2, 3, 4, 5, 6, 7, 8, 9], 3) def connect_mock_1(element): time.sleep(1) return type(element) def connect_mock_2(iterator): time.sleep(1) for element in iterator: yield type(element) % timeit rdd.map(connect_mock_1).collect() % timeit rdd.mapPartitions(connect_mock_2).collect()
  • 31. import time rdd = sc.parallelize([1, 2, 3, 4, 5, 6, 7, 8, 9], 3) def connect_mock_1(element): time.sleep(1) return type(element) def connect_mock_2(iterator): time.sleep(1) for element in iterator: yield type(element) % timeit rdd.map(connect_mock_1).collect() 3.04 s ± 6.34 ms per loop % timeit rdd.mapPartitions(connect_mock_2).collect() 1.04 s ± 5.73 ms per loop
  • 32. Korzystam z dynamic resource allocation, a mimo to mam problem z alokacją zasobów. Dynamic resource allocation TTL vs cache
  • 33. ● Job uruchomiony poprawnie, ● Dynamic resource allocation włączone, ● Parametry dostosowane do potrzeb, ● Wszystko wygląda idealnie. Dynamic resource allocation TTL vs cache
  • 34. ● Job uruchomiony poprawnie, ● Dynamic resource allocation włączone, ● Parametry dostosowane do potrzeb, ● Wszystko wygląda idealnie. Hmmm….? Dynamic resource allocation TTL vs cache
  • 35. ● Job uruchomiony poprawnie, ● Dynamic resource allocation włączone, ● Parametry dostosowane do potrzeb, ● Wszystko wygląda idealnie, A może to cache? Dynamic resource allocation TTL vs cache
  • 36. ● Job uruchomiony poprawnie, ● Dynamic resource allocation włączone, ● Parametry dostosowane do potrzeb, ● Wszystko wygląda idealnie. A może to cache? spark.dynamicAllocation.cachedExecutorIdleTimeout default: infinity Dynamic resource allocation TTL vs cache
  • 37. Dynamic resource allocation TTL vs cache - jak to działa Spark Job Executor 112 Executor 2 enabled true executorIdleTimeout 60s cachedExecutorIdleTimeout infinity initialExecutors 2 minExecutors 2 maxExecutors 10
  • 38. Dynamic resource allocation TTL vs cache - jak to działa Spark Job Executor 1 12 Executor 2 Executor 312 Executor 4 Executor 5 executorIdleTimeout 60s
  • 39. Dynamic resource allocation TTL vs cache - jak to działa Spark Job Executor 1 12 Executor 2 Executor 312 Executor 4 Executor 5 Po 60 sekundach bezczynne executory są zatrzymywane
  • 40. Dynamic resource allocation TTL vs cache - jak to działa Spark Job Executor 1 12 Executor 2 Executor 312 Executor 4 Executor 5 cachedExecutorIdleTimeout infinity Cache Cache Cache Cache Cache
  • 41. Dynamic resource allocation TTL vs cache - jak to działa Spark Job Executor 1 12 Executor 2 Executor 312 Executor 4 Executor 5 cachedExecutorIdleTimeout 120s Cache Cache Cache Cache Cache
  • 43. Sprytny Join 30/06/201829/06/201828/06/201801/06/2018 Dataset A: Frazy wyszukiwania 30/06/201829/06/201828/06/201801/06/2018 Dataset B: Decyzje zakupowe ● Dane na HDFS są partycjonowane dziennie. ● Problem: Jak połączyć decyzję zakupową z ostatnią frazą wyszukania, która została użyta nie dłużej niż X godzin przed decyzją? A join B on A.client = B.client and B.timestamp - A.timestamp < X godzin
  • 44. Sprytny join - cogroup (A, 1) (D, 2) (D, [2], []) (C, [1, 4]) cogroup (A, 3) = (C, [1,4], []) (B, [2, 3]) (B, [1,3] (A, [1], [3]) (B, [2,3], [1,3]) Rozwiązanie naszego problemu: ● A.groupby('clientId') - dostajemy mapę clientId => lista wyszukiwań ● B.groupby('clientId') - dostajemy mapę clientId => lista decyzji zakupowych ● Wykonujemy cogroupa na powyższych i otrzymujemy mapę ○ clientId => [lista elementów zbioru A], [lista elementów zbioru B] ○ łączymy posortowane listy w posortowną listę ○ iterujemy po niej w poszukiwaniu par (wyszukanie, zakup) w odstępie X godzin
  • 45. Sprytny join - łączenie danych partycjonowanych dziennie 30/06/201829/06/201828/06/201801/06/2018 Dataset A: Frazy wyszukiwania 30/06/201829/06/201828/06/201801/06/2018 Dataset B: Decyzje zakupowe ● Join na całym miesiącu był bardzo ciężki, wymagał podniesienia całego datasetu, a dane nie mieściły się w pamięci. ● Jeśli dane nie mieszczą się w pamięci, to Spark zwalnia!
  • 46. Sprytne joiny - iteracyjny join for day in days: one_day_dataset_A = fetch_one_day(dataset_A).cache()
  • 47. Sprytne joiny - iteracyjny join for day in days: one_day_dataset_A = fetch_one_day(dataset_A).cache() dailyJoinResult = joinOneDay( one_day_before_dataset_A.union(one_day_dataset_A), fetch_one_day(dataset_B) ).cache()
  • 48. Sprytne joiny - iteracyjny join for day in days: one_day_dataset_A = fetch_one_day(dataset_A).cache() dailyJoinResult = joinOneDay( one_day_before_dataset_A.union(one_day_dataset_A), fetch_one_day(dataset_B) ).cache() result = result.union(dailyJoinResult) one_day_before_dataset_A = one_day_dataset_A
  • 49. Job przed i po I ETAP ● Usunęliśmy niepotrzebnego repartition’a na początku przetwarzań ● Zmniejszyliśmy liczbę executorów z 240 do 100. ● Job nadal wykonywał się w czasie akceptowalnym biznesowo przed zmianami po zmianach Ilość executorów 240 100
  • 50. Job przed i po II ETAP ● Wczytanie danych JSON z zadanym schematem ● Zmiana joinowania na podejście iteracyjne Dane z 10 dni przed zmianami po zmianach Ilość executorów 50 20 Czas wykonania 98 mins 23 sec 24 mins 22 sec YARN MB-seconds 2,768,187,887 229,664,298 YARN Vcore-seconds 270,235 22,418