Ref:
https://dzone.com/articles/optimize-spark-with-distribute-by-cluster-by
Reduce Memory Overhead
Turn on compression for in memory tables "spark.sql.inMemoryColumnarStorage.compressed = true", "spark.sql.inMemoryColumnarStorage.batchSize =10000"
Make sure the compression of in memory tables is enabled, this will allow us to use memory efficiently. Also, without in-memory table compression, the cache command will not be available. The spark.sql.inMemoryColumnarStorage.batchSize controls the size of batches for columnar caching. Larger batch size can improve memory utilization and compression but risks out of memory when caching and requesting a new batch. Setting default 1000 is a good start. Starting Spark 1.2.0 the default values are spark.sql.inMemoryColumnarStorage.compressed=true, spark.sql.inMemoryColumnarStorage.batchSize =10000.
Ref:
https://spark.apache.org/docs/latest/sql-programming-guide.html#caching-data-in-memory If Spark version is older than 1.5, turn on tungsten. "set spark.sql.tungsten.enabled=true"
Tungsten is an optimization done to the memory management in Spark that allows for efficient use of the memory. In short, the tungsten removes many of the objects memory footprint and leave only the actual data in memory. It is the only memory management after spark 1.5, but prior to 1.5 is good idea to turn it on.
Ref:
https://community.hortonworks.com/articles/72502/what-is-tungsten-for-apache-spark.htmlRef:
http://spark.apache.org/releases/spark-release-1-5-0.htmlTurn on the "spark.memory.offHeap.enabled=true", "spark.memory.offHeap.size=1g"
Spark makes use of the Native OS memory (outside of JVM heap) if the spark.memory.offHeap.enabled and spark.memory.offHeap.size are set. The benefit of using Native OS memory is offloading work for garbage collector because it won't collect any memory allocated outside of the JVM heap. Please notice that both JVM heap and Native OS memory compete for the same limited amount of physical memory and therefore when assigning memory through spark.memory.offHeap.size, consider decreasing in similar proportion the spark.executor.memory to avoid out of memory errors.
Ref:
https://spark.apache.org/docs/latest/configuration.html#memory-managementRef:
https://dzone.com/articles/heap-vs-heap-memory-usage
Serialization and Compression
Use Kryo serializer
Serialization plays an important role in the performance of any distributed application. Formats that are slow to serialize objects into, or consume a large number of bytes, will greatly slow down the computation. Often, this will be the first thing you should tune to optimize a Spark application. Spark can also use the Kryo library (version 2) to serialize objects more quickly. Kryo is significantly faster and more compact than Java serialization (often as much as 10x), but does not support all Serializable types and requires you to
register the classes you’ll use in the program in advance for best performance. Set it using "spark.serializer" = "org.apache.spark.serializer.KryoSerializer"
Ref:
https://spark.apache.org/docs/latest/tuning.html#data-serialization Use snappy compression codec
The codec used to compress internal data such as RDD partitions, broadcast variables and shuffle outputs. By default, Spark provides three codecs: lz4, lzf, and snappy. We recommend using snappy based on this comparison:
http://blog.erdemagaoglu.com/post/4605524309/lzo-vs-snappy-vs-lzf-vs-zlib-a-comparison-of "
spark.io.compression.codec"="snappy"
Use ORC format instead of TEXT format
When tables are stored as TEXT format, they occupy much more space in HDFS. After we convert the partition tables to ORC, they have faster response times for large tables.
Performance improvements
Below two experiments: both test different datasets and different settings. We add them both here as evidence of the effect of the different tuning options:
Experiment 1: cache table, compression, broadcast join, optimized partition and offheap memory
Below find the experiments table with the actual times. The environment used for this test has the following parameters:
Hortonworks 2.6
Spark 1.6.3
3 master nodes each with 16 MB of RAM and 4 vCPU (AWS EC2 m4.xlarge)
6 slave node each with 16 MB of RAM and 4 vCPU (AWS EC2 m4.xlarge)
Yarn relevant parameters constants throughout all cases.
yarn.nodemanager.resource.memory-mb = 12288yarn.nodemanager.pmem-check-enabled = trueyarn.scheduler.maximum-allocation-mb = 12288
Spark fixed parameters:
spark.executor.cores=2
spark.dynamicAllocation.enabled=true
spark.dynamicAllocation.initialExecutors=6
spark.dynamicAllocation.maxExecutors=6
spark.dynamicAllocation.minExecutors=6
spark.memory.fraction=0.75
spark.memory.storageFraction=0.75
spark.yarn.executor.memoryOverhead=2048
Three cases were tested: Filter, Aggregation and Join.
Test data:
Table name | Number or rows | Number of columns | Size once loaded in memory (MB) |
largetable | 104,305,369 | 11 | 831 |
lookuptable | 125 | 2 | .007 MB ~ = 7.7 KB |
Performance tests
| | Cache table | Enable compression | Broadcast join | Optimized partition | Offheap memory enabled |
| Query | Yes | No | Yes | No | Yes | No | Yes | No | Yes | No |
Filtering | create table filter_output as select * from largetable where release_year = 2011; | 6.97 | 47.674 | 47.674 | 43.25 | 10.87 | 6.97 | 6.97 | 12.44 | 11.703 | 6.97 |
Aggregation | select genre, stddev(score) from largetable group by genre; | 3.919 | 21.365 | 21.365 | 18.376 | 3.44 | 3.919 | 3.919 | 3.907 | 3.652 | 3.919 |
Join | create table join_out as select a.title, b.score from largetable a inner join lookuptable b on a.genre = b.genre; | 56.88 | 78.347 | 78.347 | 85.919 | 18.42 | 56.88 | 56.88 | 180.97 | 70.977 | 56.88 |
Are tables cached? | | Yes | No | No | No | Yes | Yes | Yes | Yes | Yes | Yes |
spark.sql.inMemoryColumnarStorage.batchSize | | true | true | true | false | true | true | true | true | true | true |
spark.sql.inMemoryColumnarStorage.compressed | | 10000 | 10000 | 10000 | 0 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 |
spark.sql.autoBroadcastJoinThreshold | | 1 | 1 | 1 | 1 | 524288000 | 1 | 1 | 1 | 1 | 1 |
spark.executor.memory | | 8g | 8g | 8g | 8g | 8g | 8g | 8g | 8g | 6g | 8g |
spark.memory.offHeap.size | | false | false | false | false | false | false | false | false | true | false |
spark.memory.offHeap.enabled | | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2g | 0 |
spark.default.parallelism | | 13 | 13 | 13 | 13 | 13 | 13 | 13 | 1 | 13 | 13 |
spark.sql.shuffle.partitions | | 13 | 13 | 13 | 13 | 13 | 13 | 13 | 1 | 13 | 13 |
Total memory (execution & storage) | | 34.4 GB | 34.4 GB | 34.4 GB | 34.4 GB | 34.4 GB | 34.4 GB | 34.4 GB | 34.4 GB | 25.8 GB(in JVM heap) + 12 GB (native OS memory) | 34.4 GB |
Experiment 2: ORC, partition and cache
We ran the following query against two different sized tables and then recorded the amount of time it took to execute: select a , b, c, sum(d) from A where date between 'first date' and 'second date' group by a, b, c. All times below in seconds.
Table name | Table size | Spark execution (not cached) | Spark execution (cached) | Size in HDFS | Size in RDD (once cached) | HDFS partitions | RDD partitions | Format |
A | 5 million | 17.5 | 7.7 | 85.9 MB | 436 MB | 2 | 2 | ORC |
A_part200 | 5 million | 5 | 1.2 | 1.3 GB | 431 MB | 201 | 200 | TEXT |
A_part200_orc | 5 million | 4.5 | 1.5 | 96.1 MB | 432 MB | 201 | 200 | ORC |
B | 400 million | 88 | 24 | 7.9 GB | 33.6 GB | 192 | 905 | ORC |
B_part200 | 400 million | 67 | 21.6 | 99.6 GB | 37.4 GB | 201 | 905 | TEXT |
B_part200_orc | 400 million | 59.4 | 16.6 | 6.6 GB | 37.4 GB | 201 | 200 | ORC |
Conclusions
The caching tables optimization enhances all operations: filtering, aggregation and joining.
Auto broadcasting optimization enhances join performance by reducing the execution time by more than half (from 56 secs to 19 secs).
We see join performance improvements by tuning the number of partitions.
On the other hand we see that turning on the off-heap memory consumption does not translate to tangible improvements.