Este documento discute pontos importantes para a otimização de desempenho das plataformas Hive, Impala e Spark, incluindo a configuração de recursos como memória e núcleos, arquitetura de dados como particionamento e formato, e parâmetros de consulta como tipos de join e hints.
2. Intro
Nesta apresentação serão abordados vários pontos que devem ser tidos em
consideração na arquitectura, configuração e optimização nas plataformas
Hive, Impala e Spark.
4. Recursos
O ajuste das parametrizações do YARN devem ter
em conta o uso de vCores e memória, configurando
os containers, por forma a usar todos os recursos
disponíveis, além daqueles necessários para
sobrecarga e outros serviços.
5. Recursos
Configuração de memória do YARN e MapReduce
Deverão ser tido em conta os seguintes valores:
● RAM (quantidade de memória)
● CORES (Número de núcleos de CPU)
● DISKS (número de discos)
A RAM total disponível para o YARN e MapReduce deve considerar a
Memória Reservada.
A memória reservada é a RAM necessária para os processos do
sistema e outros processos do Hadoop (como o HBase).
Memória Reservada = Reserved for stack memory + Reserved for
HBase Memory (se o HBase estiver no mesmo nó).
Para cálculo da Memória Reservada por nó, pode ser usada como
referência a tabela..
7. Recursos
Calculo do nº máximo de containers por nó
Nº containers = min (2*CORES, 1.8*DISKS, (Total available RAM) /
MIN_CONTAINER_SIZE).
Calculo Java heap memory do Application Master e Container
O heap memory size deverá ser 80% do tamanho do container, i.e.
do yarn.app.mapreduce.am.resource.mb e
yarn.nodemanager.resource.memory-mb respectivamente.
NOTA: DISKS é referente a dfs.data.dirs (número de discos) por máquina e
MIN_CONTAINER_SIZE é o tamanho mínimo do container (na RAM).
8. Recursos
Calculo RAM por containers
RAM por container = max(MIN_CONTAINER_SIZE, (Total Available RAM) / containers).
Exemplo:
Calculo Hive Memory Map Join
hive.auto.convert.join.noconditionaltask.size = 33% do tamanho do container
9. Recursos
Exemplo de configuração de memória do YARN e MapReduce
Por intermédio de uma ferramenta da Hortonworks é possível
Ter uma base de trabalho das parametrizações:
python hdp-configuration-utils.py -c 4 -m 45 -d 2 -k False
Options:
-c CORES, Number of cores on each host
-m MEMORY, Amount of Memory on each host in GB
-d DISKS, Number of disks on each host
-k HBASE, True if HBase is installed, False is not
10. Arquitectura dos Dados
A estrutura dos dados é um ponto fulcral na optimização.
Particionamento dos Dados
Conforme imagem, pese embora seja opcional a
estruturação em partições e/ou buckets - tem um
impacto muito positivo na performance final da query
especialmente quando esta tem filtros (Where = ‘XXX’).
Exemplo:
Create Table sale(id in, amount decimal) Partitioned By
(xdate string, state string);
11. Arquitectura dos Dados
Bucketing
Permite a divisão dos dados (uma partição) em n (ex: 32)
ficheiros por intermédio de uma função de hash sobre a
chave escolhida. Ideal para uma distribuição uniforme dos
dados, tornando os Map-side joins muito mais eficientes
Exemplo:
set hive.enforce.bucketing = true;
Create Table bucketed_table
(Col1 integer, Col2 string, Col3 string, Col4 date, … )
Partitioned By (col4 date)
Clustered By (Col1) INTO 32 BUCKETS
Stored As Parquet TBLPROPERTIES (
parquet.compress'=snappy, 'transactional'='true',
'hive.exec.dynamic.partition'='true',
'hive.exec.dynamic.partition.mode'='nonstrict',
'hive.compactor.initiator.on'='true',
'hive.exec.max.dynamic.partitions.pernode'='150' );
12. Arquitectura dos Dados
Formato dos Dados
Por questões de transversalidade Hive/Impala formato recomendado é o parquet.
O parquet tem uma compressão eficiente e leitura rápida, um dos pontos que
devem ser tidos em consideração é o tamanho do disk block/row group/file size
no HDFS, que deverá ser entre 512 a 1024 MB.
Exemplo:
ALTER SYSTEM SET `store.parquet.block-size` = 1073741824;
Create Table addresses ( name string, street string, city string, state string, zip int )
STORED AS Parquet tblproperties ("parquet.compress"="snappy");
Caso a ingestão seja feita em formato texto é sempre possível efectuar uma
View sobre os dados já existentes.
Exemplo:
Create Table TABLE_PARQUET STORED AS Parquet AS SELECT * FROM TABLE_CSV;
13. Arquitectura da Query
A forma como é efectuada a query deverá ser estar o mais
correlacionada possível com a estrutura dos dados.
JOINS
De forma standardizada o JOIN é efectuado conforme imagem.
Cada JOIN deve ser analisado e por intermédio de hints e
parâmetros é possível alterar a forma como o Hive interpreta o
JOIN e a performance destes respectivamente .
Exemplo:
Select /*+ MAPJOIN(c) */ * FROM orders o JOIN cities c ON (o.city_id = c.id);
14. Arquitectura da Query
JOINS
Com o MAPJOIN permite que a tabela seja carregada para
memória, permitindo que a operação seja feita dentro do
Mapper sem o uso do passo Mapper/Reducer.
Nota: Antes da execução de qualquer query todas as tabelas
envolvidas devem ter as estatísticas actualizadas.
Exemplo:
Tabela:
ANALYZE TABLE <table_name> COMPUTE STATISTICS;
ANALYZE TABLE <table_name> COMPUTE STATISTICS for COLUMNS;
Colunas:
ANALYZE TABLE <table_name> partition (coll="x") COMPUTE STATISTICS for
COLUMNS;
15. Arquitectura da Query
Escolha - JOIN
Conforme a tabela indica existem prós e contras para cada
tipo de JOIN, como tal a escolha deve ser ponderada de acordo com a realidade de cada query e respectivo modelo.
17. Arquitectura da Query
Parametros - JOIN
set hive.exec.dynamic.partition.mode=nonstrict;
set hive.exec.max.dynamic.partitions=10000;
set hive.exec.max.dynamic.partitions.pernode=500;
set hive.auto.convert.join.noconditionaltask = true;
set hive.auto.convert.join.noconditionaltask.size = 10000;
set hive.optimize.correlation=true;
set hive.smbjoin.cache.rows=15000;
set hive.auto.convert.join.noconditionaltask.size=20971520;
set hive.mapjoin.localtask.max.memory.usage = 0.999;
set hive.mapjoin.smalltable.filesize = 30000000;
set hive.mapjoin.lazy.hashtable=false;
set hive.exec.parallel=true;
set hive.auto.convert.join = true;
JOIN Tabelas - com Bucketing
set hive.optimize.bucketmapjoin = true;
set hive.optimize.bucketmapjoin.sortedmerge = true;
set hive.input.format =
org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat;
set hive.optimize.bucketmapjoin = true;
18. Arquitectura da Query
Parametros Genéricos
Vectorization
set hive.vectorized.execution.enabled=true;
set hive.vectorized.execution.reduce.enabled = true;
set hive.exec.dynamic.partition = true;
set hive.exec.dynamic.partition.mode = nonstrict;
set hive.optimize.sort.dynamic.partition=true;
set hive.optimize.reducededuplication=true;
set hive.optimize.index.filter=true;
Cost-Based Optimizer & Statistics
set hive.compute.query.using.stats=true;
set hive.prewarm.enabled=true;
set hive.cbo.enable=true;
set hive.fetch.task.conversion=more;
set hive.fetch.task.conversion.threshold=1073741824;
set hive.stats.autogather=true;
set hive.stats.fetch.column.stats=true;
set hive.stats.fetch.partition.stats=true;
set hive.stats.autographer=true;
20. Recursos
Conforme imagem, conceptualmente o Hive on Spark é mais eficiente que o
MapReduce pois evita os passos intermédios em HDFS e em vez disso usa a
memória. Como tal alguns pontos que devem ser considerados/configurados.
Exemplo:
21. Recursos
Executors
Por forma não existirem desperdícios de recursos é recomendável que
existam fat executors, i.e. menos executores mas cada um com mais
cores, isto permite uma melhor utilização da memória.
Memory
Para além do tamanho do container deverá ser tido em conta o
tamanho do Driver (spark.driver.memory) e respectivo overhead
(spark.yarn.driver.memoryOverhead) que deverá ser pelo menos 20% do
driver.
22. Recursos
Para um Use Case de uma VM Cloudera - 37Gb Memória e 4 Cores
Parametros
set hive.execution.engine=spark;
set yarn.nodemanager.resource.cpu-vcores=2;
set yarn.nodemanager.resource.memory-mb=16384;
set yarn.scheduler.maximum-allocation-vcores=4;
set yarn.scheduler.minimum-allocation-mb=4096;
set yarn.scheduler.maximum-allocation-mb=10192;
set spark.executor.memory=1684354560,
set spark.yarn.executor.memoryOverhead=1000;
set spark.driver.memory=10843545604
set spark.yarn.driver.memoryOverhead=800;
set spark.executor.instances=10;
set spark.executor.cores=2;
set hive.spark.dynamic.partition.pruning.map.join.only=true;
set hive.exec.reducers.bytes.per.reducer=6001088640;
24. Impala
O Impala é semelhante em alguns aspectos com o Hive, i.e. partilha
os metadados e em parte a sintaxe., porém em vez do MapReduce
usa a memória.
Nota: Algumas funções e tipos de dados não funcionam ou são
diferentes entre o Hive e o Impala.
À semelhança do Hive, no Impala é igualmente importante a
escolha da Arquitectura dos Dados assim como a da própria Query.
25. Arquitectura dos Dados
A estrutura dos dados é um ponto de partida para a optimização.
Em termos do modelo devem ser tidos em consideração os seguintes aspectos:
● Particionamento
● Sorting
● Runtime filter and dynamic partition pruning
● Tipos de Dados
● Flat vs Nested
Particionamento
Visto que o Impala e o Hive partilham os metadados, a granularidade deve ser bem ponderada. Baixa de mais afeta o
paralelismo e alta de mais irá criar bottlenecks no HDFS NameNode, Hive Metastore e no Impala catalog service.
26. Arquitectura dos Dados
Sorting
Com a criação da tabela com um Sort inicial, irá funcionar de forma complementar ao particionamento existente pois
pode não só evitar o over-partitioning de uma tabela, assim como torna muito mais eficientes pesquisas de min/max.
27. Arquitectura dos Dados
Runtime filter e dynamic partition pruning
Por intermédio dos filtros permite optimizar JOINs entre uma tabela
grande e outra que seja pequena (conforme imagem).
Após uma primeira execução alguns parâmetros que devem ser
considerados e testados são:
● RUNTIME_FILTER_MODE
● MAX_NUM_RUNTIME_FILTERS
● DISABLE_ROW_RUNTIME_FILTERING
● RUNTIME_FILTER_MAX_SIZE
● RUNTIME_FILTER_MIN_SIZE
● RUNTIME_BLOOM_FILTER_SIZE
28. Arquitectura dos Dados
Tipos de Dados
O tipo de dados escolhidos afecta tanto ao
nível da computação da query como do próprio
espaço ocupado no HDFS.
Sempre que possível devem ser escolhidos
tipo de dados numéricos e o formato Parquet.
Nota: Os tipo de de dados CHAR, TIMESTAMP,
TINYINT não são suportados no Impala.
29. Arquitectura dos Dados
Flat vs Nested
Conforme é possível verificar pela imagem, a
elaboração do modelo de dados tem um grande
impacto na performance. Relações 1-n idealmente
devem ser representadas como nested tables e usar
sempre que possível nested aggregates em vez de
nested data, fazendo com que os JOINs hierárquicos
tenham um custo muito mais baixo.
Exemplo:
30. Arquitectura da Query
A forma como é efectuada a query deverá ser estar o mais
correlacionada possível com a estrutura dos dados.
JOINS
Por forma a se obter os melhores resultados a nível de
performance devem ser tidos em conta os seguintes pontos:
● JOIN que minimiza o uso dos recursos (Broadcast/Partition).
● Identificar os JOINs que beneficiam de Runtime filters.
● A ordem dos JOINs
31. Arquitectura da Query
Um dos aspectos mais importantes na execução de uma query
é ser feito à priori, i.e. a computação de todas as estatísticas das
tabelas envolvidas.
Statistics
Por forma a que o Impala consiga fazer a melhor análise das
estatísticas (Explain Plain) é fulcral ter de antemão a
computação das mesmas.
Exemplo:
COMPUTE STATS SALES_PART_2 PARTITION (Region,Country);
COMPUTE INCREMENTAL STATS SALES_PART_2 PARTITION (Region,Country);
Nota: Incremental Stats apenas aplica-se a tabelas particionadas.
32. Arquitectura da Query
Hints
Os hints são usualmente usados em queries pesadas, quando as estatísticas
não estão devidamente actualizadas ou por outros factores que estejam a
afectar a performance.
Nota: Servem como sugestões e não como directivas. Caso se pretenda forçar
o uso do hint no JOIN deverá ser incluido straight_join no Select.
Hints JOIN
/* +SHUFFLE */ Indica que deve ser usada técnica de particionamento no JOIN.
/* +BROADCAST */ Faz com que o conteúdo do lado direito do JOIN seja
enviado para todos os nós envolvidos no processamento (Default).
Exemplo:
Select straight_join t1.name, t2.id, t3.price from t1 join /* +shuffle */ t2 join /* +broadcast */ t3
on t1.id = t2.id and t2.id = t3.id;
33. Arquitectura da Query
INSERT/SELECT
/* +SHUFFLE */ Apenas um nó irá escrever, reduzindo a fragmentação, i.e. minimizando o número de ficheiros, assim
como o número de buffers em memória (Default).
/* +NOSHUFFLE */ Desactiva o re-particionamento, o Explain Plan poderá ser mais rápido mas também poderá gerar
muito mais ficheiros com poucos dados.
/* +CLUSTERED */ Ordena os dados, por forma a assegurar que apenas uma partição é escrita por cada nó.
/* +NOCLUSTERED */ Não ordena os dados, recomendável para tabelas Kudu (Default).
HDFS
Por forma a influenciar a forma como o Impala processa os Blocos de Dados do HDFS é possível por intermédio:
* +SCHEDULE_CACHE_LOCAL */, /* +SCHEDULE_DISK_LOCAL */ ou /* +SCHEDULE_REMOTE */. Sendo ainda possível
combinar com a hint /* +RANDOM_REPLICA */ que activa um desempate random na escolha dos blocos HDFS.
Exemplo:
Select * /* +SCHEDULE_CACHE_LOCAL,RANDOM_REPLICA */ from table;
34. Arquitectura da Query
HDFS Caching
Com o caching de dados em memória permite que o Impala leia a
informação directamente da memória evitando leituras ao HDFS, trazendo
alguns improvements na performance (Conforme imagem).
O caching pode ser feito de uma tabela inteira ou apenas de uma partição,
sendo ainda possível indicar o nº de hosts (replication = x).
Nota: Esta técnica não é aplicável a tabelas HBase, S3, Kudu, ou Isilon.
Exemplo:
HDFS
hdfs cacheadmin -addPool pool_1 -owner impala -limit 4000000000
IMPALA
alter table SALES_PART partition (year=1960) set cached in 'pool_1' ;
alter table SALES_PART set cached in 'pool_1' with replication = 3;
35. Arquitectura da Query
Parametros Genéricos
set mem_limit=13221225472;
set query_timeout_s = 1000;
set optimize_partition_key_scans = true;
set exec_single_node_rows_threshold = 20000;
set default_join_distribution_mode = 1;
set max_num_runtime_filters = 100;
set rm_initial_mem = 1000000;
set runtime_filter_wait_time_ms = 600;
set strict_mode = true;
set disable_row_runtime_filtering = true;
set sync_ddl = true;
set batch_size=1000;
set num_nodes = '';
set max_scan_range_length=0;
set num_scanner_threads = 0;
37. Recursos
Conceptualmente a configuração dos recursos no Spark são semelhantes ao que foi dito na
secção do Hive on Spark. Porém é necessário ressalvar que caso seja configurado um executor
de 10Gb o Spark irá criar uma JVM conforme imagem, i.e. por forma a prevenir erros de falta
memória (00Ms) apenas 90% é configurável (“Safety” - spark.storage.safetyFraction).
Posteriormente temos efectivamente “Storage” - spark.storage.memoryFraction e “Shuffle” -
spark.shuffle.memoryFraction, que por default são respectivamente 60% e 20%. Concluindo
dos iniciais 10Gb, temos apenas 5.4Gb (JVM*90%*60%).
Internamente existe outra configuração “shuffle” - spark.shuffle.safetyFraction que é 80% da
JVM Heap, i.e. 80%*20% - 1.6Gb. Esta área de memória é usada para agregações pré-reduce
(reduceByKey), caso os resultados não caibam nesta área o Spark irá escrever no HDFS.
A nível dos parâmetros de uma JVM que devem ser também considerados, especialmente se
esta tiver menos de 32Gb é a flag -XX:+UseCompressedOops pois em termos de optimização
faz com que os apontadores passem a ser de 4 Bytes ao invés de 8 Bytes (Default).
38. Recursos
Memória
No Spark o uso da memória pode de uma forma grosseira ser dividido em três blocos:
● Caching (.cache ou .persist)
● Shuffle (buffering de dados entre nós)
● Tasks (.map)
E para cada situação os parâmetros podem e devem ser ajustados.
Exemplo:
Low cluster memory
sparkConf.set("spark.storage.memoryFraction", "0.1")
sparkConf.set("spark.shuffle.memoryFraction", "0.2")
Shuffle intensive jobs
sparkConf.set("spark.storage.memoryFraction", "0.1")
sparkConf.set("spark.shuffle.memoryFraction", "0.8")
Computation intensive jobs
sparkConf.set("spark.storage.memoryFraction", "0.1")
sparkConf.set("spark.shuffle.memoryFraction", "0.6")
39. Recursos
Caching
No Spark é fulcral evitar a escrita no HDFS, para tal o recomendável é o caching data e
conforme imagem existem diversas técnicas e com diversos resultados:
● MEMORY_ONLY - Dados como objectos Java. Em termos de espaço é pouco
eficiente mas é o mais rápido.
● MEMORY_ONLY_SER - Dados serializados, porém existe o custo CPU (serialized/deserialized).
● MEMORY_AND_DISK_SER - Dados serializados, porém tudo o que não caiba em memória será
escrito no HDFS. Uma boa opção caso exista alto processamento envolvido ou joins/reduces
antes de caching.
● DISK_ONLY - Dados em HDFS.
Nota: Por default o serializer é em Java, sendo recomendável o Kryo Serializer
Exemplo:
rdd.cache() ou rdd.persist(storageLevel.MEMORY_ONLY).
sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
sparkConf.set("spark.storage.memoryFraction", "0.6")
sparkConf.set("spark.shuffle.memoryFraction", "0.2")
40. Recursos
GC
À medida que um Job Spark é executado e existe a persistência de objectos em
caching que irá conduzir a um aumento do consumo de memória e por sua vez
despoleta mais tentativas de GC, nestas situações recomenda-se que sejam
libertados todos e quaisquer RDD’s que já não estejam a ser usados.
Um dos factores que também influencia a eficiência do GC é a técnica usada:
● Serial GC (-XX:+UseSerialGC)
● Parallel GC (-XX:+UseParallelGC)
● Parallel Old GC (-XX:+UseParallelOldGC)
● Concurrent Mark Sweep (CMS) Collector (-XX:+UseConcMarkSweepGC)
● G1 Garbage Collector (-XX:+UseG1GC)
Sendo o recomendado o G1, pois é o que lida melhor com a flutuação dos heap
sizes e apresenta melhor taxa de transferência e baixa latência.
Exemplo:
sparkConf.set("spark.executor.extraJavaOptions", "-XX:+UseG1GC")
41. Arquitectura
Particionamento e Paralelismo
No processamento é recomendável a configuração de 2-3 tasks por CPU core. Os RDD’s são
tipicamente particionados com base no número de blocos HDFS ocupados. Por intermédio
dos mecanismos de particionamento permite reduzir o número de shuffles, sendo estes:
● Hash partitioning - As keys que têm o mesmo hash estão no mesmo nó.
● Range partitioning - As keys que se encontram dentro do mesmo range estão no mesmo nó.
Pouco particionamento faz com que não seja dado o uso correcto ao paralelismo(Data
skewing e/ou Less concurrency), já em excesso faz com que as tasks demorem mais do que
é suposto (Task scheduling). Usualmente o número ronda entre 2x o número de cores no
Cluster e 100k partições. Com o correcto particionamento tem igualmente um impacto muito
positivo em operações de shuffling data (cogroup(), groupWith(), join(), groupByKey(),
combineByKey(), reduceByKey(), and lookup()).
42. Arquitectura
Particionamento e Paralelismo
É necessário conhecer bem o Job que estamos a correr. Conforme imagem, por default
os valores dos parâmetros spark.default.parallelism=12 e
spark.sql.shuffle.partitions=200, para o cálculo adequado destes parâmetros é
necessário identificar na timeline do Job se existem tasks com bastante scheduler delay
deverá ser reduzido o shuffle partition.
Nº Partições = Total input dataset size / partition size
Nível de Localidade
A localidade dos dados e respectivo processamento tem um impacto directo
na performance do Job, quanto mais próximos (Dados/Processamento) melhor.
● PROCESS_LOCAL - Execução no mesmo processo que a fonte de dados.
● NODE_LOCAL - Execução no nó da fonte de dados.
● RACK_LOCAL - Execução no rack da fonte de dados.
● ANY - Em qualquer sítio na rede.
Em algumas situações (streaming) poderá ser necessário um ajuste do delay do
cálculo da localidade, i.e. através do parâmetro spark.locality.wait, que por default é
de 3s e poderá ser ajustado para 0-0.5.
43. Arquitectura
Acções/Transformações
As acções/transformações é uma das fases no Spark que requerem
especial atenção. Sempre que possível devem ser usadas
transformações Narrow (ex: map() e filter()) em invez das Wide, pois
conforme se observa na imagem operações como groupByKey(),
reduceByKey() e join() implicam múltiplas partições e shuffling.
Caso sejam necessárias transformações Wide podem ser feitas
algumas optimizações, tais como por exemplo:
● No Join() por forma a minimizar o shuffling deve ser feito broadcasting o
grupo de dados mais pequeno ou hash partitioning de ambos os RDD pelas
respectivas chaves.
● Redução associativa deve ser usado reduceByKey() e evitar groupByKey().
● Junção de dois datasets agrupados e mantém-los agrupados deve ser
usado o cogroup() ao invés do flatMap-join-groupBy().
● Re-particionamento de dados deverá ser feito com
repartitionAndSortWithinPartitions() ao invés de se feio separadamente o re-
particionamento e posteriormente o sort a cada partição pois fará com que
exista comparativamente mais operações de shuffling.
44. Arquitectura
Dataset/Dataframe
Para Jobs que operem com instruções SQL, Dataframes/Datasets é o
caminho a escolher, pois a API contém muitas optimizações por forma a
que estas estruturas operem de forma semelhante a uma tabela do
modelo relacional (ex: Tabelas Hive, HDFS, etc...).
O dataframe porém apresenta algumas limitações que devem ser tidas
em consideração:
● A API do SparkSQL não permite a compilação time type safety.
● Após a transformação de um RDD em DF não é possível a sua regressão.
Exemplo:
45. Arquitectura
Sorting
Tendo como objectivo final a escrita de queries SQL com JOIN’s no SparkSQL
pode ser benéfico fazer o sort à prior aos dados nos dataframes
Exemplo:
Distribute By
SET spark.sql.shuffle.partitions = 2
df.repartition($"key", 2)
Cluster By (Distribute By + Sort By)
SET spark.sql.shuffle.partitions = 2
df .repartition($"key", 2).sortWithinPartitions()
Broadcasting Variables
Tendo em consideração a arquitectura e a localização dos dados deverá ser
considerada a possibilidade da distribuição das variáveis pelos diversos nós,
por exemplo dados de referência.
Exemplo
val b = sc.broadcast(1); b: org.apache.spark.broadcast.Broadcast[Int] = Broadcast(0)
Sort By
df.sortWithinPartitions()
Skewed Data
SET spark.sql.shuffle.partitions = 5
SELECT * FROM df DISTRIBUTE BY key, value
46. SQL
HiveContext
No Spark mais concretamente com a API HiveContext é possível aceder aos
metadados em Hive e assim como proceder a algumas optimizações à
semelhança do que foi feito no Hive on Spark.
Exemplo:
hiveCtx = HiveContext(sc)
hiveCtx.sql("SET yarn.nodemanager.resource.cpu-vcores=4")
hiveCtx.sql("SET yarn.nodemanager.resource.memory-mb=16384")
hiveCtx.sql("SET yarn.scheduler.maximum-allocation-vcores=4")
hiveCtx.sql("SET yarn.scheduler.minimum-allocation-mb=4096")
hiveCtx.sql("SET yarn.scheduler.maximum-allocation-mb=8192")
47. SQL
JOINS
Usualmente são a grande fonte de problemas de performance:
● Shuffle Hash
● Broadcast Hash
● Skew (Data skew)
● Cartesian
● One to Many
● Theta
Shuffle Hash é usado quando existe uma distribuição uniforme das
chaves e assim como o paralelismo usado. Quando não é o caso
conforme imagem, uma possível solução pode passar para Broadcast se
um dos dataframes couber em memória ou por sua vez filtrar os dados.
Broadcast Hash é maior parte das vezes melhor que o Shuffle Hash, para
tal deve-se validar no Explain Plan se o Spark está a usá-lo, uma
optimização que pode e deve ser feita é o caching do dataframe mais
pequeno.
48. SQL
JOINS
Skew é usado quando existe data skew , i.e. uma distribuição não-
uniforme das partições, que num JOIN implica shuffling e é mau para a
performance. É possível melhorar esta situação por intermédio de hints
por forma a que o Spark construa uma explain plan melhor.
Exemplo:
Tabela com Skew
Select /*+ SKEW('orders') */ * From orders, customers Where c_custId = o_custId
Tabela com Skew - Apenas às colunas do JOIN
Select /*+ SKEW('orders', ('o_custId', 'o_storeRegionId')) */ * From orders, customers
Where o_custId = c_custId And o_storeRegionId = c_regionId
Cartesian é muitas vezes um problema camuflado, pois em termos de
output, conforme imagem é algo que pode implodir rapidamente. Um
possível workaround a esta situação é:
● Criação de um RDD com os UID por UID.
● Forçar o Broadcast às linhas da tabela.
● Executar uma UDF dado o UID por UID que procura na tabela e
efectua o respectivo processamento.
49. SQL
JOINS
One to Many Join à semelhança do produto cartesiano também
pode implodir no número de resultados, pelo que deixa de ser um
problema desde que seja usado parquet nas tabelas.
Theta Join é um JOIN peculiar que implica um conjunto de loops
por forma a validar a condição na cláusula ON. Uma forma de
optimizar é a criação de buckets para o matching da condição ON.
Exemplo:
ON(Car_Price >= Boat_Price)
ON(KeyA < KeyB + 1000)
50. SQL
Bucketing
Conforme mencionado no Theta Join e na secção Hive - Arquitectura
de Dados, bucketing é uma solução elegante para JOIN’s, i.e. por
exemplo chaves que são usadas em vários JOIN’s (ID’s, etc…) i.e.:
Bucketing = pre (Shuffle + Sort) nas Chaves dos JOIN’s
Porém, conforme imagens o bucketing do Spark é conceptualmente
diferente e não é compatível com o do Hive.
Em termos de performance é expectável obter ganhos 2x a 5x após
bucketing.
Exemplo:
set spark.sql.sources.bucketing.enabled = true
Dataframe API
df.write.bucketBy(numBuckets,”col1”,...).sortBy(“col1”,...).saveAsTable(“bucketed_Table”
)
SQL Statement
Create Table bucketed_table(col1 int, …) using Parquet
Clustered By (col1, …) Sorted By (col1, …) Into X Buckets