Generic 을 한 눈에 파악하기 위해 간단한 샘플 코드를 작성해 봄

 

public class Test {

	public static void main(String[] args) {
		MyClass<String> TisString = new MyClass<>("Hi!");
		System.out.println(TisString.getItem()); //Hi!
		
		MyClass<Integer> TisInteger = new MyClass<>(123);
		System.out.println(TisInteger.getItem()); //123
	}
}

class MyClass<T>{
//	static T staticItem; //T 는 static 멤버의 타입으로 넣을 수 없음
	T item;
	MyClass(T item){
		this.item = item;
	}
	T getItem() {
		return this.item;
	}
}

 

만약 MyClass 에 extends 를 통해 제한을 걸게된다면?

public class Test {

	public static void main(String[] args) {
		MyClass<String> TisString = new MyClass<>("Hi!");
		System.out.println(TisString.getItem()); //Hi!
		
		//Integer 는 String 의 자손이 아니기 때문에, 아래부터 컴파일 에러가 뜸 
		//MyClass<Integer> TisInteger = new MyClass<>(123);
		//System.out.println(TisInteger.getItem()); //123
	}
}

class MyClass<T extends String>{
	T item;
	MyClass(T item){
		this.item = item;
	}
	T getItem() {
		return this.item;
	}
}

 

 

Generic Class 를 파라미터로 갖는 메소드를 사용하려면 다음과 같이 가능하지만

String, Integer 외에 다른 타입, 이를테면 Character, Long 등의 타입을 받으려면 printT3, printT4.... 를 더 만들어야 함

 

public class Test {

	public static void main(String[] args) {
		MyClass<String> TisString = new MyClass<>("Hi!");
		printT1(TisString);
		
		MyClass<Integer> TisInteger = new MyClass<>(123);
		printT2(TisInteger);
	}
	
	static void printT1(MyClass<String> myClass) {
		System.out.println(myClass.getItem());
	}

	static void printT2(MyClass<Integer> myClass) {
		System.out.println(myClass.getItem());
	}
    
    //필요하면 더 만들어야 함..
}

class MyClass<T>{
	T item;
	MyClass(T item){
		this.item = item;
	}
	T getItem() {
		return this.item;
	}
}

 

따라서 메소드에 Wildcard 를 사용하여 모든 타입을 받아줄 수 있도록 함

여기서 static 의 유무는 Generic 과는 상관 없음

public class Test {

	public static void main(String[] args) {
		MyClass<String> TisString = new MyClass<>("Hi!");
		printT(TisString);
		
		MyClass<Integer> TisInteger = new MyClass<>(123);
		printT(TisInteger);
	}
	
	static void printT(MyClass<?> myClass) {
		System.out.println(myClass.getItem());
	}
}

class MyClass<T>{
	T item;
	MyClass(T item){
		this.item = item;
	}
	T getItem() {
		return this.item;
	}
}

혹은 다음과 같이 Generic Method 를 만들면, Method 에 Generic 을 넣을 수 있다.

public class Test {

	public static void main(String[] args) {
		MyClass<String> TisString = new MyClass<>("Hi!");
		printT(TisString);
		
		MyClass<Integer> TisInteger = new MyClass<>(123);
		printT(TisInteger);
	}
	
	static <T> void printT(MyClass<T> myClass) {
		System.out.println(myClass.getItem());
	}
}

class MyClass<T>{
	T item;
	MyClass(T item){
		this.item = item;
	}
	T getItem() {
		return this.item;
	}
}

 

WildCard 랑 Generic Method 랑 무슨 차인지 모르겠음

WildCard 에만 상한/하한 제한을 줄 수 있다는 것 외에는...

 

Generic 타입은 컴파일러에 의해 모두 제거된다.

예를 들어, Generic 이 아름답게 수놓아진 코드를 컴파일한다고 하자.

이 코드 내의 Generic 은 컴파일러에 의해 String, MyClass, Integer ,,,, 등으로 바뀐다.

그래서 그 결과물(.class 파일) 내에서 Generic (T) 은 없다.

 

 

 

Spark Documentation 문서 번역을 한 번 해보려고 한다.

눈가락번역기의 번역 상태가 많이 좋지 않으니 양해 바람.

 

번역 대상 문서 : https://spark.apache.org/docs/latest/tuning.html

 

Other Considerations
다른 고려할 튜닝 방법들

Level of Parallelism
병렬성 수준

Clusters will not be fully utilized unless you set the level of parallelism for each operation high enough.
작업의 병렬성 수준을 충분히 높게 설정하지 않는다면, 클러스터는 전체가 활용되지 않을 것이다.
Spark automatically sets the number of “map” tasks to run on each file according to its size (though you can control it through optional parameters to SparkContext.textFile, etc),

Spark 는 각 파일의 크기에 따라 자동으로 map 태스크의 수를 설정한다. (사용자가 SparkContext.textFile 파라미터를 설정하여 컨트롤 할 수 있음)

and for distributed “reduce” operations, such as groupByKey and reduceByKey, it uses the largest parent RDD’s number of partitions.

분산 reduce 작업들, 예를 들어 groupByKey, reduceByKey 들은, 가장 큰 상위 RDD 의 파티션 수를 사용한다.

You can pass the level of parallelism as a second argument (see the spark.PairRDDFunctions documentation), or set the config property spark.default.parallelism to change the default.

두 번째 아규먼트(spark.PairRDDFuntions 문서 확인) 로 병렬성 수준을 설정할 수 있다.

혹은 spark.default.parallelism 옵션을 설정하여 default 값을 바꿀 수 있다.

In general, we recommend 2-3 tasks per CPU core in your cluster.
일반적으로, 클러스터에서 CPU 당 두 세개의 태스크를 실행하는 병렬성 수준을 설정하도록 권장한다.

Parallel Listing on Input Paths
인풋이 많을 때 Parallel Listing

Sometimes you may also need to increase directory listing parallelism when job input has large number of directories,

때때로 job input 이 대량의 디렉터리를 갖는다면, 디렉터리 listing parallelism 을 증가할 필요가 있다.

