유니티 엔진으로 모바일 게임을 개발하던 중, 유니티  에디터에서는 분명히 잘 되던 파일 입출력이 안드로이드로 빌드해서 테스트하면 안되는 문제를 겪었다. 

 이 문제에 대해서 알아보니, 유니티 에디터에서는 에셋 폴더에 접근 가능하지만, 빌드 버전에서는 접근이 불가능하다는 것을 알게 되었다.(https://answers.unity.com/questions/272486/cannot-read-or-write-a-txt-file-after-build.html)

 빌드버전에서 에셋 파일을 사용하기 위해서는 Streaming Asset을 사용할 수 있다.


1. Streaming Asset이란

 에셋 폴더 내의 폴더들 중 특별취급되는 폴더 중 하나다.(http://wiki.unity3d.com/index.php/Special_Folder_Names_in_your_Assets_Folder)

 이 폴더에 포함된 파일들은 빌드 타겟 컴퓨터(안드로이드든, ios든 윈도우든)로 복사되어 들어가고, 읽기 전용이라서 게임 도중에 데이터 저장은 불가능하다.

 이 방법을 사용하려면 유니티 엔진에서 에셋 폴더 내에 StreamingAssets 라는 폴더를 만들고 ,그 안에 파일을 넣으면 이용 가능하다.

StraminAssets 폴더를 Asset 폴더 내에 배치하여 사용한다.


2. Streaming Assets 이용 방법

  Streaming Assets를 이용하는 방법은 2가지가 있다. WWW 클래스를 이용하는 방법, BetterStreamingAssets 오픈소스 라이브러리를 이용하는 방법, 이렇게 2가지인데, 각 방법은 각각의 포스팅으로 다루었으니 링크를 참조하기 바란다.

https://invincibletyphoon.tistory.com/48 (WWW Class) (비추천)

https://invincibletyphoon.tistory.com/47 (BetterStreaminAssets) (추천)



3. 레퍼런스

https://answers.unity.com/questions/272486/cannot-read-or-write-a-txt-file-after-build.html - 에디터에서는 에셋 폴더 접근이 가능하지만, 빌드버전에서는 불가능하다.

http://wiki.unity3d.com/index.php/Special_Folder_Names_in_your_Assets_Folder - 유니티 에셋 내의 특별한 디렉토리명들

https://docs.unity3d.com/2018.4/Documentation/Manual/StreamingAssets.html - 유니티 StreamingAssets 레퍼런스

https://stackoverflow.com/questions/20188742/writing-to-unitys-streamingassets-folder-at-runtime-on-android - StreamingAsset은 읽기 전용임.

http://memocube.blogspot.com/2014/04/blog-post.html - 유니티 DataPath 정리

https://ajh322.tistory.com/135 - 유니티 안드로이드 파일입출력 예제




'IT 관련 > 유니티 엔진' 카테고리의 다른 글

유니티 모바일 한글 깨짐  (2) 2019.09.25
WWW 클래스를 이용한 StreamingAsset 처리  (0) 2019.09.25
Better Streaming Asset  (5) 2019.09.25
블로그 이미지

서기리보이

,

1. 이번 장에서 다루는 내용

- Deterministic Finite Accepter (DFA)

- Non-deterministic Finite Accepter (NFA)

- DFA와 NFA의 공통점


2. Deterministic Finite Accepter (DFA)


2.1 Deterministic Accepters and Transition Graphs


DFA란?

- 컴퓨터란 무엇인가에 대한 가장 간단한 모델

- 많은 장치들은 DFA로 프로그래밍 되었다.(ATM, 자판기, 엘리베이터, 컴파일러 등등...)

- 관측 가능한 세상은 모두 Finite하다.


 모든 오토마타와 마찬기지로, Deterministic Accepter는 내부 States, 한 State에서 다른 State로 전환하기 위한 규칙, 입력, 그리고 결정을 내릴 방법들을 가진다.

 DFA의 구성요소는 다음과 같이 정리 할 수 있다.



  • Q는 내부 State들이다.

  • Σ는 Input Alphabet으로, Symbol들의 유한한 집합이다.

  • δ : Q x Σ -> Q 은 transition function으로, total function이다..

  • 는 initial state다.

  • 는 final state다.


DFA는 다음과 같은 순서로 작동한다.

  1. 처음에는 오토마타를 initial state q0상태라고 하고, 입력 스트링의 가장 왼쪽 심볼부터 처리한다(입력 메커니즘을 가장 왼쪽 심볼에 둔다.).

  2. 오토마톤(오토마타의 단수형)의 각 이동마다, 입력 메커니즘은 오른쪽으로 한 포지션씩 이동한다. 여기서 한 번의 이동에 하나의 입력 심볼을 소비한다.

  3. 스트링의 끝에 도달했을 때 오토마톤이 Final State들 중 하나에 있다면 String은 Accept 된다.

  4. 스트링의 끝에 도달했는 때 오토마톤이 Final State에 있지 않다면 String은 Reject 된다.


예를 들어,


이라고 할 때, DFA가 q0 상태이고 현재 입력 Symbol이 a이면 dfa는 q1 상태로 전환 될 것이다.


2.2 Transition Graph

 오토마타는 깔끔하고 직관적으로 보여주는 것이 중요하다. Finite Automata를 시각화 하기 위해서 Transition Graph라는 것을 사용 할 수 있다.

 DFA의 요소들은 Transition Graph에서 다음과 같이 표현된다.

  • Vertex - DFA의 State를 나타냄.

  • Edge - DFA의 Transition을 나타냄.

  • 2중원 Vertex - DFA의 Final Stat을 나타냄.

  • Initial State는 특정 Vertex에서 출발하지 않은, 이름이 붙여지지 않은 화살표가 가르키는 Vertex로 표현된다.


DFA 을 나타내는 그래프 은 정확하게 |Q|개의 Vertex를 가지며 각각의 Vertex는 라고 이름 붙인다. q0에 매칭되는 Vertex가 Initial Vertex이고, 인 qf들은 모두 Final Vertex다.

 

예제)


순서대로 (States, Input Alphabet, Transition Function, Initial State, Final States)



위의 DFA는 다음과 같이 표현 할 수 있다.


출처 Formal Languages and Automata - Peter Linz


이 DFA가 01이라는 String을 입력받으면 다음의 순서로 진행된다.

  1. q0 State에서 시작

  2. Symbol 0를 먼저 읽게되고, 오토마톤은 q0->q0으로 이동하게 됨.

  3. Symbol 1을 읽으면 오토마톤은 q0->q1로 이동하게 됨.

q1은 Final State이므로 01 String은 accept 된다.


같은 방식으로 00이라는 String을 입력하면 Reject된다.


2.3 Extended Transition Function

 Extended Transition Function 라는 것이 있다.

 의 2번째 Argument는 하나의 Symbol이 아닌 String이고, 의 값은 이 String을 읽고 난 후의 Automaton의 State를 나타낸다.


예시)

이면


이다.


을 재귀적으로 정의하기

Extended Transition Function()은 다음과 같이 정의 할 수도 있다.



예제)

다음의 Transition Graph를 보고 String w=abba 에 대해 를 계산하고 String w가 accept 되는지 판단해라.


출처 - 강의자료




풀이



q2는 Final State이므로 abba는 Accept 된다.


2.4 Language와 DFA


2.4.1 Language란?

Language는 오토마톤에 의해 Accept되는 모든 String들의 집합이다.


DFA 

의 Language L은 Σ에 속하는 String들 중 M에 의해 Accept 될 수 있는 모든 String들의 집합이다.

L은 다음과 같이 표현한다.




DFA는 의모든 String을 처리 할 수 있고, 그 각각의 String들을 Accept하거나 하지 않을 수 있다.

여기서 Accept되지 않는다는 것은 DFA가 FInal State가 아닌 State에서 끝났다는 것을 의미한다.

Accept되지 않는 Language는 다음과 같이 표현한다.



Trap State

한번 들어가면 빠져 나올 수 없는 State를 Trap State라고 한다.


예시)


출처 Formal Languages and Automata - Peter Linz


이 Transition Graph에서 오토마톤이 q2 State로 들어가면 빠져 나올 수 없다. 여기선 q2가 Trap State다.(Non-final Trap State)


2.5 Regular Language

 모든 유한 오토마톤은 어떤 Language를 Accept한다.

 어떤 유한 오토마타와 관련된 Language들을 묶어서 Family로 만들 수 있다.

 DFA에 의해 Accept되는 Language들의 Fmaily는 굉장히 제한된다. DFA에 의해 Accept되는 Family의 구조와 특성에 대해서는 계속 공부해나가다 보면 알게 될 것이다.


2.5.1 Regular Language란?

어떤 Language L에 대하여 L=L(M)을 만족하는 DFA M이 존재한다면 L이 Regular하다고 한다.

그래서 Language L이 Regular 하다는 것을 증명하기 위해서는 L에 맞는 DFA를 찾아야한다.



3. Non-deterministic Finite Accepter(NFA)

 Non-determinism이란 것은 오토마톤의 이동에서 하나의 선택을 의미한다. Non-deterministic하면 각 상황에서 한가지 이동을 제공하지 않고, 가능한 이동 방법 여러개를 제공한다. 이는 Transition Function가 하나의 State를 값으로 가지는 것이 아닌, 가능한 State들의 범위를 가지는 것으로 구현해낸다.