otherwise the process could take a very long time, especially when against object store like S3.

그렇지 않으면 프로세스는 굉장히 오래 걸릴 것이다. 특히 입력값이 s3 같은 저장소에 있다면 더욱.

If your job works on RDD with Hadoop input formats (e.g., via SparkContext.sequenceFile), the parallelism is controlled via spark.hadoop.mapreduce.input.fileinputformat.list-status.num-threads (currently default is 1).

만약 job 이 Hadoop input 포맷(예를 들어 SparkContext.sequenceFile)으로 된 RDD 로 작업하고 있다면, 병렬성은 spark.hadoop.mapreduce.input.fileinputformat.list-status.num-threads 을 통해 조절된다.(현재 기본 1)

For Spark SQL with file-based data sources, you can tune spark.sql.sources.parallelPartitionDiscovery.threshold and spark.sql.sources.parallelPartitionDiscovery.parallelism to improve listing parallelism.

file 기반의 데이터 소스로 작업하는 Spark SQL의 경우, spark.sql.sources.parallelPartitionDiscovery.threshold and spark.sql.sources.parallelPartitionDiscovery.parallelism 옵션값을 listing 병렬성을 개선하도록 설정하라.....

Please refer to Spark SQL performance tuning guide for more details.
더 자세한 사항은 이 문서에 있다.

Memory Usage of Reduce Tasks
Reduce Task 의 메모리 사용률

Sometimes, you will get an OutOfMemoryError not because your RDDs don’t fit in memory, but because the working set of one of your tasks, such as one of the reduce tasks in groupByKey, was too large.

때때로, RDD 크기가 memory 에 딱 맞지 않음에도 불구하고 OOM 에러를 볼 수 있다.

이것은 task(예를 들어 groupByKey 를 실행하는 reduce task) 중 하나의 워킹셋이 너무 크기 때문에 발생한 것이다.

Spark’s shuffle operations (sortByKey, groupByKey, reduceByKey, join, etc) build a hash table within each task to perform the grouping, which can often be large.

Spark 의 셔플 동작들은, 그룹핑을 수행하기 위해 각 task 내부적으로 hash table 을 구성하는 데, 그것이 종종 커질 수 있다.

The simplest fix here is to increase the level of parallelism, so that each task’s input set is smaller.

이 이슈를 해결할 가장 간단한 방법은 병렬성 수준을 높여 각 task 의 input set 크기를 줄이는 것이다.

Spark can efficiently support tasks as short as 200 ms, because it reuses one executor JVM across many tasks and it has a low task launching cost,

Spark 는 200ms 만큼 짧게 동작하는 task 들도 효율적으로 서포트 할 수 있는데,

이는 전체 task 에 걸쳐 하나의 executor JVM이 재사용되고 그것이 적은 task 실행 비용을 갖기 때문에 가능하다.

so you can safely increase the level of parallelism to more than the number of cores in your clusters.
따라서 클러스터 내 코어 수 보다 병렬성 수준을 크게 올려도 안전하다.

Broadcasting Large Variables
큰 변수들 브로드캐스트하기

Using the broadcast functionality available in SparkContext can greatly reduce the size of each serialized task, and the cost of launching a job over a cluster.

SparkContext 에서 사용 가능한 브로드캐스트 함수를 사용하는 것은 각 직렬화 task 수를 확연히 줄일 수 있고, 클러스터 위에 실행되는 job 의 비용도 줄일 수 있다.

If your tasks use any large object from the driver program inside of them (e.g. a static lookup table), consider turning it into a broadcast variable.

만약 task들이 task 내부적으로 driver 프로그램으로부터 온 큰 객체를 사용한다면 브로드캐스트 변수를 사용하여 튜닝하는 것을 고려하라.

Spark prints the serialized size of each task on the master, so you can look at that to decide whether your tasks are too large;

Spark 는 동작하는 각 task의 직렬화된 크기를 마스터에 출력하는데, 이걸 보고 task 가 너무 큰지 아닌지 결정할 수 있다. 

in general, tasks larger than about 20 KiB are probably worth optimizing.
일반적으로, 20kb 보다 큰 task 들은 최적화 해볼 만 하다.

Data Locality
데이터 지역성

Data locality can have a major impact on the performance of Spark jobs.

데이터 지역성은 Spark job 성능에 영향을 주는 주요 요인이다.

If data and the code that operates on it are together, then computation tends to be fast.

데이터와 코드가 함께 존재한다면, 계산은 굉장히 빠르다.

But if code and data are separated, one must move to the other.

그러나 코드와 데이터가 나뉘어져 있다면, 계산을 위해서 둘 중 하나가 움직여야 한다.

Typically, it is faster to ship serialized code from place to place than a chunk of data because code size is much smaller than data.

일반적으로, 데이터 자체를 옮기는 것 보다 직렬화 된 코드를 다른 곳으로 옮기는 것이 더 빠른데, 왜냐하면 코드의 사이즈가 데이터보다 더 작기 때문.

Spark builds its scheduling around this general principle of data locality.

Spark 는 데이터 지역성 원칙에 기반하여 자신의 스케줄링 계획을 세운다.

Data locality is how close data is to the code processing it.

데이터 지역성은 데이터와 데이터를 처리하는 코드가 얼마가 가까운지에 대한 것이다.

There are several levels of locality based on the data’s current location.

현재 데이터 위치에 기반한 지역성 수준이 몇 가지 있다. 

In order from closest to farthest:

가장 가까운 곳에서부터 가장 먼 곳 순으로:

  • PROCESS_LOCAL : data is in the same JVM as the running code. This is the best locality possible.
    PROCESS_LOCAL : 데이터와 실행 코드가 같은 JVM 안에 있다. 이것은 실현가능한 최고의 지역성이다.
  • NODE_LOCAL : data is on the same node.
    NODE_LOCAL : 데이터는 같은 노드에 있다.
    Examples might be in HDFS on the same node, or in another executor on the same node.
    예제는 같은 노드 위의 HDFS 안에 있거나, 같은 노드 위 다른 executor 에 있다.
    This is a little slower than PROCESS_LOCAL because the data has to travel between processes.
    이것은 PROCESS_LOCAL 보다 살짝 더 느린데, 왜냐면 데이터가 process들 사이를 이동해야 하기 때문이다.
  • NO_PREF : data is accessed equally quickly from anywhere and has no locality preference.
    NO_PREF : 데이터는 어느 곳에서든 동일하게 빠르게 접근 가능한 곳에 있다. 이 경우 지역성 특성은 없다.
  • RACK_LOCAL : data is on the same rack of servers.
    RACK_LOCAL : 데이터는 서버의 같은 rack 에 있다.
    Data is on a different server on the same rack so needs to be sent over the network, typically through a single switch.
    데이터가 같은 랙의 다른 서버에 존재해서, 네트워크를 통해 전송될 필요가 있다. 일반적으로 싱글 스위치를 통해 전송된다.
  • ANY : data is elsewhere on the network and not in the same rack.
    ANY : data 가 같은 랙이 아니지만 네트워크로는 연결된 어느 곳에 있다.

Spark prefers to schedule all tasks at the best locality level, but this is not always possible.
Spark 는 모든 task 들이 가장 최선의 지역성 수준으로 스케줄링 되길 선호하지만, 항상 그것을 만족하지는 않는다.
In situations where there is no unprocessed data on any idle executor, Spark switches to lower locality levels.

어느 놀고있는 executor 위에 처리되지 않은 데이터가 없는 상황에서, Spark 는 지역성 수준을 낮추도록 전환한다. 

There are two options:

두 가지 옵션이 있다.

a) wait until a busy CPU frees up to start a task on data on the same server, or

a) 바쁜 CPU 가, 같은 서버 위의 데이터로 task 를 시작할 수 있을 때까지 기다린다 혹은

b) immediately start a new task in a farther away place that requires moving data there.

b) 더 멀리 떨어진 곳에 데이터를 이동시키고 즉시 새로운 task 를 시작한다.

What Spark typically does is wait a bit in the hopes that a busy CPU frees up.

Spark 가 주로 선택하는 것은 바쁜 CPU 가 자유로워질 때 까지 기다리는 것이다.

Once that timeout expires, it starts moving the data from far away to the free CPU.

타입아웃이 만료되면, Spark 는 가용한 CPU 가 있는 더 먼 곳으로부터 데이터를 옮기기 시작한다.

The wait timeout for fallback between each level can be configured individually or all together in one parameter;

각 지역성 수준 간 fallback 을 위한 wait 타임아웃은 독립적으로 혹은 하나의 파라미터로 모두 함께 설정 가능하다.

see the spark.locality parameters on the configuration page for details.

자세한 사항은 configuration 페이지에 있는 spark.locality 파라미터 확인.

You should increase these settings if your tasks are long and see poor locality, but the default usually works well.
만약 task 가 너무 오래 걸리거나 지역성이 처참하다면, 이 세팅값들을 올려야 한다. 하지만 기본값으로도 충분히 잘 동작 할 것이다.

 

Summary
정리

This has been a short guide to point out the main concerns you should know about when tuning a Spark application – most importantly, data serialization and memory tuning.

이것은 사용자가 Spark application 을 튜닝할 때 알아야 할 주요 요인들을 설명하는 짧은 가이드이다.

가장 중요한 것은 데이터 직렬화와 메모리 튜닝이다.

For most programs, switching to Kryo serialization and persisting data in serialized form will solve most common performance issues.

대부분의 프로그램에서 Kryo 직렬화 사용과, 캐싱할 때 직렬화하는 것은 대부분의 성능 이슈를 해결해 줄 것이다.

 

'English' 카테고리의 다른 글

Study English 23.08.15  (0) 2023.09.03
Study English 23.08.14  (0) 2023.08.30
Study English 23.08.12 Tuning Spark 번역3  (0) 2023.08.19
Study English 23.08.11 Tuning Spark 번역2  (0) 2023.08.19
Study English 23.08.10 Tuning Spark 번역1  (0) 2023.08.19

 

 

Spark Documentation 문서 번역을 한 번 해보려고 한다.

눈가락번역기의 번역 상태가 많이 좋지 않으니 양해 바람.

 

번역 대상 문서 : https://spark.apache.org/docs/latest/tuning.html

 

Garbage Collection Tuning
Garbage Collection 튜닝하기

JVM garbage collection can be a problem when you have large “churn” in terms of the RDDs stored by your program.

만약 프로그램에 의해 RDD 가 저장되어  생기는 "churn" 이 많이 생긴다면, JVM garbege collection 은 문제가 될 수 있다.

(It is usually not a problem in programs that just read an RDD once and then run many operations on it.)

(RDD 를 한 번 읽고 처리를 하는 프로그램에서는 보통 문제가 되지 않는다.)

When Java needs to evict old objects to make room for new ones, it will need to trace through all your Java objects and find the unused ones.

Java 가 새로운 객체를 위해 공간을 만들려고 오래된 객체를 지울 필요가 있을 때, 사용자의 모든 Java 객체를 추적하고 사용하지 않는 객체를 찾을 필요가 있을 것이다.

The main point to remember here is that the cost of garbage collection is proportional to the number of Java objects, so using data structures with fewer objects (e.g. an array of Ints instead of a LinkedList) greatly lowers this cost.

여기서 기억해야 할 주요한 점은, GC 비용은 JAVA 객체 수에 따라 달라진다는 것이다. 그래서 적은 수의 객체를 갖는 데이터 구조(예를 들어 LinkedList 대신 Int 배열)를 사용하면 비용을 낮출 수 있다.

An even better method is to persist objects in serialized form, as described above: now there will be only one object (a byte array) per RDD partition.

더 나은 방법은, 위에서 설명했듯이 객체를 직렬화 하는 것이다: 지금 RDD 파티션 당 오직 하나의 객체만 있을 수 있다.

Before trying other techniques, the first thing to try if GC is a problem is to use serialized caching.