3.1 Non-deterministic Finite Accepter(NFA)란

 Non-deterministic Finite Accepter(NFA)은 다음과 같이 Quintuple로 정의된다.


  • Q는 내부 State들이다.

  • Σ는 Input Alphabet으로, Symbol들의 유한한 집합이다.

  • 은 transition function으로, total function이다.(* 2^Q는 Q의 Powerset.)

  • 는 initial state다.

  • 는 final state다.


Q,Σ,q0,F는 DFA와 동일하고, δ(transition function)은 다르다.


DFA와의 비교

 이 정의는 DFA와 주요한 3가지 차이점이 있다.

 첫째로, Non-deterministic Accepter에서, δ의 범위는 2^Q(Q의 Powerset)에 포함되기 떄문에 δ의 값은 Q의 하나의 요소가 아니라 Q의 Subset이다. 이 Subset은 Transition에 의해 만들 수 있는 모든 State들의 집합을 정의한다.

 예를 들어, 현재 State가 q1이고 Symbol a를 읽어 냈을 때, δ(q1,a) = {q0,q2}라고 정의하면 q0, q2 둘 다 NFA의 다음 State가 될 수 있다.

 둘째로, λ를 δ의 두번째 아규먼트로 쓸 수 있다.

 이 말은 NFA는 Input Symbol을 소모하지 않고도 Transition을 일으킬 수 있다는 뜻이다. 지금까지 입력 메커니즘이 오른쪽으로만 이동 할 수 있는 줄 알았지만, 오토마톤이 이동해도 입력메커니즘은 움직이지 않는 경우도 있다.

 마지막으로, NFA에서 δ(qi,a)의 집합이 비어있을 수도 있다. 그런 경우에는 Transition이 하나도 정의되지 않은 경우다.


 DFA와 마찬가지로, Non-deterministic Accepter는 Transition Graph로 표현 할 수 있다. Vertex들은 Q에 의해 결정되고, a라는 이름의 Edge(qi,qj)는 δ(qi,a)가 qj를 포함할 때 Edge로 그래프에 포함된다.

 그리고 a는 빈 스트링일 수도 있으니까 λ라는 이름의 Edge도 존재 할 수 있다.


NFA에서 스트링이 Accept되는 조건

 NFA에서 어떤 스트링을 따라 머신을 이동하는 여러 경우의 수들 중 스트링의 끝에 머신을 Final State에 가게 만드는 경우의 수가 있다면 그 스트링으 Accept되고, 그런 경우가 없다면 Reject된다.


Transition Function 확장

 NFA에서도 Transition Function은 확장 될 수 있다. 그래서 확장된 Transtion Function의 두번째 아규먼트는 String이다.

 확장된 Transition Function인 에 대하여 를 수행한 결과로 오토마톤이 될 수 있는 모든 State들의 집합을 Qj라고 하고, 가 qi부터 시작되고 w 스트링을 읽는다고 하면 다음과 같이 표현한다.



예제)

다음의 Transition Graph를 보고 NFA의 Transition Function들을 정의해라.


출처 Formal Languages and Automata - Peter Linz


정답)



풀이)

을 살펴보자면, q2->q0로의 λ 라는 이름의 Transition이 존재한다. 그러므로 는 q0를 포함한다.

그리고 어떤 State든지 자기자신에서 움직이지 않고 머무를 수 있는데, 그 때는 Input Symbol을 소모하지 않는다. 그러므로 는 q2도 포함한다고 할 수 있다.


따라서 



을 살펴보자면, 

λ-λ-a-λ-λ 순으로 q1->q2->q0->q1->q2->q0 로 갈 수 있으니 q0를 포함한다.

λ-λ-순으로 q1->q2->q0->q1 로 갈 수 있으므로 q1를 포함한다.

λ-λ-a -λ  순으로 q1->q2->q0->q1->q2 로 갈 수 있으니 q2를 포함한다.


따라서 



Languages Accepted By NFA


NFA 에 의해 Accept되는 Language L은 다음과 같이 정의 할 수 있다.




4. DFA와 NFA의 동일함(Equivalence of DFA and NFA)

 

 DFA와 NFA는 어떤 점에서 다른가?

 물론 둘은 정의에서 부터 차이가 있다. 하지만 그 사실이 그 둘의 근본적인 차이가 있다는 의미는 아니다. 이 질문에 답하기 위해 오토마타 사이의 Equivalence라는 컨셉을 소개한다.

 

 정의

 두 유한 Accepter M1과 M2가 둘 다 같은 Language를 Accept하면 Equivalent하다고 한다.

 즉, 다음을 만족하면 M1과 M2는 동일하다.



Powerful

어떤 종류의 오토마톤이 다른 어떤 오토마톤으로도 달성 할 수 없는 것을 할 수 있는 오토마톤을 Powerful하다고 한다.

 DFA와 NFA를 비교해보면, 둘은 동등하게 Powerful하다.

 DFA는 NFA에 규칙을 더한 것이 때문에, DFA에 의해 Accept되는 String을 Accept 하는 NFA가 반드시 존재한다. 그 반대로 NFA에 의해 Accept되는 String을 Accept하는 DFA도 반드시 존재한다.

 그러니 모든 DFA, NFA에 대해 DFA->NFA, NFA->DFA로의 변경이 가능하다.

 

NFA->DFA 전환

NFA를 DFA로 전환하려면 NFA의 Transition Graph로 다음의 과정을 따라가면 된다.

  1. 심볼을 따라 그래프를 따라간다.

  2. Non-deterministic Transition에 대한 새로운 State를 추가한다.

  3. 부족한 Edge들을 추가한다.

  4. Final State를 포함하는 State들은 Final State로 한다.


5. Finite Automata에서 State의 수 줄이기

State의 수를 줄이려면 다음의 과정을 따라가면 된다.

  1. State를 Final State와 Non-final State로 나눈다.

  2. Transition들을 계산하고 식별불가능한 State들은 나눈다.

  3. 더이상 나눌 필요가 없어질때까지 2번 과정을 반복한다.


레퍼런스

Formal Languages and Automata - Peter Linz

강의자료


'수학 > 오토마타' 카테고리의 다른 글

1장 Introduction to the Theroy of Computation  (0) 2019.04.08
블로그 이미지

서기리보이

,



1. 이번 장에서 다루는 내용


1.1 오토마타 관련 기본 수학

 - 집합이론

 - 트리와 그래프

 - 기본적인 증명법들(귀납법, 연역적 추론법, 반례)


1.2 Language, Grammar, Automata 개념



2. 집합(Set)


집합이란? 

- 서로다른 대상들이 모여 이루는 새로운 대상.(https://ko.wikipedia.org/wiki/%EC%A7%91%ED%95%A9)


집합 관련 기호

- 포함관계(Membership)


집합 표현 예제



집합의 Powerset

Powerset 2^S는 집합 S의 모든 subset의 집합이다.


집합 S = {a,b,c}에 대하여 집합 S의 Powerset 2^S = { {}(공집합), {a}, {b}, {c}, {a,b}, {a,c}, {b,c}, {a,b,c} }



드모르간의 법칙


Function

: 집합을 입력으로 받고 하나의 숫자를 출력으로 내는 함수.


Relation

: S X S의 임의의 서브클래스



3. 그래프

그래프는 2개의 유한 집합 Vertices, Edges로 구성됩니다.


4. 트리

트리는 그래프의 일종으로, Cycle이 없는 Directed Graph입니다.



5. 증명법

- 연역적 추론(모든 인간은 죽는다. 소크라테스는 인간이다. 따라서 소크라테스는 죽는다.)


- 귀납법(소크라테스는 아테네인이다. 소크라테스는 철학자이다. 그러므로 모든 아테네인들은 철학자다.)


- 반례제시(소크라테스는 죽지 않는다. 따라서 모든 인간이 죽는것은 아니다.)



6. Language

6.1 용어 정리


- Alphabet 

Symbol(a,b,c,...)들로 구성된 유한한, 비어있지 않은 집합 


- String

유한한 갯수의 Symbol들이 연속으로 이어진 것



- Concatenation

두 개의 스트링을 잇는다.



- Reverse

뒤집기




- Length

스트링의 길이



- Empty String  λ

비어있는 문자열을 나타내는 람다문자




- Substring, Prefix & Suffix




- Repeats

반복



- Infinite Alphabets

 


 ∑의 Symbol을 0개 이상 Concatenate해서 얻은 모든 String의 집합(Star-Closure)



(Positive Closure)



ex1)

라고 하면


이다.



ex2) 

집합 

의 Language이고,

Finite Language이다.


ex3)

집합 

의 Language이고,

Infinite Language다.


- Language의 일반적인 정의


Language L은 일반적으로  의 Subset으로 정의된다.



7. Grammars


7.1 Grammar란?

- Language를 표현하기 위한 메커니즘