다른 기술을 시도하기 전, GC 가 문제인 상황에서 첫째로 시도해야 할 것은 캐싱을 직렬화 하는 것이다.

GC can also be a problem due to interference between your tasks’ working memory (the amount of space needed to run the task) and the RDDs cached on your nodes.

GC 는 사용자 task의 워킹 메모리(task 를 실행하기 위해 필요한 메모리공간)와 RDD 캐시 간 간섭때문에 문제가 될 수 있다. (interference 가 왜 들어가있는거지?)

We will discuss how to control the space allocated to the RDD cache to mitigate this.

이를 완화하기 위해, RDD 캐시에 할당된 공간을 어떻게 컨트롤 할 지 의논해보자.

 

Measuring the Impact of GC
GC 영향 측정하기

The first step in GC tuning is to collect statistics on how frequently garbage collection occurs and the amount of time spent GC.

GC 튜닝의 첫번째 절차는, GC 가 얼마나 빈번하게 발생하는지, 그리고 GC 로 인해 소요되는 시간의 양이 얼마인지 통계치를 수집하는 것이다.

This can be done by adding -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps to the Java options.

JAVA 옵션에 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 을 추가하여 측정할 수 있다.

(See the configuration guide for info on passing Java options to Spark jobs.)

(Spark jobs 에 Java 옵션을 전달하는 정보를 담은 configuration guide 문서를 확인하라)

Next time your Spark job is run, you will see messages printed in the worker’s logs each time a garbage collection occurs.

그 후 Spark job 을 실행하면, 워커노드의 로그에 GC 발동 및 걸린 시간에 대한 메세지가 출력되는 것을 볼 수 있다.

Note these logs will be on your cluster’s worker nodes (in the stdout files in their work directories), not on your driver program.

이 로그들은 클러스터의 워커 노드에 생성 되며(워커 directories 에 stdout 파일로), driver 프로그램 노드에는 로그가 생기지 않는다.

 

Advanced GC Tuning
좀 더 전문적으로 GC 튜닝하기

To further tune garbage collection, we first need to understand some basic information about memory management in the JVM:
조금 더 GC 튜닝을 하기 위해, JVM 에서 메모리 관리에 대한 기본 정보를 어느정도 이해하고 있어야 한다.

  • Java Heap space is divided in to two regions Young and Old.
    Java Heap 공간은 두 가지 영역인 Young, Old 로 나뉜다.
    The Young generation is meant to hold short-lived objects while the Old generation is intended for objects with longer lifetimes.
    Young 세대는 짧게 살았던 객체들이 보유된 것을 의미하고, Old 세대는 긴 시간을 산 객체들을 위해 사용된다.
  • The Young generation is further divided into three regions [Eden, Survivor1, Survivor2].
    Young 세대는 세 가지 영역으로 나뉜다. Eden, Surv1, Surv2
  • A simplified description of the garbage collection procedure:
    GC 작업 절차에 대한 간단한 설명:
    When Eden is full, a minor GC is run on Eden and objects that are alive from Eden and Survivor1 are copied to Survivor2.
    Eden 이 꽉 차면, minor GC 가 Eden 대상으로 동작함. 그러면 Eden, Surv1 내 객체들 중 살아있는 객체들은 Surv 2로 복사된다.
    The Survivor regions are swapped.
    Surv 공간들은 교환된다.
    If an object is old enough or Survivor2 is full, it is moved to Old.
    만약 객체가 충분히 오래 살았거나 Surv2 가 꽉 차면, 그 객체들은 Old 로 옮겨진다.
    Finally, when Old is close to full, a full GC is invoked.
    마지막으로, Old 가 꽉차기 직전에 FullGC 가 발동된다.

The goal of GC tuning in Spark is to ensure that only long-lived RDDs are stored in the Old generation and that the Young generation is sufficiently sized to store short-lived objects.

Spark 에서 GC 튜닝의 목적은 오래 산 RDD 들이 old 세대에 저장하는 것과, Young 세대는 짧게 사는 객체들을 저장할 충분한 크기를 갖는 것이다.

This will help avoid full GCs to collect temporary objects created during task execution.

task 를 실행하는 동안 생성되는 임시 객체를 수집하기 위해 full GC 를 피할 수 있도록 도와준다.

Some steps which may be useful are:
유용할 수 있는 몇 가지 스텝은 다음과 같다.

  • Check if there are too many garbage collections by collecting GC stats.
    GC 수치 수집에 의해 GC 가 너무 많이 발동되는지 확인하라.
    If a full GC is invoked multiple times before a task completes, it means that there isn’t enough memory available for executing tasks.
    만약 task 가 마무리 되기 전에 full GC 가 여러번 발동된다면, 이것은 task 를 실행하기에 충분한 메모리가 없다는 것을 의미한다.
  • If there are too many minor collections but not many major GCs, allocating more memory for Eden would help.
    Major GC 보다 minor GC 가 더 많이 발생한다면, Eden 영역 크기를 더 주는 것이 도움이 될 수 있다.
    You can set the size of the Eden to be an over-estimate of how much memory each task will need.
    각 태스크가 얼마나 많은 메모리를 필요로 할지 과하게 추정해서 Eden 영역 사이즈를 설정하라.
    If the size of Eden is determined to be E, then you can set the size of the Young generation using the option -Xmn=4/3*E.
    만약 Eden 크기가 E 로 결정되었다면, -Xmn=4/3*E옵션을사용해서 Young 영역의 사이즈를 설정할 수 있다.
    (The scaling up by 4/3 is to account for space used by survivor regions as well.)
    (4/3 으로 크기를 올리는 것은 Surv 영역에 의해 사용되는 공간 또한 고려한다.)
  • In the GC stats that are printed, if the OldGen is close to being full, reduce the amount of memory used for caching by lowering spark.memory.fraction;
    출력된 GC 수치에서, OldGen 영역이 거의 꽉 찼다면, spark.memory.fraction 을 줆여서 캐싱을 위한 메모리 양을 줄여야한다.
    it is better to cache fewer objects than to slow down task execution.
    작업 속도를 늦추는 것 보다는, 캐싱 객체를 적게 만드는 편이 더 좋다.
    Alternatively, consider decreasing the size of the Young generation.
    대안으로 Young 영역의 크기를 줄이는 것도 고려해봐라.
    This means lowering -Xmn if you’ve set it as above.
    이것은, 위에서 말한대로 -Xmn 옵션으로 크기를 줄이는 걸 의미한다.
    If not, try changing the value of the JVM’s NewRatio parameter.
    혹은, JVM 의 NewRatio 파라미터 값을 바꾸는 걸 시도해봐라.
    Many JVMs default this to 2, meaning that the Old generation occupies 2/3 of the heap.
    많은 JVM 의 기본 값은 2로 되어있는데, 이것은 old 영역이 힙메모리의 2/3 을 차지한다는 것을 의미한다.
    It should be large enough such that this fraction exceeds spark.memory.fraction.
    이것은 spark.memory.fraction 을 넘는 비율이 되도록 충분히 커야 한다.
  • Try the G1GC garbage collector with -XX:+UseG1GC.
    -XX:+UseG1GC 옵션으로 G1GC 사용을 하라.
    It can improve performance in some situations where garbage collection is a bottleneck.
    GC 가 보틀넥이 되는 몇몇 상황에서 성능을 개선시켜준다.
    Note that with large executor heap sizes, it may be important to increase the G1 region size with -XX:G1HeapRegionSize.
    큰 executor 힙 사이즈를 사용한다면, -XX:G1HeapRegionSize 옵션으로 G1 영역 크기를 증가하는 것이 중요할 지 모른다.
  • As an example, if your task is reading data from HDFS, the amount of memory used by the task can be estimated using the size of the data block read from HDFS.
    예를 들어 task 가 HDFS 로부터 데이터를 읽는 중이라면, task 에 의해 사용되는 메모리의 크기는 HDFS 로부터 읽는 데이터 블럭의 크기를 사용하여 추정된다.
    Note that the size of a decompressed block is often 2 or 3 times the size of the block.
    종종 압축을 푼 블럭의 크기는 블럭 크기의 두 세배이다.
    So if we wish to have 3 or 4 tasks’ worth of working space, and the HDFS block size is 128 MiB, we can estimate the size of Eden to be 4*3*128MiB.
    그래서 서너개의 task 가 동작중이고 HDFS 크기가 128mb 라면, Eden 의 사이즈는 4(task 수) x 3(block 수) x 128(block 크기) 라고 추정할 수 있다.
  • Monitor how the frequency and time taken by garbage collection changes with the new settings.
    새로운 세팅을 적용 한 이후, GC 가 얼마나 빈번하게 일어나는지 또 얼마나 걸리는지 모니터링하라.

Our experience suggests that the effect of GC tuning depends on your application and the amount of memory available.
GC 튜닝의 영향은 사용자 application 과 가용한 메모리 크기에 따라 달라진다.
There are many more tuning options described online, but at a high level, managing how frequently full GC takes place can help in reducing the overhead.
온라인에는 더 많은 튜닝 옵션들이 설명되어있지만, 전반적으로 볼 때,  full GC 가 얼마나 빈번히 일어나는지 관리하는 것은 오버헤드를 줄이는 데 도움이 된다.

GC tuning flags for executors can be specified by setting spark.executor.defaultJavaOptions or spark.executor.extraJavaOptions in a job’s configuration.

executor 를 위한 GC 튜닝 flags 는, job 의 설정에서 spark.executor.defaultJavaOptions 혹은 spark.executor.extraJavaOptions 를 세팅하는 것으로 명시할 수 있다.

 

 

 

 

Spark Documentation 문서 번역을 한 번 해보려고 한다.

눈가락번역기의 번역 상태가 많이 좋지 않으니 양해 바람.

 

번역 대상 문서 : https://spark.apache.org/docs/latest/tuning.html

 

 

Memory Management Overview
메모리 관리 개요

Memory usage in Spark largely falls under one of two categories: execution and storage.

Spark 에서 메모리 사용은 넓게 다음 두 카테고리 중 하나에 속한다. 실행execution과 저장storage

Execution memory refers to that used for computation in shuffles, joins, sorts and aggregations,

"실행 메모리"는 셔플이나 조인, 정렬, 집계 계산을 위해 사용되는 메모리다.

while storage memory refers to that used for caching and propagating internal data across the cluster.

반면 "저장 메모리"는 클러스터 전체에 내부 데이터를 캐싱하고 전파하는 데 사용되는 메모리다. 

In Spark, execution and storage share a unified region (M).

Spark 에서, 실행과 저장은 하나의 통합된 공간을 공유한다 (위 그림의 M 부분)

When no execution memory is used, storage can acquire all the available memory and vice versa.

만약 실행 메모리가 사용되지 않는다면, 저장 메모리는 가용한 메모리를 모두 얻을 수 있으며, 그 반대도 마찬가지다.

Execution may evict storage if necessary, but only until total storage memory usage falls under a certain threshold (R).

실행은 필요하다면 저장을 밀어낼 수 있지만, 전체 '저장 메모리' 사용률이 특정 threshold 를 넘긴 상태여야 저장을 밀어낼 수 있다.(위 그림의 R 부분)

In other words, R describes a subregion within M where cached blocks are never evicted.

다른 말로 하면, R 은 M 의 하위 공간이며, 캐싱된 데이터가 절대 밀리거나 사라지지 않는 공간이다.

Storage may not evict execution due to complexities in implementation.

저장공간은 구현의 복잡성 때문에 실행 공간을 밀어내지 않는다.

This design ensures several desirable properties.

이러한 디자인은 몇 가지 특징들을 가져온다.

First, applications that do not use caching can use the entire space for execution, obviating unnecessary disk spills.

첫째, 캐싱 기능을 사용하지 않는 applications 는 실행을 위해 전체 메모리 공간을 사용할 수 있고, 그에 따라 불필요한 디스크 스필을 방지할 수 있다.

Second, applications that do use caching can reserve a minimum storage space (R) where their data blocks are immune to being evicted.