Grammar G는 다음의 quadruple로 표현된다.

  • G=(V,T,S,P).

  • V : 변수, 유한한 객체의 집합.

  • T : Terminal Symbol. 유한한 객체의 집합.

  • V: Start Variable. 특별한 Symbol

  • P: Productions. 유한한 객체의 집합

  • V와 T는 비어있지 않고, disjoint하다.


7.2 Production Rule 이란?

- Grammar가 String을 어떻게 변화시키는지를 나타내는 것.

- Grammar = Language + Production Rules


ex) Grammar G=({S}, {a,b}, S, P)이고, Production Rule P가

  • S -> aSb

  • S -> λ

라고 하면,


  • S => aSb => aaSbb => aabb


7.3 Derivation

  • x -> y

  • w = uxv

  • z = uyv

라고 하면

w => z 이다.(z is derived from w)

 : w1=>w2=>...=>wn (wn이 w1으로부터 derive 될 수 있다.)


7.4 Language

Grammar G=(V,T,S,P)에 의해 생성되는 Language는


이다.


ex) Grammar G=({S}, {a,b}, S, P)이고, Production Rule P가

  • S -> aSb

  • S -> λ

라고 하면,

G의 Language 는 다음과 같다.




'수학 > 오토마타' 카테고리의 다른 글

2장 Finite Automata  (1) 2019.04.09
블로그 이미지

서기리보이

,

본 포스팅은 개인적인 공부를 위해 책의 내용을 요약하여 정리하는 글입니다.

작성자는 분산 데이터베이스, 하둡에 대해서 처음 공부해보는 것이니 참고가 된다면 기쁘겠지만 틀린 정보가 있을 수 있습니다.


개발환경 : 우분투 18.04, 하둡 3.0.3


데이터셋의 크기가 한개의 물리적 머신의 저장용량보다 커지면 여러개의 머신에 데이터셋을 분할시킬 필요가 있다.


분산 파일 시스템(Distributed File System)

머신들의 네트워크의 저장소를 관리하는 파일 시스템

(Filesystems that manage the storage across a network of machines are called distributed filesystem)


하둡은 HDFS라는 분산 파일 시스템을 가진다. HDFS는 하둡의 주력 파일 시스템이지만, 사실 하둡은 일반목적 파일시스템에 대한 추상화도 가지고 있다. 이번 장에서는 하둡이 어떻게 다른 저장소 시스템(Storage system)(로컬 파일 시스템, 아마존 S3 등)과 통합되는 지 볼 것이다.


1. HDFS의 디자인(The Design of HDFS)

HDFS는 아주 큰 파일들을 스트리밍 데이터 엑세스 패턴으로 저장하도록 디자인 된 파일시스템으로, 상용 하드웨어들의 클러스터 상에서 돌아간다.

다음 용어들을 보도록 하자.


Very large

"Very large"라는 말은 수백 메가바이트, 기가바이트, 테라바이트의 크기를 말한다. 최근에는 페타바이트 단위의 데이터를 저장하는 하둡 클러스터도 있다.


Streaming data access

HDFS는 write-once, read-many-time(한번 쓰고 여러번 읽기)가 가장 효율적인 데이터 처리 패턴이라는 아이디어를 기반으로 설계되었다.  데이터셋은 생성되거나 복제 된 것이고, 그 데이서 셋에 대한 다양한 분석 작업이 이루어 질 것이다. 각각의 분석작업은 데이터셋의 많은 부분들을 필요로 할 것이므로 전체 데이터셋을 읽는 시간이 첫 번째 레코드를 읽는 대기시간보다 중요하다.