두번째, 캐싱 기능을 사용하는 applications 는, 캐싱 데이터가 메모리에서 밀리거나 사라질 걱정 없는 최소 저장공간을 R 자리에 예약해둘 수 있다.

Lastly, this approach provides reasonable out-of-the-box performance for a variety of workloads without requiring user expertise of how memory is divided internally.

마지막으로 이러한 접근법은, 메모리가 내부적으로 어떻게 구분되는지 알아야 하는 전문 지식의 유무와 상관없이, 다양한 작업부하에 대해 이미 합리적인 퍼포먼스를 제공한다.

여기서 말하는 out-of-the-box performance 는, 사용자의 추가 설정 없는 순정 상태 메모리의 성능을 말한다고 이해하면 편함.

즉, 사용자가 전문 지식을 갖추고 메모리 이곳 저곳 옵션을 준 게 아님에도 불구하고, 처음부터 기본 성능 자체가 합리적이라는 의미.

Although there are two relevant configurations, the typical user should not need to adjust them as the default values are applicable to most workloads:
관련된 설정값이 두 개가 있지만, 대부분의 유저는 이 옵션값들을 업데이트없이 그냥 기본값으로 두고 사용하는데 그럼에도 대부분의 작업에서 적용될 수 있다.

  • spark.memory.fraction expresses the size of M as a fraction of the (JVM heap space - 300MiB) (default 0.6).
    spark.memory.fraction 는 M의 크기를 JVM 힙 스페이스 - 300mb 의 비율로 나타낸다. 기본 0.6
    The rest of the space (40%) is reserved for user data structures, internal metadata in Spark, and safeguarding against OOM errors in the case of sparse and unusually large records.
    남은 공간(0.6에서 남은 공간은 0.4, 즉 40%) 사용자 데이터 구조, Spark 내부 메타데이터, 그리고 혹시모를 희소하고 비정상적으로 큰 데이터에 의한 OOM 에러 방지를 위해 예약된다.
  • spark.memory.storageFraction expresses the size of R as a fraction of M (default 0.5). 
    spark.memory.storageFraction 은 R 의 크기를 M의 비율로 나타낸다. 기본 0.5
    R is the storage space within M where cached blocks immune to being evicted by execution.
    R 은 M 내에 저장데이터를 저장하기 위한 공간이며, 이 공간에서는 실행에 의해 캐시 데이터가 밀려사라지지 않는다.

The value of spark.memory.fraction should be set in order to fit this amount of heap space comfortably within the JVM’s old or “tenured” generation.

spark.memory.fraction 값은 JVM 의 old 나 tenured 세대 데이터 내의 힙스페이스 양을 맞추기 위해 설정되어야 한다.(???)

See the discussion of advanced GC tuning below for details.
아래 GC 튜닝에 대해 더 자세히 의논한 부분을 확인하라.

Determining Memory Consumption
메모리 소비 알아내기

The best way to size the amount of memory consumption a dataset will require is to create an RDD, put it into cache, and look at the “Storage” page in the web UI.

dataset 이 필요로하는 메모리 소비양을 측정할 가장 좋은 방법은 RDD 를 만들어서 캐싱한 후 WebUI 에서 "Storage" 페이지 부분을 확인하는 것이다.

The page will tell you how much memory the RDD is occupying.

이 페이지는 RDD 메모리가 얼마나 많은 공간을 차지하는지 보여줄것이다.

To estimate the memory consumption of a particular object, use SizeEstimator’s estimate method.

특정 객체의 메모리 소비양을 추정하려면, SizeEstimator 의 estimate 메소드를 사용하라.

This is useful for experimenting with different data layouts to trim memory usage, as well as determining the amount of space a broadcast variable will occupy on each executor heap.

이것은 메모리 사용량을 줄이기 위해 서로다른 데이터 레이아웃들을 실험하는 데 유용하고, 또한 각 executor heap 에서 브로드캐스트 변수가 차지하게 될 공간을 파악하는 데 유용하다.

Tuning Data Structures
데이터 구조 튜닝하기

The first way to reduce memory consumption is to avoid the Java features that add overhead, such as pointer-based data structures and wrapper objects.

메모리 소비를 줄이는 첫번째 방법은, pointer 기반의 데이터 구조와 래퍼 객체 같이 오버헤드가 있는 Java feature 사용을 피하는 것이다.

There are several ways to do this:
이를 위한 몇 가지 방법이 있다.

  1. Design your data structures to prefer arrays of objects, and primitive types, instead of the standard Java or Scala collection classes (e.g. HashMap).
    HashMap 같은 일반적인 Java/Scala 컬렉션 클래스 대신, 객체 배열이나 원시타입을 선호하는 데이터 구조를 디자인하라.
    The fastutil library provides convenient collection classes for primitive types that are compatible with the Java standard library.
    fastutil lib 는, Java 기본 lib 와 견줄만큼 편리한 원시타입 컬렉션 클래스들을 제공한다.
  2. Avoid nested structures with a lot of small objects and pointers when possible.
    가능하다면 많은 수의 작은 객체와 포인터들을 갖는 중첩 구조를 갖지 않도록 하라.
  3. Consider using numeric IDs or enumeration objects instead of strings for keys.
    문자열 형태의 key 값 대신, 숫자형태의 ID 혹은 열거형 객체 사용을 고려하라.
  4. If you have less than 32 GiB of RAM, set the JVM flag -XX:+UseCompressedOops to make pointers be four bytes instead of eight.
    만약 ram 이 32 gb 보다 작다면, 8바이트 포인터 대신 4바이트 포인터를 사용하도록 JVM flag -XX:+UseCompressedOops을 설정하라.
    You can add these options in spark-env.sh.
    이 옵션은 spark-env.sh 에 추가할 수 있다.

Serialized RDD Storage
직렬화하여 RDD 저장

When your objects are still too large to efficiently store despite this tuning, a much simpler way to reduce memory usage is to store them in serialized form, using the serialized StorageLevels in the RDD persistence API, such as MEMORY_ONLY_SER.

이러한 튜닝에도 불구하고, 효율적으로 저장하기에 객체 크기가 아직 너무 크다면, 메모리 사용률을 줄이는 아주 간단한 방법은 객체를 직렬화하여 저장하는 것이다. RDD persistence API 문서에 따르면, MEMORY_ONLY_SER 같은 직렬화StorageLevel 을 사용하면 된다고 한다.

Spark will then store each RDD partition as one large byte array.

그러면 Spark 는 각 RDD 파티션을 하나의 큰 바이트 배열로 저장 할 것이다.

The only downside of storing data in serialized form is slower access times, due to having to deserialize each object on the fly.

직렬화하여 데이터를 저장하면 생기는 단 하나의 불리한 점은 접근 시간이 느려진다는 것이다. 왜냐면 각 객체를 그때 그때 역직렬화해야 하기 때문.

We highly recommend using Kryo if you want to cache data in serialized form, as it leads to much smaller sizes than Java serialization (and certainly than raw Java objects).

캐싱된 데이터를 직렬화 하고싶다면, Kryo 를 추천한다. Kryo 로 직렬화한 결과는 Java Serialization, 그리고 원시 Java 객체보다 더 작은 크기를 갖게 되기 때문이다.

 

 

 

 

 

 

 

 

 

Spark Documentation 문서 번역을 한 번 해보려고 한다.

눈가락번역기의 번역 상태가 많이 좋지 않으니 양해 바람.

 

 

 

번역 대상 문서 : https://spark.apache.org/docs/latest/tuning.html

 

Tuning Spark
Spark 튜닝하기

Because of the in-memory nature of most Spark computations, Spark programs can be bottlenecked by any resource in the cluster: CPU, network bandwidth, or memory.
Spark 가 실행하는 대부분 계산이 memory 내에서 일어나기 때문에, Spark 프로그램에서 클러스터가 갖는 자원의 부족은 보틀넥이 될 수 있다. 여기서 말하는 자원이란 CPU나 네트워크 대역폭, 메모리 등을 말한다.
Most often, if the data fits in memory, the bottleneck is network bandwidth, but sometimes, you also need to do some tuning, such as storing RDDs in serialized form, to decrease memory usage.
대부분의 경우, data 가 memory 에 알맞게 들어가는 상황에서는, 네트워크 대역폭이 보틀넥이 된다.

하지만 때때로 RDD 를 직렬화된 포맷으로 저장하거나 memory 사용률 감소하게 만드는 등의 튜닝을 해야 할 필요가 있다.
This guide will cover two main topics: 

이 가이드는 두 가지 주요 주제에 대해 논한다.

data serialization, which is crucial for good network performance and can also reduce memory use, and memory tuning.

1. 데이터 직렬화 : 네트워크 퍼포먼트에 크게 영향을 주는 요인이며 메모리 사용률도 낮출 수 있다.

2. 메모리 튜닝

We also sketch several smaller topics.

Data Serialization
데이터 직렬화

Serialization plays an important role in the performance of any distributed application.

직렬화는 어느 분산 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 application 의 최적화를 위해 튜닝해야 할 가장 첫번째 부분이다.

Spark aims to strike a balance between convenience (allowing you to work with any Java type in your operations) and performance.

Spark는 퍼포먼스와 편리성(사용자가 어떤 자바 타입을 사용해도 괜찮도록 하는 등) 사이의 균형을 맞추려고 노력한다.

It provides two serialization libraries:
이것은 두 가지 직렬화 lib 를 제공한다.

  • Java serialization: By default, Spark serializes objects using Java’s ObjectOutputStream framework, and can work with any class you create that implements java.io.Serializable.
    Java serialization : 기본적으로 제공됨. Spark 는 Java 의 ObjectOutputStream 프레임워크를 사용하여 객체를 직렬화한다. 그리고 java.io.Serializable 을 상속받은 사용자의 어떠한 클래스를 직렬화 할 때도 마찬가지.
    You can also control the performance of your serialization more closely by extending java.io.Externalizable.
    또한 java.io.Externalizable 을 확장하여 직렬화의 성능을 더 자세히 제어할 수도 있습니다.
    Java serialization is flexible but often quite slow, and leads to large serialized formats for many classes.
    Java 직렬화는 유연하지만 종종 매우 느리다. 그리고 많은 클래스의 경우 큰 직렬화 포맷(??)으로 이어진다 ????
  • Kryo serialization: Spark can also use the Kryo library (version 4) to serialize objects more quickly.
    Kryo serialization : Spark 는 또한 더 빠르게 객체를 직렬화하기 위해 Kryo lib 를 사용할 수 있다.
    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.
    Kryo 는 바로 위에서 설명한 Java Serialization 보다 훨씬 더 빠르고 압축률도 좋지만, 모든 Serializable 타입들을 제공하지 않는다. 또한 사용자가 프로그램에 사용할 class 들을 미리 등록해야 사용 가능하다.

You can switch to using Kryo by initializing your job with a SparkConf and calling conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer").

SparkConf 를 사용하거나 conf.set 옵션을 추가 후, job 을 초기화하여 Kryo 를 사용하도록 만들 수 있다. 

This setting configures the serializer used for not only shuffling data between worker nodes but also when serializing RDDs to disk.

이 설정은 워커 노드 사이에 데이터 셔플링 뿐 아니라 RDD 가 disk 에 직렬화로 쓰여질 때 Kryo 를 이용하여 직렬화 하도록 설정한다.

The only reason Kryo is not the default is because of the custom registration requirement, but we recommend trying it in any network-intensive application.

Kryo 가 기본값이 아닌 유일한 이유는 사용자 지정 등록 요구때문이다.

하지만 우리는 어느 네트워크 집약적인 application에서든 Kryo 를 사용하길 권장한다.

Since Spark 2.0.0, we internally use Kryo serializer when shuffling RDDs with simple types, arrays of simple types, or string type.

Spark 2.0.0 버전 이후로, 간단한 타입, 간단한 타입의 배열, string 타입을 갖는 RDD 를 셔플할 때 내부적으로 Kryo 를 사용한다.