*(Streaming Data Access란 랜덤 탐색 없이 파일의 시작점으로 부터 sequential하게 쭉 읽어나가는 것을 말한다.

https://stackoverflow.com/questions/16260535/streaming-data-access-and-latency-in-hadoop-applications)


Commodity hardware

하둡은 비싸고 신뢰할 수 있는 하드웨어를 필요로하지 않는다.

하둡은 상용 하드웨어(Commodity hardware)(여러 상점에서 구할 수 있는 일반적인 하드웨어들)의 클러스터 상에서 돌아가도록 디자인되었기 때문에 클러스터 상의 노드에 오류가 있을 가능성이 높다. HDFS는 그런 오류에도 크게 방해되지 않고 작동되도록 디자인되었다.



HDFS를 사용하는게 부적합 한 경우도 있다. 이 문제는 미래에는 바뀔 수 있지만, 지금은 HDFS가 취약한 부분이다.

다음의 특성을 가지는 어플리케이션에는 HDFS를 사용하는 것이 부적합 할 수 있다.


짧은 반응시간(Low-latency data access)

수십 밀리세컨드 정도로 데이터 접근에 대해 짧은 응답시간(latency)을 필요로하는 어플리케이션은 HDFS와는 잘 맞지 않다. HDFS는 처리량(throughput)이 높지만 응답시간은 느릴 수 있다. 데이터 접근에 짧은 응답시간을 위해서는 HBase(20장에서 다룬다.)이 더 적합하다.


많은 작은 파일(Lots of small files)

메모리 상에 존재하는 네임노드(namenode)가 파일 시스템의 메타데이터를 가지고 있기 때문에, 파일 시스템 상에 존재 할 수 있는 파일의 수는 네임노드의 크기에 의해 제한된다.

일반적으로(As a rule of thumb) 각 파일, 디렉토리, 블록의 크기는 150바이트다. 그러므로 예를 덜어, 각각 1블록을 차지하는 100만개의 파일이 있다고 하면 적어도 300MB의 메모리를 필요하게 된다. 수백만가지의 파일을 저장하는 것이 가능하긴 해도, 수억개의 파일을 저장하는 것은 현재의 하드웨어 기술력으로는 무리다.


다수의 작성자, 무작위적인 변경(Multiple writers, arbitrary file modifications)

HDFS 상의 파일들은 한 명의 writer에 의해 쓰여지는 것이 좋다. Writer는 항상 append-only 방법으로 파일의 맨 끝에 만들어진다. 그래서 다수의 writer나 파일의 무작위적인 오프셋 변경은 지원되지 않는다.


2. HDFS 컨셉


2.1 Blocks

블록은 데이터를 읽고 쓰는 최소한의 크기단위다. 파일 시스템의 블록은 몇 키로바이트 정도이고, 디스크의 블록은 보통 512 바이트다.

HDFS도 블록의 컨셉을 가지는데, 블록의 크기가 기본 128MB로 아주 크다.

다른 블록 개념들과 같이 HDFS도 블록의 크기보다 큰 파일은 블록의 크기만큼 나눠서 저장하는데(예를 들어 블록의 크기가 128MB이고, 200MB의 파일을 저장해야 한다면 한 블록에 128MB, 다른 블록에 나머지를 저장) 다른 블록 개념들과 다르게 블록의 크기보다 작은 파일은 블록 하나를 완전히 소유하지 않는다.(예를 들어 블록의 크기가 128MB이고 파일의 크기가 1MB이면 파일은 128MB를 다 차지하는 것이 아니라 1MB만 차지한다.)


이제부터 Block이라는 단어가 나오면 HDFS의 블록을 얘기하는 것이다.


HDFS의 블록 크기가 큰 이유

HDFS의 블록이 큰 것은 탐색의 코스트(seek time)를 줄이기 위해서다.

디스크에서 한 블록을 읽는데 걸리는 시간은 seek time + transfer time 이다.(seek time : 데이터의 시작점을 찾는 시간. transfer time : 블록의 크기 / transfer rate). 여기서 transfer rate는 디스크의 물리적인 한계가 있으므로 더 빠르게 만들기는 어려우니 블록의 크기를 크게 만들면 transfer time이 크게 늘어나 한 블록을 읽는 시간에 seek time은 무시해도 될 정도로 작은 값으로 취급할 수 있다.

그러므로 블록을 크게 만들면 대용량 데이터를 읽을 때 seek time의 영향이 줄어들고 디스크의 물리적인 속도인 transfer rate과 데이터를 읽는 속도가 거의 비슷해진다.


분산 파일 시스템에서 블록 개념을 도입하는 이점

- 한개의 파일의 크기가 네트워크 내의 디스크 한개의 크기보다 커도 된다.

- 파일 대신 블록으로 추상화하면 저장소의 서브시스템을 간단하게 할 수 있다.

- Fault tolerance와 availability를 위한 복제(replication)에 블록이 더 용이하다.


같은 파일의 블록들이 반드시 한 디스크 내에 존재해야하는 것은 아니다. 그래서 블록 개념을 도입하면 아주 큰 파일이더라도 네트워크상의 여러 디스크에 나눠서 저장 할 수 있다.


분산 시스템의 경우 고장나는 경우의 수가 다양하기 때문에 간단함(simplicity)이 아주 중요하다. 블록은 크기가 고정되어 있기 때문에 저장소 관리를 쉽게 하고,  블록은 데이터 덩어리일 뿐이니 블록에 대한 접근 권한 등 메타데이터가 불필요하다.

그래서 블록 개념을 도입하는것이 간단함을 유지하는데 도움이 된다.


각 블록은 몇개의 머신들에 중복되어 저장된다.(보통 3개의 머신에 나눠서 저장한다.) 한 블록이 이용불가능한 상황이 되면 다른 머신에 있는 중복 데이터를 가져 올 수 있는데, 이 과정은 클라이언트에게 투명하다(transparent).(클라이언트가 이런 과정은 신경쓰지 않아도 원하는 블록을 얻어 올 수 있다.)

그리고 자주 사용되는 파일의 블록들의 경우 좀 더 여러 곳에 복사 해 두어 클러스터 내의 읽기 부하량(read load)를 분산 시킬 수도 있다.



* HDFS의 fsck 명령어는 블록 개념을 이해하고 있는 명령어다. 다음의 명령어를 입력하면 파일시스템 내의 각 파일을 구성하는 블록들을 리스트화 해 줄 것이다.

# hdfs fsck / -files -blocks


2.2 네임노드와 데이터노드














의문점

HDFS에서 다수의 작성자, 무작위적인 변경이 지원되지 않는다는 것의 의미. 아직 이해하지 못함.



레퍼런스

스택 오버플로우. streaming data access에 관한 내용. - https://stackoverflow.com/questions/16260535/streaming-data-access-and-latency-in-hadoop-applications


스택 오버플로우. HDFS의 블록이 큰 이유. - https://stackoverflow.com/questions/22353122/why-is-a-block-in-hdfs-so-large

'IT 관련 > Hadoop The Definitive Guide (4th)' 카테고리의 다른 글

Chapter2. Map Reduce  (0) 2019.01.23
Chapter1. Meet Hadoop  (0) 2019.01.17
블로그 이미지

서기리보이

,

본 포스팅은 개인적인 공부를 위해 책의 내용을 요약하여 정리하는 글입니다.

작성자는 분산 데이터베이스, 하둡에 대해서 처음 공부해보는 것이니 참고가 된다면 기쁘겠지만 틀린 정보가 있을 수 있습니다.


개발환경 : 우분투 18.04, 하둡 3.0.3


개발환경 구축은 Ssup2 블로그를 참조하였습니다.

(https://ssup2.github.io/record/Hadoop_%EC%84%A4%EC%B9%98_%EC%84%A4%EC%A0%95_Ubuntu_18.04/)


하둡은 여러 언어로 쓰여진 프로그램을 돌릴 수 있다. 이 장에서는 자바, 루비, 파이썬으로 표현된 프로그램을 보도록 하자.


1. A Weather Dataset

예제로 날씨 데이터를 분석하여 각 년도별로 최대 온도가 몇도인지 분석하는 프로그램을 만들어 보자.


1.1 DataFormat

예제 프로그램 작성을 위해 NCDC의 데이터를이용한다.

그리고 문제를 단순화하기 위해 기온 등 기본적인 요소만 사용한다.


* NCDC 데이터 받는 법

이 깃허브 링크로 들어가 전체 프로젝트를 다운받는다.

https://github.com/tomwhite/hadoop-book/


input/ncdc/ 디렉토리의 sample.txt 파일을 이용하면 된다.


2. Unix Tool을 이용한 데이터 분석

Shell Script를 이용한 데이터 분석에 대한 내용.

사실상 하둡과는 무관하므로 생략.


3. Hadoop을 이용한 데이터 분석

하둡의 병렬 처리기능을 이용하려면 쿼리문을 맵리듀스용으로 표현해야한다. 로컬로 소규모 테스트를 마치고 나면 클러스터 머신에서 돌려 볼 수 있다.


3.1 Map and Reduce

맵리듀스 작업은 맵/리듀스 2개의 phase로 구분하여 작동한다. 각 페이즈는 key-value pair를 입출력으로 가지고 그 타입은 프로그래머가 결정한다.


그리고 맵함수와 리듀스함수도 프로그래머가 결정한다.


맵 페이즈의 입력값은 raw NCDC 데이터다. 맵 페이즈에서는 텍스트 입력 포멧을 선택하여 raw 데이터의 각 라인을 텍스트값으로 변형하는데 사용 할 수 있다.


맵 함수는 리듀스 함수가 작업할 수 있게 데이터를 준비하는 단계로, 이번 예제에서 맵 함수는 년도와 온도를 뽑아오는 간단한 함수다.


또한 맵 함수는 빠져있거나, 의심스럽거나, 에러가 있는 베드 레코드(Bad Record)를 걸러내기도 한다.


그리고 리듀스 함수의 역할은 각 년도별로 최대 온도를 찾는 것이다.


데이터 처리 과정


먼저 맵함수는 다음의 입력을 받는다.


0067011990999991950051507004+68750+023550FM-12+038299999V0203301N00671220001CN9999999N9+00001+99999999999

0043011990999991950051512004+68750+023550FM-12+038299999V0203201N00671220001CN9999999N9+00221+99999999999 0043011990999991950051518004+68750+023550FM-12+038299999V0203201N00261220001CN9999999N9-00111+99999999999 0043012650999991949032412004+62300+010750FM-12+048599999V0202701N00461220001CN0500001N9+01111+99999999999 0043012650999991949032418004+62300+010750FM-12+048599999V0202701N00461220001CN0500001N9+00781+99999999999


맵 함수에서 처리할 때, 데이터는 다음의 key-value pair로 표현된다. 

(*참고로 여기서 key 값은 파일의 시작점으로부터의 offset이다.)


(0, 0067011990999991950051507004+68750+023550FM-12+038299999V0203301N00671220001CN9999999N9+00001+99999999999)

(106, 0043011990999991950051512004+68750+023550FM-12+038299999V0203201N00671220001CN9999999N9+00221+99999999999) (212, 0043011990999991950051518004+68750+023550FM-12+038299999V0203201N00261220001CN9999999N9-00111+99999999999) (318, 0043012650999991949032412004+62300+010750FM-12+048599999V0202701N00461220001CN0500001N9+01111+99999999999) (424, 0043012650999991949032418004+62300+010750FM-12+048599999V0202701N00461220001CN0500001N9+00781+99999999999)


여기서 우리에게 필요한 데이터는 진하게 칠한 년도와 온도 데이터다.


여기서 key 값은 필요없으니 무시하고, 맵 함수는 년도와 기온을 추출하여 결과값으로 내보낸다.

(1950, 0)

(1950, 22)

(1950, -11)

(1949, 111)

(1949, 78)


이제 이 결과값을 리듀스함수로 보내기 전에 맵리듀스 프레임워크가 이 결과값을 가공한다.

맵리듀스 프레임워크의 가공 과정에서는 데이터를 key 값을 기준으로 정렬하고 그룹화한다.


(1949, [111, 78])

(1950, [0, 22, -11])


각 연도별 기온 리스트는 완성되었으니 리듀스함수에서 이 리스트들을 돌며 최대값을 고르면 완료된다.


(1949, 111)

(1950, 22)


3.2 Java MapReduce


이클립스 설정

우선 맵 리듀스 라이브러리 JAR 파일들을 프로젝트에 추가해야한다.

(* 라이브러리가 root 디렉토리에 있는 경우 이클립스를 관리자 권한으로 실행해야 JAR 파일들이 있는 디렉토리에 접근 가능합니다.)


생선한 프로젝트 우클릭-properties-Java Build Path-Libraries 에서 Add External JARs를 클릭하여 필요한 라이브러리들을 모두 추가한다.


Add External JARs....




이제 이번 예제 구현에 사용된 클래스 코드들을 살펴보도록 하자.


Mapper

import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; public class TestMapClass extends Mapper<LongWritable, Text, Text, IntWritable>{ private static final int MISSING = 9999; @Override public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); String year = line.substring(15,19); int airTemperature; if(line.charAt(87) == '+') { airTemperature = Integer.parseInt(line.substring(88,92)); }else{ airTemperature = Integer.parseInt(line.substring(87,92)); }

//퀄리티 코드 String quality = line.substring(92,93);


//퀄리티 코드 == 01459 : 올바르게 읽어졌음.

//올바르게 읽어진 데이터만 결과에 적용한다. if(airTemperature != MISSING && quality.matches("[01459]")) {

//map() 메소드는 context를 통해 결과값을 전달한다.

//결과값은 Hadoop 데이터 타입으로 변환하여 입력한다. context.write(new Text(year), new IntWritable(airTemperature)); } } }


Mapper 클래스는 제네릭 타입이라서 <input key, input value, output key, output value> 4개의 타입 패러미터를 갖는다.

(쉽게 말해서 템플릿을 사용하기 때문에 이 4개의 타입을 결정해줘야 한다.)


여기서 각 타입은 다음과 같다.

input key = Long int = LongWritable

input value = Text Line = Text

output key = Year = Text

output value = Air Temperature = IntWritable


하둡에서는 자바의 기본 타입을 쓰지 않고 새로운 타입을 제공한다.

LongWritable, Text, IntWritable이 각각 Long, String, Integer에 해당되는 하둡의 클래스 타입들이다.

이 하둡 클래스 타입들은 Serialization에 최적화된 클래스 타입들이다.


Reducer

import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; public class MaxTemperatureReducer extends Reducer<Text, IntWritable, Text, IntWritable>{ @Override public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException{ int maxValue = Integer.MIN_VALUE; for(IntWritable value : values) { maxValue = Math.max(maxValue, value.get()); } context.write(key, new IntWritable(maxValue)); } }


리듀서 클래스도 제네릭 타입이다.

<input key, input value, output key, output value>를 설정해 줘야하는데, 리듀서 함수의 input 타입은 맵 함수의 output 타입과 같아야 하므로 input key는 Text, input value는 IntWritable이다.

리듀서 함수의 output key는 Text, output value는 연도별 최대 온도값을 IntWritable로 내보낸다.


Main Class

import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class MaxTemperature { public static void main(String[] args)throws Exception{ if(args.length != 2) { System.err.println("Usage: MaxTemperature <input path> <output path>"); System.exit(-1); } //버전 업으로 사용법이 바뀜 //Job job = new org.apache.hadoop.mapreduce.Job();

Job job = Job.getInstance();


job.setJarByClass(MaxTemperature.class); job.setJobName("Max Temperature");

//입력 경로, 출력 경로 설정 FileInputFormat.addInputPath(job, new Path(args[0]));; FileOutputFormat.setOutputPath(job,new Path(args[1]));

//매퍼, 리듀서 클래스 설정 job.setMapperClass(TestMapClass.class); job.setReducerClass(MaxTemperatureReducer.class);

//output key, value 타입 설정 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); System.exit(job.waitForCompletion(true) ? 0 : 1); } }


Job 객체는 작업의 세부사항을 정의하고 일이 어떻게 처리되는지에 대한 통제권을 제공하는 객체다.


하둡 클러스터에서 작업을 수행할 때, main 클래스 코드를 JAR 파일로 만들면 하둡이 클러스터 내에서 이 JAR 파일을 분배한다.


Job 클래스의 setJarByClass() 메소드를 사용하여 클래스를 전달하면 하둡이 해당 클래스를 포함한 JAR 파일을 찾아준다.


Job 객체를 생성한 후에는 FileInputFormat 클래스의 addInputPath() 메소드로 input 경로를 정하는데, input 경로는 파일, 디렉토리, 파일 패턴, 무엇이든 될 수  있고, addInputPath() 메소드를 여러번 불러 다양한 경로를 포함시키는 것도 가능하다.


output 경로는 FileOutputFormat 클래스의 setOutputPath() 메소드로 결정한다. 리듀서 함수의 결과가 이 경로에 쓰여지며, 해당 디렉토리는 비어있어야한다.


3.3 A test Run

이제 구현한 코드로 실제 데이터를 처리해 보도록 하자.


JAR 파일로 export

우선 구현한 프로젝트를 JAR 파일로 export 해야한다.

File - Export에서  JAR file을 선택하고 파일 이름을 정한 후 JAR 파일을 만든다.


finish


HDFS에 파일 등록

하둡을 이용해 데이터를 처리하기 위해서는 우선 HDFS(Hadoop Distributed File System)에 데이터 파일을 등록해야한다.


다음 명령어들을 이용하여 이를 수행한다.

# hadoop fs -mkdir '디렉토리 명'

# hadoop fs -put '파일 명' 'HDFS상 디렉토리'

# hadoop fs -ls '디렉토리 명'

mkdir : 디렉토리 생성

put : HDFS에 파일 등록

ls : 디렉토리 내용 출력


HDFS에 입력 파일 디렉토리 생성 및 파일 등록


출력 파일용 디렉토리 생성


분석하기

이제 yarn 명령어를 이용하여 분석을 실행한다.

# yarn '실행시킬 타입' '실행시킬 파일 이름' '실행시킬 클래스 경로' [-옵션]

여기서 우리는 JAR 파일을 실행 할 것이므로 실행시킬 타입은 jar 이다.

실행시킬 파일 이름은 HDFS에 등록된 파일 들 중 입력 파일로 사용할 파일의 이름이다.

실행시킬 클래스 경로는 main 함수를 포함하는 클래스를 (패키지명.클래스명)으로 입력하면 된다.


분석 시작


분석 과정 출력


분석 결과


분석 결과 1949년도 최고 온도는 11.1도, 1950년도 최고 온도는 2.2도로 측정되었다고 한다.


output 디렉토리 입력시 output 디렉토리는 존재하지 않는 디렉토리여야한다.

분석 결과를 출력 할 때 하둡이 입력한 output 디렉토리 이름으로 새 폴더를 만들 것이다.



4. Scaling out


스케일 아웃 작업을 하기 위해서 데이터는 HDFS에 저장되어야 한다.


4.1 데이터 플로우(Data Flow)


* 맵 리듀스 Job 이란 : 클라이언트가 수행되길 원하는 작업들의 묶음이다. 입력 데이터, 맵리듀스 프로그램, 설정 정보로 구성된다.

(A MapReduce job is a unit of work that the client wants to be performed;)


하둡은 맵리듀스 job을 task로 나누어 처리한다.

이 task는 map task와 reduce task로 나뉘고, task들은 YARN에 의해 스케쥴링되고 클러스터상의 노드에서 실행된다.


하둡은 맵 리듀스 job의 input을 input split, 또는 split이라고 불리는 고정크기의 조각들로 나눈다.

하둡은 각 스플릿(split)마다 하나의 태스크를 만들고 각각의 테스크는 사용자 정의 맵함수를 실행시킨다.


이 스플릿의 크기는 너무 작으면 스플릿과 map task를 관리하는 오버헤드가 너무 커지고, 너무 크면 처리하는데 시간이 오래 걸린다.

그래서 적절한 크기로 스플릿을 나누어야 하는데, 일반적으로 HDFS block의 크기인 128MB가 가장 효율적이다.


Data Locality Optimization

하둡은 HDFS 내의 입력 데이터가 있는 노드에서 태스크를 수행할 때 가장 빠르게 작동한다.

이러한 속성을 데이터 지역성 최적화(Data Locality Optimization) 라고 한다.


*Reduce task는 상관없고 Map task에만 영향이 있는 속성이다.



HDFS block의 크기로 스플릿을 나누는 것이 효율적인 이유.

간혹 HDFS 블록 복제본을 호스팅하는 모든 노드들이 이미 다른 맵 태스크를 수행하고 있는 경우 잡 스캐줄러가 같은 랙 장비 내에서 비어있는 맵 슬롯(map slot)을 가진 노드를 찾으려 하게 된다.

더욱 잘 일어나지 않지만, 다른 랙 장비에 있는 노드를 사용해야하는 경우도 있다. 이런 경우는 렉 장비간 네트워크 통신이 필요하게 된다.

이런 상황들에선 성능이 저하된다.

Data Locality Optimization라는 특성도 네트워크 통신으로 인한 성능저하가 발생하기 때문에 생기는 특성이라고 볼 수 있을 것이다.


이제, HDFS Block의 크기(128MB)로 스플릿을 나누는 것이 효율적인 이유에 대해 얘기해보자면, 한개의 노드에 저장 될 수 있는 가장 큰 크기의 데이터가 128MB이기 떄문이다.

128MB보다 큰 데이터를 저장하려고 하면 두개의 블록에 저장하게 될 것이고, 스플릿들 중 일부는 네트워크를 통해 다른 렉 장비에 있는 노드에 저장될 것이다. 네트워크 작업이 늘어나기 때문에 성능이 저하된다.


(사진 출처 : Hadoop the definitive Guide)

Map Task가 HDFS Block에 접근하는 경우의 수

(a) data-local (같은 노드 내에서 처리함. 가장 효율 적인 상황.)

(b) rack-local (같은 랙 장비 내에서, 다른 노드에 있는 맵 슬롯에  데이터를 전송)

(c) off-lack (다른 랙장비에 있는 맵 슬롯에 네트워크를 통해 데이터를 전송)


Map Task는 output을 HDFS에 저장하지 않고 로컬 디스크에 저장한다.

Map Output은 최종결과가 아닌 reduce task에서 입력 데이터로 사용할 중간 결과다. 모든 작업이 끝나면 불필요하기 때문에 HDFS에 저장하지 않는다.



Reduce Task에는 Data Locality 속성의 이점이 없다.

일반적으로 한개의 Reduce Task의 입력은 모든 매퍼들로부터 오기 때문에 Reduce Task가 입력 데이터를 받는 데에는 네트워크 통신이 불가피하기 때문이다.


Reduce ouput의 첫 번째 복사본만 로컬로 저장되고 나머지는 다른 랙 장비에 저장되어 네트워크 대역폭을 소모하는 작업이 된다. 하지만 이는 HDFS 파이프라인이 소모하는 양 만큼만이다.


Reduce Task의 수는 input size와 상관 없이 독립적으로 명시해줘야 한다. 이것에 관해서는 추후에 다루도록 한다.


Reduce Task가 다수일 때에는 Map Task가 output을 partition으로 나누고, 한 파티션당 한 개의 reduce task에 할당된다.

각 파티션에는 다수의 키 값이 존재할 수 있고, 각 키 값들에 대한 레코드는 각 키와 같은 파티션 내에 포함되어야 한다.


(사진 출처 : Hadoop the definitive Guide)

reduce가 한개일 경우 Map Reduce 데이터 플로우.


(사진 출처 : Hadoop the definitive Guide)

reduce가 여러개일 경우 Map Reduce 데이터 플로우.



Reduce Task가 0개 필요한 경우도 있다. 이는 프로세스가 완전히 평행하게 수행 될 수 있어서 Shuffle 작업이 필요 없는 경우에 유용하다. 이 경우 노드를 벗어나서 데이터를 전송하는 경우는 Map Task가 HDFS에 결과값을 보낼 때 뿐이므로 성능도 괜찮게 나올 수 있다.


(사진 출처 : Hadoop the definitive Guide)

Reducer Task가 없는 경우 Data Flow


4.2 Combiner Function

많은 맵리듀스 job들이 클러스터의 이용가능한 네트워크 대역폭 한계 때문에 성능이 저하된다. 그래서 맵과 리듀스 태스크 사이의 데이터 전송을 최소화하는것이 중요하다.


하둡에서는 개발자가 Combiner function을 정의하게 할 수 있다.

Combiner function은 map의 output을 입력으로 받아서 새로운 output으로 만들고, 그 output을 reduce 함수의 input으로 전달하는 역할을 하는 함수다.

Combiner function은 최적화 함수이므로 몇 번 호출되든 상관 없이 reducer 함수의 output은 동일해야한다.


Combiner function의 contract는 사용될 함수의 타입을 제한한다.


Combiner function에 대한 이해를 돕기 위해, 위에서 봤던 연도별 최고 온도 예제를 생각해 보자.

1950년도의 온도 데이터를 2개의 Mapper가 추출해냈다고 하자.


첫 번째 Mapper 결과:

(1950, 0)

(1950, 20)

(1950, 10)


두 번째 Mapper 결과:

(1950, 25)

(1950, 15)


Combiner function을 따로 정의하지 않으면 Reducer Task는 다음의 데이터를 받는다.


(1950, [0, 20 , 10, 25, 15])


그리고 Reducer Task의 최종 output은 


(1950, 25)


일 것이다.


여기서 Mapper Task의 Output 중 최고 온도만 선별하도록 Combiner Function을 정의하면 Reduce Task에는 다음의 데이터만 전송하면 된다.


(1950, [20, 25])


이렇게 하면 맵과 리듀스 태스크 사이의 데이터 전송량이 최소화 되고, Combiner Function을 몇 번 적용하든지 Reduce Task의 결과값이 변하지 않는다.


MAX(0, 20, 10, 25, 15) = 25


MAX(MAX(0, 20, 10), MAX(25, 15)) =MAX(20, 25) = 25


그리고 이 경우에는 Combiner Function을 몇 번 적용하든지 Reduce Task의 결과값이 변하지 않기 때문에 적용해도 되는데, 그렇지 않은 경우에는 주의해야한다.

예를 들어, 최고 온도가 아닌 평균 온도를 구하는 경우를 생각 해보자.

결과는 다음과 같이 변하게 된다.


mean(0, 20, 10, 25, 15) = 14


mean(mean(0, 20, 10), mean(25, 15)) =mean(10, 20) = 15


Combiner Function 적용

Combiner Function은 Reducer 클래스를 사용하여 정의된다.(Reducer 클래스를 상속하여 구현)

최고 온도 예제에서는 Reducer 함수와 Combiner 함수의 역할이 사실상 똑같기 때문에 MaxTemperatureReducer 클래스를 Combiner로 그대로 사용하면 된다.


Main Class (Combiner 포함)

import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class MaxTemperature { public static void main(String[] args)throws Exception{ if(args.length != 2) { System.err.println("Usage: MaxTemperature <input path> <output path>"); System.exit(-1); } //버전 업으로 사용법이 바뀜 //Job job = new org.apache.hadoop.mapreduce.Job();

Job job = Job.getInstance();


job.setJarByClass(MaxTemperature.class); job.setJobName("Max Temperature");

//입력 경로, 출력 경로 설정 FileInputFormat.addInputPath(job, new Path(args[0]));; FileOutputFormat.setOutputPath(job,new Path(args[1]));

//매퍼, 리듀서, Combiner 클래스 설정 job.setMapperClass(TestMapClass.class);

job.setCombinerClass(MaxTemperatureReducer.class); job.setReducerClass(MaxTemperatureReducer.class);

//output key, value 타입 설정 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); System.exit(job.waitForCompletion(true) ? 0 : 1); } }



Running a Distributed MapReduce Job

전체 데이터셋에 대해서 동일한 프로그램을 변환하지 않고 사용해서 처리가능하다.

이것에 관해서는 6장에서 다룬다.


5. 하둡 스트리밍

하둡은 자바 이외의 언어로도 맵, 리듀스 함수를 작성 할 수 있는 API를 제공한다. 그러니 표준 입출력을 사용할 수 있는 어떤 언어든 MapReduce 프로그램 개발에 사용할 수 있다.


스트리밍은 원래 텍스트 프로세싱에 가장 적합하다.

맵 입력 데이터는 표준 입력으로 줄별로 전달되고, 줄별로 출력된다.


맵함수의 Output인 key-value pair는 tab으로 구분되고, 이는 리듀스 함수의 입력에도 동일하게 적용된다.



레퍼런스

Hadoop The Definitive Guide 4th Edition(O'REILLY. Tom White)


하둡 설치

(https://ssup2.github.io/record/Hadoop_%EC%84%A4%EC%B9%98_%EC%84%A4%EC%A0%95_Ubuntu_18.04/)


NCDC 예제 데이터

(https://github.com/tomwhite/hadoop-book/)


Kamang's IT Blog

(https://kamang-it.tistory.com/entry/Hadoop-02%EA%B8%B0%EC%B4%88-%EC%98%88%EC%A0%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0SingleFileWirteRead)

'IT 관련 > Hadoop The Definitive Guide (4th)' 카테고리의 다른 글

Chapter3. The Hadoop Distributed Filesystem  (0) 2019.02.20
Chapter1. Meet Hadoop  (0) 2019.01.17
블로그 이미지

서기리보이

,

1. 퍼사드 패턴이란

1.1 퍼사드 패턴의 정의

어떤 서브시스템의 일련의 인터페이스에 대해 통합된 인터페이스를 제공하는 패턴.

고수준의 인터페이스를 정의하여 서브시스템을 더 쉽게 사용가능하게 함.


퍼사드 패턴은 인터페이스를 단순화시키기 위해서 사용된다.


어떤 작업을 하기 위해서 다수의 인터페이스로 구성된 서브시스템을 사용해야 할 때, 그 서브시스템을 사용하기 쉽게 포괄적인 인터페이스를 정의하는 패턴이다.


여기서 말하는 서브시스템은 실제 구현에서는 표현되지 않았을 수 있고, 전체 시스템 중의 일부분을 묶어서 서브시스템이라고 표현하는 것이다.


UML만으로 설명하면 더 헷갈릴 수 있을 것 같아서 바로 예제로 넘어가도록 하겠다.


2. 예제


Head First Desgin Pattern에 나오는 예제를 수정한 것이다.


한참동안 준비하여 DVD 플레이어, 프로젝터, 자동 스크린, 앰프 오디오, 팝콘기계, 라디오 등을 집에 배치했다.

이를 클래스 다이어그램을 표현하면 다음과 같다.



이제 이 장비들을 이용해 영화를 보고자 한다.

영화를 보려면 몇 가지 일들을 해야한다.


  1. 팝콘 기계를 켠다.

  2. 팝콘를 튀긴다

  3. 프로젝터를 켠다.

  4. 프로젝터에 DVD 신호가 입력되게 연결한다.

  5. 프로젝터를 와이드 스크린 모드로 전환한다.

  6. 앰프 오디오를 켠다.

  7. 앰프를 서라운드 음향 모드로 전환한다.

  8. 앰프 볼륨을 중간(5)로 설정한다.

  9. DVD 플레이어를 켠다.

  10. DVD를 재생한다.


이 작업을 하기 위해서 필요한 코드를 보자.


popcornPopper->on(); popcornPopper->pop(); projector->on(); projector->wideScreenMode(); amplifier->on(); amplifier->setDvd(this->dvdPlayer); amplifier->setSurroundSound(); amplifier->setVolume(5); dvdPlayer->on();


굉장히 복잡하다.

여기서 끝이 아니라 영화가 끝나고 나면 모든 장비들을 특정 순서대로 꺼야하고, 라디오를 들을 때도 복잡한 과정을 거쳐야한다.


퍼사드 패턴을 적용하면 클라이언트 측에서는 이런 복잡한 과정들을 거치지 않아도 된다.

퍼사드 패턴을 적용하면 어떻게 되는지 보자.



HomeTheaterFacade 클래스가 추가되었다.

이제 영화를 보고, 영화 보기를 끝내는 일련의 작업은 HomeTheaterFacade 클래스에서 처리하고, 클라이언트들은 이 클래스 객체에 대한 레퍼런스를 얻어 watchMoive(), endMovie() 메소드를 호출하면 된다.


watchMoive() 메소드와 endMovie() 메소드는 다음과 같이 구현된다.


//HomeTheaterFacade.cpp


void HomeTheaterFacade::watchMovie() { cout << "영화보기 모드 실행" << endl; this->popcornPopper->on(); this->popcornPopper->pop(); this->projector->on(); this->projector->wideScreenMode(); this->amplifier->on(); this->amplifier->setDvd(this->dvdPlayer); this->amplifier->setSurroundSound(); this->amplifier->setVolume(5); this->dvdPlayer->on(); this->dvdPlayer->off(); } void HomeTheaterFacade::endMovie() { cout << "영화 끄기" << endl; this->popcornPopper->off(); this->screen->up(); this->projector->off(); this->amplifier->off(); this->dvdPlayer->stop(); this->dvdPlayer->eject(); this->dvdPlayer->off(); }



구현된 프로젝트는 다음의 깃허브 페이지에서 확인 할 수 있다.

(https://github.com/InvincibleTyphoon/FacadePatternExample/tree/master)


3. 퍼사드 패턴의 장점


- 서브시스템의 복잡한 인터페이스를 단순화된 하나의 인터페이스로 묶을 수 있음

- 클라이언트 구현과 서브시스템을 분리 할 수 있음

- 서브시스템을 캡슐화하지 않기 때문에 서브시스템의 클래스에 직접 접근하여 저수준 기능도 그대로 이용 할 수 있음


퍼사드 패턴을 이용하면 복잡한 서브시스템을 하나의 인터페이스로 묶을 수 있다.


클라이언트의 구현과 서브시스템이 분리되기 때문에 서브시스템의 구현에 변화가 생겨도 클라이언트 코드는 수정하지 않아도 된다.


퍼사드 클래스로 간단하게 원하는 작업을 수행 할 수 있고, 퍼사드 패턴을 무시하고 서브시스템을 구성하는 클래스들을 그대로 사용 할 수도 있다.

퍼사드 클래스를 적용해도 원래의 시스템에서 아무런 제약이 추가되지 않는 것이다.


4. 레퍼런스

- Head First Design Pattern(O'REILLY media)

- GoF의 디자인 패턴(Pearson Addison Wesley)


블로그 이미지

서기리보이

,

본 포스팅은 개인적인 공부를 위해 책의 내용을 요약하여 정리하는 글입니다.

작성자는 분산 데이터베이스, 하둡에 대해서 처음 공부해보는 것이니 참고가 된다면 기쁘겠지만 틀린 정보가 있을 수 있습니다.


사전 지식

- 맵 리듀스(Map Reduce) : 대용량 데이터 처리를 분산 병렬 컴퓨팅에서 처리하기 위해서 제작한 프레임워크



1. Data!


IDC의 예측에 따르면 "디지털 세상"은 4.4 제타바이트에 달한다.(1제타파비트 = 1억테라바이트).

현재는 데이터시데이며 IoT로 인해 생성되는 데이터는 더욱 많아질 것이다.

일부 분야에서는 알고리즘의 퀄리티 보다는 테이터의 양이 더 중요하다.

이런 상황에서 대용량의 데이터를 관리하고, 처리하는 방법에 대한 개발이 매우 중요해졌다.


2. Data Storage and Analysis


기술 발전으로 인해 하드 디스크 등 저장장치의 용량(capacity)은 매년 매우 커지는 중이지만, 드라이브에서 데이터를 읽고 쓰는 접근 속도(access rate)는 크게 발전하지 못하고 있다.

접근 속도를 높이는 방법으로 여러개의 디스크에 데이터를 나눠서 저장해두고 한번에 병렬적으로 읽는 방법이 있다.


다수의 디스크에 병렬적으로 데이터를 저장하는 것의 문제점(어려움)

- 하드웨어 장치가 많으니 그 중에 고장나는 장치가 발생할 가능성이 높다.

- 여러 디스크에 분산 저장된 데이터를 하나로 모아야하는 경우가 발생할 수 있다.


하드디스크 1개에 데이터를 저장할 때 하드디스크가 고장날 확률보다 하드디스크 100개에 데이터를 저장할 때 고장나는 하드디스크가 생길 확률이 더 높다. 장치가 하나라도 고장나면 데이터 전체를 쓸 수없게 될 수도 있는데, 이 문제는 하나의 데이터를 여러 디스크에 중복(replication)되게 저장하여 어느정도 해결 할 수 있다.


그리고 여러 디스크에 분산 저장된 데이터를 하나로 모으는 작업은 매우 어렵다. 맵 리듀스를 사용하면 디스크 읽고 쓰기의 문제를 키값과 value값에 대한 계산문제로 문제를 추상화기 때문에 이러한 어려움이 해결된다.



3. Querying All Your Data


맵 리듀스는 일괄 쿼리 프로세서(batch query processor)이기 때문에 전체 데이터 집합에 대해 비정형 쿼리(ad hoc query)를 보내는 능력과 그 결과를 빠르게 가져오는 능력이 있다.


하지만 반응형 분석에는 부적합하다.


4. Beyond Batch(일괄처리를 넘어서)


맵 리듀스는 일괄처리 시스템이라서 반응형 분석에는 부적합하다. 

하지만 하둡은 반응형 SQL, 반응형 프로세싱, 스트림 프로세싱, 검색 기능과도 잘 동작한다. 맵 리듀스를 활용하여 개발되었지만, 하둡은 일괄처리 프로세싱의 한계를 뛰어넘은 것이다.


YARN의 등장으로 하둡은 일괄처리 프로세싱 모델 이외에 다른 프로세싱 모델들을 적용할 수 있다.

*YARN : 어떤 분산처리 프로그램이라도 하둡 클러스터의 데이터로 실행가능하게 만드는 클러스터 리소스 관리 프로그램


5. Comparison with Other Systems(다른 시스템과의 비교)


5.1 Relational Database Management Systems(RDBMS)

 

RDBMS 

MapReduce 

데이터 크기 

기가바이트(Gigabytes) 단위 처리에 효율적

페타바이트(Petabytes) 단위 처리에 효율적

접근 방식 

반응형 및 일괄처리

(Interactive and batch) 

일괄처리(Batch)

 업데이트

업데이트가 자주 있어도 효율적 

업데이트가 자주 이뤄지지 않고, 읽기가 많을 때 효율적 

트랜잭션 특성 

ACID 

없음 

구조 

Schema-on-write 

Schema-on-read 

Scaling 

비선형(Non-linear) 

선형(Linear) 


* schema on read : 데이터의 스키마를 데이터를 읽는 시점에 확인한다.(schema on write는 반대로 쓰는 시점에 확인)

(참조 : http://datacookbook.kr/90)


맵 리듀스 데이터 병렬 처리

맵 리듀스는 데이터를 분할해 병렬적으로 처리하는 것이 가능하다.


Unstructured/Semi-Structured Data

비정형 데이터(Unstructured Data)는 미리 정의된 방식으로 정리되지 않은 데이터, 반정형 데이터(Semi-Structured Data)는 정형구조를 준수하지 않는 정형 데이터를 말한다.


RDBMS는 미리 정의된 형식을 따르는 정형 데이터(Structured)만 처리 할 수 있지만, 하둡은 Scheam-on-read, 즉 데이터를 읽는 시점에 데이터의 스키마를 확인하기 때문에 비정형 데이터, 반정형 데이터도 처리 할 수 있다.

따라서 유연성이 높다고 할 수 있고, RDBMS에서의 데이터 로딩 단계가 필요하지 않다.


Normalization 

관계형 데이터는 무결성(Integrity) 유지와 중복제거를 위해 자주 정규화(normalization)된다.

하둡에서 정규화를 적용하면 특정 레코드를 읽는 작업이 local opeartion이 아닌 non-local opeartion이 되어 문제가 발생 할 수 있다.

그리고 하둡은 빠른 속도로 스트리밍 읽기/쓰기를 수행할 수 있어야 하기 때문에 그 점도 정규화 적용 시 문제가 된다.


로그 파일이 정규화 되지 않은 대표적인 예시이다. (로그파일이 정규화되지 않는 예시로, 로그파일에는 고객의 이름이 여러 로그에 중복되어 나타나는 경우가 많다.) 그래서 로그파일 분석에는 하둡이 적합하다.



풀리지 않은 의문점

5.1에서 하둡에는 RDBMS에서의 데이터 로딩 단계가 필요하지 않은 이유



레퍼런스

Schema-on-read의 이해 - http://datacookbook.kr/90

비정형 데이터 

https://ko.wikipedia.org/wiki/%EB%B9%84%EC%A0%95%ED%98%95_%EB%8D%B0%EC%9D%B4%ED%84%B0

반정형 데이터 

https://ko.wikipedia.org/wiki/%EB%B0%98%EC%A0%95%ED%98%95_%EB%8D%B0%EC%9D%B4%ED%84%B0


'IT 관련 > Hadoop The Definitive Guide (4th)' 카테고리의 다른 글

Chapter3. The Hadoop Distributed Filesystem  (0) 2019.02.20
Chapter2. Map Reduce  (0) 2019.01.23
블로그 이미지

서기리보이

,

1. 어댑터 패턴이란

1.1 어댑터 패턴의 정의

특정 클래스의 인터페이스를 클라이언트가 기대하는 다른 인터페이스로 변환하는 패턴


어댑터 패턴은 클라이언트가 요구하는 인터페이스와 호환되지 않는 인터페이스를 가져서 함께 동작 할 수 없는 클래스를 함께 동작 할 수 있게 해준다.

그리고 어댑터패턴은 오브젝트 어댑터(Object Adapter)와 클래스 어댑터(Class Adapter)로 구현방법이 나뉘어지는데,  클래스 어댑터가 가지는 몇 가지 단점 때문에 이 글에서는 오브젝트 어댑터를 주로 다루려고 한다.



1.2 어댑터 패턴에 대한 대략적 이해


A라는 클래스를 B라는 클래스와 함께 동작하게 하고 싶지만,  B 클래스와 함께 동작하려면 C 인터페이스 형태를 따르도록 구현되어야 한다.

하지만 기존 코드를 수정하는 것(A 클래스가 C 인터페이스를 상속하도록 수정하거나 B 클래스가 A 클래스와 함께 동작할 수 있도록 수정하는 것)은 비효율적이고 오류를 일으키는 원인이 되는 일이므로 A 클래스를 C 인터페이스에 맞게 변환해주는 Adapter를 중간에 추가하여 문제를 해결한다.



그러면 A 클래스가 B 클래스와 함께 동작 할 수 있다.


1.3 오브젝트 어댑터(Object Adapter)

오브젝트 어댑터는 구성(Composition)을 사용하여 어댑터 패턴을 구현한다. UML과 같이 보도록 하자.



Client - Target 인터페이스를 요구하는 요소를 지닌 클래스

Target - 어떤 인터페이스

Adaptee - Client의 Target 인터페이스를 요구하는 요소에 집어넣고 싶은 클래스. Target 인터페이스에 호환되지 않음.

Adapter - Adaptee 클래스를 Target 인터페이스에 맞춰주는 클래스


여기서 Adapter는 Target 인터페이스의 request() 메소드를 구현 하기 위해 Adaptee 클래스의 method1() 메소드를 사용 할 것이다.

그러면 Client는 Adapter 클래스를 통해 Adaptee를 Target 인터페이스의 구상 클래스처럼 사용할 수 있다.


1.4 클래스 어댑터(Class Adapter)

클래스 어댑터는 상속(Inheritance)를 사용하여 어댑터 패턴을 구현한다. 이것도 UML과 같이 보도록 하자.



Client - Target 인터페이스를 요구하는 요소를 지닌 클래스

Target - 어떤 인터페이스

Adaptee - Client의 Target 인터페이스를 요구하는 요소에 집어넣고 싶은 클래스. Target 인터페이스에 호환되지 않음.

Adapter - Adaptee 클래스를 Target 인터페이스에 맞춰주는 클래스


클래스 어댑터는 다중 상속을 사용한다.

클래스 어댑터 패턴에서는 Adapter가 Target 클래스와 Adaptee 클래스를 둘 다 상속하여 Target 클래스가 필요한 곳에서도 사용 될 수 있고 Adaptee 클래스가 필요한 곳에서도 사용 될 수  있게 한다.


2. 예제

게임을 개발했다고 생각 해 보자.

개발하려는 게임은 정말 간단해서, 몬스터와 액티브 아이템, 그리고 UI로만 구성된다.


몬스터와 액티브아이템, UI는 각각 다른 인터페이스로 구현된다.

개발 초기에 클래스 다이어그램은 다음과 같을 것이다.




유저 인터페이스 클래스에서는 setActiveItem() 메소드로 사용할 액티브아이템을 결정하고, useItem() 메소드를 실행하면 ActiveItem 인터페이스의 useItem() 메소드를 이용하여 액티브아이템의 효과를 사용한다.


몬스터는 grawl() 메소드를 사용하면 울부짖는 소리를 낸다. 울부짖을때는 grawlSound 어트리뷰트에 저장된 소리로 울부짖는다.


그런데 게임 기획자가 새로운 아이디어를 떠올렸다. 유저가 몬스터를 포획하여 액티브아이템처럼 몬스터를 사용할 수 있게 하자는 것이다.

몬스터를 포획하면 몬스터가 액티브아이템으로 등록되고, 이 아이템을 사용하면 유저에게 몬스터가 울부짖는 소리를 들려준다.


이를 구현하고자 할 때 문제는 유저 몬스터 클래스는 유저 인터페이스의 setActiveItem() 메소드에 호환되지 않는다는 것이다.


위에서 언급했듯이, UserInterface에 setItem(Monster) 메소드를 추가하거나, Monster 인터페이스가 ActiveItem 인터페이스를 상속하게 하는 등 기존 코드를 수정하는 것은 좋은 선택이 아니다.


각각의 몬스터별로 새로운 아이템 클래스를 만드는 것도 좋은 방법이지만 그러면 몬스터의 숫자만큼 클래스의 수가 늘어나게 되기 때문에 더 좋은 방법이 있었으면 한다.


더 좋은 방법으로서 어댑터 패턴을 적용해보자.

몬스터를 액티브아이템처럼 사용될 수 있도록 적절한 어댑터를 만들어 클래스 구조를 다음과 같이 만든다.



MonsterToActiveItemAdapter가 바로 Monster 인터페이스를 ActiveItem 인터페이스처럼 사용 될 수 있게 해주는 클래스다.


MonsterToActiveItemAdapter 클래스의 useItem() 메소드는 Monster 인터페이스의 grawl() 메소드를 호출하도록 구현된다.

그래서 붙잡은 몬스터를 액티브아이템 처럼 사용 할 수 있다.


구현된 프로젝트는 다음의 깃허브 페이지에서 확인 할 수 있다.

(https://github.com/InvincibleTyphoon/AdapterPatternExample/tree/master)


3. 어댑터 패턴의 장점

- 인터페이스 호환성 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있음

- 상속이 아닌 구성(Composition)을 이용하기 때문에 Adaptee의 모든 서브클래스에 대해 어댑터를 사용가능(Object Adapter에만 해당)


인터페이스가 호환되지 않는 문제가 발생해도 어댑터 패턴을 적용하면 기존의 코드를 수정하지 않고도 문제를 해결 할 수 있다.


Class Adapter에는 적용되지 않는 장점이지만, 상속이 아닌 구성을 사용하기 때문에 Adaptee의 모든 서브클래스에 대해 어댑터를 적용 할 수 있고, 실행중에 동적으로 Adaptee를 바꿀수도 있다.



4. 오브젝트 어댑터 VS 클래스 어댑터

위의 예제에서는 오브젝트 어댑터를 적용했다.

오브젝트 어댑터와 클래스 어댑터를 비교했을 때, 서로다른 장단점이 있는데 우선 그 차이점을 비교해보자.

 

 Ojbect Adapter

Class Adapter 

장점 

상속이 아닌 구성(Composition)을 사용하기 때문에 더 유연하다 

Adapter가 Adaptee의 서브클래스이기 때문에 Adaptee의 행동을 오버라이드 할 수 있다.


Adaptee 객체를 만들지 않아도 된다. 

단점

 Adaptee 객체를 만들어야 사용가능하다.

상속을 이용하기 때문에 한 Adapter 클래스가 특정 Adatee 클래스에만 적용가능하다.


다중상속이 지원되는 언어에서만 사용가능하다. 



Object Adapter는 상속이 아닌 구성을 사용하기 때문에 더 유연하다. 하지만 Adaptee 객체를 만들어야하기 때문에 Class Adapter에 비해 관리해야할 객체가 하나 늘어난다는 단점이 있다.


Class Adapter는 Adapter가 Adaptee의 서브클래스이기 때문에 Adaptee의 행동을 오버라이드하여 사용 할 수 있고. Adaptee 객체를 따로 만들지 않고 Adapter 객체를 만들어서 사용가능하다는 장점이 있다. 관리해야 할 객체가 하나 적다는 것이다.

하지만 Class Adapter는 상속을 사용하기 때문에 한 Adapter 클래스가 특정 Adaptee 클래스 하나에 대해서만 적용된다. 그 Adaptee클래스의 서브클래스에 대해서 적용 불가능하고, 실행 중간에 동적으로 변경도 불가능한 것이다.

그리고 이것은 치명적인 단점이라고 할 수 있는데, 자바처럼 다중상속이 지원되지 않는 언어에서는 사용이 불가능하다.


개인적인 견해로 말하자면 유연성 측면에서 Object Adapter의 장점이 Class Adapter의 장점보다 낫고, Object Adapter의 단점은 그다지 치명적이거나 하지 않다고 본다.

상황에 따라서 어떤 방법을 쓸 지 선택애햐겠지만, 이 두가지 방법 중 한가지를 선택해야하는 상황이라면 나는 Object Adapter를 사용할 것 같다.


Head First Design pattern에서 클래스 어댑터는 객체어댑터에 비해 Adaptee 전체를 다시 구현하지 않아도 된다는 장점을 가진다고 하는데, 이에 대해 객체 어댑터도 Adaptee에게 필요한 일을 시키는 코드만 구현하면 된다고 답하는 내용이 있는 걸로 봐서, 클래스 어댑터 패턴이 Adaptee에게 일을 시키는 코드를 구현하지 않다도 되는게 장점이라는 의미로 그렇게 언급하는 것 같다.





5. 레퍼런스

- Head First Design Pattern(O'REILLY media)

- 위키피디아 Adapter Pattern(https://en.wikipedia.org/wiki/Adapter_pattern)

블로그 이미지

서기리보이

,