Spark automatically includes Kryo serializers for the many commonly-used core Scala classes covered in the AllScalaRegistrar from the Twitter chill library.

Spark 는 Twitter chill lib 의 AllScalaRegistrar 에 포함된, 널리 흔하게 사용되는 핵심 Scala 클래스들을 위해 Kryo 직렬화 lib 를 내장하고있다.

To register your own custom classes with Kryo, use the registerKryoClasses method.

사용자가 만든 class 를 Kryo 를 사용하도록 등록하려면, registerKryoClass 메소드를 사용하라.

val conf = new SparkConf().setMaster(...).setAppName(...)
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
val sc = new SparkContext(conf)

The Kryo documentation describes more advanced registration options, such as adding custom serialization code.

Kryo 문서에서 더 자세한 등록 옵션에 대해 설명한다. 예를 들어 커스텀 직렬화 코드 추가하는 방법 등.

If your objects are large, you may also need to increase the spark.kryoserializer.buffer config.

만약 사용자의 객체 크기가 크다면, spark.kryoserializaer.buffer 설정을 증가할 필요가 있을 것 같다.

This value needs to be large enough to hold the largest object you will serialize.

이 값은 사용자가 직렬화 하는 가장 큰 객체를 수용하기 위해 충분히 커야 할 필요가 있다.

Finally, if you don’t register your custom classes, Kryo will still work, but it will have to store the full class name with each object, which is wasteful.

마지막으로, 사용자가 자신의 커스텀 클래스를 등록하지 않는다면, Kryo 는 물론 동작하겠지만, 각 객체 별로 모든 클래스 이름을 저장할 것이며 이것은 낭비이다.

Memory Tuning
메모리 튜닝하기

There are three considerations in tuning memory usage:

메모리 사용률을 튜닝하는 데 세 가지 고려해야 할 점이 있다.

the amount of memory used by your objects (you may want your entire dataset to fit in memory),
the cost of accessing those objects,
and the overhead of garbage collection (if you have high turnover in terms of objects).

1. 사용자 객체에 의해 사용되는 메모리 양 (사용자는 아마도 모든 데이터가 memory 에 딱 맞는 크기이길 원할것이다.)

2. 1번 객체들에 접근하는 데 드는 비용

3. GC 의 오버헤드,부하 (객체 관점에서 high turnover 가 높을 때)

여기서 말하는 high turnover 는 "특정 시간 동안 생성된 객체들 중에서 사용되지 않는 객체들이 많은 상황" 이라고 함.

그래서 메모리가 가득 차고, GC 가 청소하러 출동하고...

By default, Java objects are fast to access, but can easily consume a factor of 2-5x more space than the “raw” data inside their fields.

기본적으로, Java 객체는 빠르게 접근할 수 있지만, 내부 필드의 원시 데이터보다 2~5배 더 많은 공간을 소비하기 쉽다. (their fields 가 뭔지 모르겠는데 아래 설명보면 아하 하게 됨)

This is due to several reasons:
이것은 다음과 같은 이유들 때문이다.

  • Each distinct Java object has an “object header”, which is about 16 bytes and contains information such as a pointer to its class.
    각기 다른 Java 객체들은 object header 를 갖고 있는데 이것은 16바이트이고 그 클래스를 가리키는 포인터 같은 정보를 포함하고 있다.
    For an object with very little data in it (say one Int field), this can be bigger than the data.
    굉장히 작은 데이터, 이를테면 int 단 하나만 갖는 객체의 크기는, 내부에 있는 int 데이터보다 객체 크기가 훨씬 크다.
  • Java Strings have about 40 bytes of overhead over the raw string data (since they store it in an array of Chars and keep extra data such as the length), and store each character as two bytes due to String’s internal usage of UTF-16 encoding.
    Java String 은 실제 내부 문자열 데이터와 더불어 약 40바이트만큼의 추가 오버헤드를 갖고 있다. 왜냐면 Chars 의 배열도 저장해야하고, length 같은 추가 정보도 계속 갖고 있어야 하기 때문에. 또한, String 의 내부 UTF-16 인코딩의 사용으로 인해, 문자열의 각 character 는 2 바이트로 저장된다.... 영어 진짜 마음에 안 듦...
    Thus a 10-character string can easily consume 60 bytes.
    따라서, 10개 글자를 갖는 문자열이 60바이트나 소비할 수 있다.
  • Common collection classes, such as HashMap and LinkedList, use linked data structures, where there is a “wrapper” object for each entry (e.g. Map.Entry).
    HashMap 이나 LinkedList 같은 일반적인 collection 클래스들은 각 항목 당 "wrapper" 객체가 있는 linked 데이터 구조를 사용한다.
    This object not only has a header, but also pointers (typically 8 bytes each) to the next object in the list.
    이 객체는 header 뿐만 아니라, 다음 리스트의 객체를 가리키는 pointer (일반적으로 각 포인트 당 8바이트) 도 갖고 있다.
  • Collections of primitive types often store them as “boxed” objects such as java.lang.Integer.
    primitive 타입의 Collections 들은 종종 java.lang.Integer 같은 "boxed" 객체로 저장된다.

This section will start with an overview of memory management in Spark, then discuss specific strategies the user can take to make more efficient use of memory in his/her application.
이 세션에서는 Spark 에서의 메모리 관리 개요부터 시작할 것이다. 그 후, 사용자들이 그들의 application 내 메모리를 더 효율적으로 사용하도록 취할 수 있는 몇가지 전략에 대해 논의해본다.
In particular, we will describe how to determine the memory usage of your objects, and how to improve it – either by changing your data structures, or by storing data in a serialized format.
특히, 사용자 객체의 메모리 사용률을 어떻게 판별하는지, 그리고 그것을 어떻게 개선할지 설명 할 것이다. 예를 들면 데이터 구조를 바꾼다거나, 직렬화 포맷으로 데이터를 저장한다거나...
We will then cover tuning Spark’s cache size and the Java garbage collector.

그 후, Spark 캐시 크기와 Java GC 튜닝에 대해 다룰 것이다.

 

 

 

 

 

 

+ Recent posts