파이썬 기본 문법을 정리해보았습니다.
  기존에 개발을 해 보신 분들이, 예제를 통해 대략적으로 파이썬 기본 문법을 파악하기 위한 용도입니다.
  따라서, 긴 설명 없이 항목 별 예제를 중심으로 정리하였으며, 추가적인 설명이 필요한 경우에만 간략하게 정리하였습니다.

  각 '주제'를 클릭하면 관련 내용이 명시된 파이썬 자습서 홈페이지로 이동합니다.

 


 

파이썬 인터프리터

  - 대화형 모드로 동작

 

 


문자열 연산 : 불변

# 문자열 연산
>>> word = 'Python'
>>> word[0]
'P'
>>> word[-2]
'o'
>>> word[:2]
'Py'
>>> word[2:5]
'tho'
>>> 3 * word[0] + word[1:4] + '~~~' + word[4:]
'PPPyth~~~on'
>>> len(word)
6

 

 


if 문

>>> x = int(input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
...         x = 0
...         print('Negative changed to zero')
...     elif x == 0:
...         print('Zero')
...     elif x == 1:
...         print('Single')
...     else:
...         print('More')
...
More

 

for 문

>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words:
...         print(w, len(w))
...
cat 3
window 6
defenestrate 12
>>> for n in range(2, 10):
...         for x in range(2, n):
...             if n % x == 0:
...                 print(n, 'equals', x, '*', n//x)
...                 break
...         else:
...             print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

 

 


리스트 연산

  - Compound 데이터 타입 중 대표적
  - 가변인 시퀀스형
  - 대괄호나 list 함수를 이용하여 생성

>>> squares = [1, 4, 9, 16]
>>> squares 
[1, 4, 9, 16]
>>> squares[2]
9
>>> squares + [36, 49]
[1, 4, 9, 16, 36, 49]
>>> squares.append(50)
>>> len(squares)
5
>>> x = [squares, squares]
>>> x[0]
[1, 4, 9, 16, 50]
>>> x[0][1]
4
>>> lst = list('abcde')
>>> lst
['a', 'b', 'c', 'd', 'e']

 

 

리스트 메소드

append(x) 리스트의 끝에 항목 추가. a[len(a):] = [x] 와 동일.
extend(iterable) 리스트의 끝에 이터러블의 모든 항목을 추가. a[len(a):] = iterable 와 동일
insert(i, x) 주어진 위치에 항목을 삽입. 첫 번째 인자는 삽입 위치(인덱스). 두 번째 인자는 삽입할 값.
a.insert(len(a), x)와 a.append(x) 와 동일함.
remove(x) 리스트에서 값이 x첫 번째 항목을 삭제.
pop([i]) 리스트에서 주어진 위치에 있는 항목을 삭제하고, 그 항목을 return.
인덱스를 지정하지 않으면
리스트의 마지막 항목을 삭제하고 return.
clear() 리스트의 모든 항목을 삭제. del a[:] 와 동일
index(x[, start[, end]]) 리스트에 있는 항목 중 값이 x 와 같은 첫 번째 항목의 인덱스를 return.
count(x) 리스트에서 x 의 전체 건수를 return.
list.sort(key=None, reverse=False) 리스트의 항목을 정렬.
reverse() 리스트의 요소의 순서를 역으로 변경.
copy() 리스트의 사본 반환. a[:] 와 동일.

 

 

리스트 컴프리헨션 (Comprehension)

>>> squares = [x**2 for x in range(10)] 
>>> squares 
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> squares = list(map(lambda x: x**2, range(10))) 
>>> squares 
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> squares = [] 
>>> for x in range(10): 
...        squares.append(x**2)
... 
>>> squares 
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

 

>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
>>> combs = [] 
>>> for x in [1,2,3]: 
...         for y in [3,1,4]: 
...             if x != y: 
...                  combs.append((x, y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

 

>>> matrix = [
...         [1, 2, 3, 4],
...         [5, 6, 7, 8],
...         [9, 10, 11, 12],
... ]
>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
>>> transposed = []
>>> for i in range(4):
...          transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
>>> transposed = []
>>> for i in range(4):
...         transposed_row = []
...         for row in matrix:
...             transposed_row.append(row[i])
...         transposed.append(transposed_row)
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
>>> list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

 

>>> vec = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

 

 

튜플

  - 불변인 시퀀스형.
  - 괄호나 tuple 함수를 사용하여 생성.

>>> tu = 12345, 54321, 'hello!'
>>> tu
(12345, 54321, 'hello!')
>>> tu[0]
12345
>>> v1, v2, v3 = tu
>>> v1
12345
>>> v2
54321
>>> v3
'hello!'
>>> tu = tuple('abcde')
>>> tu
('a', 'b', 'c', 'd', 'e')

 

집합

  - 중복 없는 유일한 값들이 순서에 상관없이 저장되는 컬렉션.
  - 중괄호{}나 set() 함수를 사용하여 생성.

>>> chr1 = {'a', 'z', 'z', 'r', 'd', 'j', 'd'}
>>> chr1
{'j', 'd', 'r', 'z', 'a'}
>>> chr2 = set('arojxg')
>>> chr2
{'j', 'o', 'g', 'r', 'x', 'a'}
>>> chr1 - chr2
{'z', 'd'}
>>> chr1 & chr2
{'j', 'r', 'a'}
>>> chr1 | chr2
{'j', 'o', 'd', 'x', 'r', 'z', 'g', 'a'}
>>> chr1 ^ chr2
{'o', 'x', 'd', 'z', 'g'}

 

>>> a = {x for x in 'abracadabra' if x not in 'abc'} 
>>> a 
{'r', 'd'}

 

 

딕셔너리

  - 매핑 데이터 형. 가변형 임.
  - {키:값} 쌍의 형태로 관리되며, 키로 인덱싱함.
  - 중괄호{}나 dict() 함수를 사용하여 생성. 

>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'jack': 4098, 'sape': 4139, 'guido': 4127}
>>> tel['jack']
4098
>>> list(tel)
['jack', 'sape', 'guido']
>>> sorted(tel)
['guido', 'jack', 'sape']

 

>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}

 

 


사용자 함수 

>>> def fib(n): 
...        """Print a Fibonacci series up to n. <-- Docstring"""
...         a, b = 0, 1 
...         while a < n: 
...             print(a, end=' ') 
...              a, b = b, a+b 
...         print() 
... 
>>> 
>>> fib(2000) 
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
>>> def fib2(n, a=0, b=1): 
...          """Return a list containing the Fibonacci series up to n.""" 
...          result = [] 
...          n1, n2 = a, b 
...          while n1 < n:
...               result.append(n1)
...               n1, n2 = n2, n1+n2 
...          return result 
... 
>>> f100 = fib2(100) 
>>> f100 
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> def cheeseshop(kind, *arguments, **keywords): 
...         print("-- Do you have any", kind, "?") 
...         print("-- I'm sorry, we're all out of", kind) 
...         for arg in arguments: 
...             print(arg) 
...         print("-" * 40) 
...         for kw in keywords: 
...             print(kw, ":", keywords[kw]) 
...
>>> cheeseshop("Limburger", "It's very runny, sir.", 
           "It's really very, VERY runny, sir.", 
           shopkeeper="Michael Palin", 
           client="John Cleese", 
           sketch="Cheese Shop Sketch") 
-- Do you have any Limburger ? 
-- I'm sorry, we're all out of Limburger 
It's very runny, sir. 
It's really very, VERY runny, sir. 
---------------------------------------- 
shopkeeper : Michael Palin 
client : John Cleese 
sketch : Cheese Shop Sketch

 

 


모듈

  - 함수, 클래스, 변수가 저장된 파일. import 하여 사용.
  - 파일명 형식 : [패키지명\]모듈명.py
  - 모듈 검색 경로
     1) 내장 모듈 검색
     2) sys.path
        . 입력 스크립트를 포함하는 디렉터리 (또는 파일이 지정되지 않았을 때는 현재 디렉터리).
        . PYTHONPATH (디렉터리 이름들의 목록, 셸 변수 PATH 와 같은 문법).
        . 설치 의존적인 기본값

${PYTHONPATH}\pkg\fibo.py
# Fibonacci numbers module

def fib(n):    # write Fibonacci series up to n
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

def fib2(n):   # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

if __name__ == "__main__":  #__main__ : executed by script
    import sys 
    fib(int(sys.argv[1]))

 

 

# 패키지명 : pkg, 모듈명 : fibo

>>> import pkg.fibo
>>> pkg.fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> pkg.fibo.fib2(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> pkg.fibo.__name__
'pkg.fibo'
>>> from pkg.fibo import fib, fib2
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
>>> fib2(50)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

 - 모듈을 스크립트로 실행

D:\90_Workspace_Python\pkg>python fibo.py 50

 

 


클래스

  - 파이썬 클래스는 객체 지향형 프로그래밍의 모든 표준 기능들(클래스 상속, 매서드 재정의 등)을 제공

${PYTHONPATH}\myclass.py
class MyClass:

    def __init__(self, msg):
        print("Init message of MyClass")
        self.__msg = msg

    def setmsg(self, msg):
        self.__msg = msg
    
    def printmsg(self):
        print(self.__msg)
    
    def getmsg(self):
        return self.__msg


class MyClassSub(MyClass):

    def __init__(self, msg):
        print("Init message of MyClassSub")
        super().__init__(msg)
    
    def printmsg(self):
        print("Message is \"{}\"".format(super().getmsg()))

>>> from myclass import MyClass, MyClassSub
>>> m = MyClass('test...')
Init message of MyClass
>>> m.printmsg()
test...
>>> m.getmsg()
'test...'
>>> m.setmsg('new message')
>>> m.printmsg()
new message
>>> m.getmsg()
'new message'
>>> ms = MyClassSub('Sub Message')
Init message of MyClassSub
Init message of MyClass
>>> ms.printmsg()
Message is "Sub Message"
>>> ms.getmsg()
'Sub Message'

  - __init()__ : 클래스의 생성자 실행 시 자동으로 init__() 를 호출. 

 

 


입출력

  - 포맷 문자열 리터럴(formatted string literal) 또는 f-문자열 (f-string)
     . 'f' 나 'F' 를 앞에 붙인 문자열 리터럴
     . 치환 필드 포함 가능
     . 중괄호 {} 로 구분되는 표현식
     . 실행시간에 계산되는 표현식

>>> year = 2020
>>> event = 'Referendum'
>>> f'Results of the {year} {event}'
'Results of the 2020 Referendum'
>>> yes_votes = 42_572_654
>>> no_votes = 43_132_495
>>> percentage = yes_votes / (yes_votes + no_votes)
>>> '{:-9} YES votes {:2.2%}'.format(yes_votes, percentage)
' 42572654 YES votes 49.67%'
>>> import math
>>> print(f'The value of pi is approximately {math.pi:.3f}.')
The value of pi is approximately 3.142.
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...         print(f'{name:10} ==> {phone:10d}')
...
Sjoerd     ==>       4127
Jack        ==>       4098
Dcab       ==>       7678
>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"
>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam
>>> print('This {food} is {adjective}.'.format(
...     food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.
>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred', other='Georg'))
The story of Bill, Manfred, and Georg.
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
...            'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678
>>> for x in range(1, 6):
...         print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...         print(repr(x*x*x).rjust(4))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125

 

 

파일 입출력

  - open(filename, mode)
    . mode : r (readonly, 기본 값), w (writeonly), a (append), r+ (read and write), b (binary mode)

>>> f = open('d:\\testfile', 'r+')
>>> read_data = f.read()
>>> read_data
'This is the first line of the file.\nSecond line of the file\n'
>>> f.readline()
''
>>> f.seek(0)
0
>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''
>>> f.write('This is a test\n')
15
>>> f.seek(0)
0
>>> f.read()
'This is the first line of the file.\nSecond line of the file\nThis is a test\n'
>>> f.closed
False

 

 


예외 처리

>>> import sys
>>>
>>> try:
...         f = open('myfile.txt')
...         s = f.readline()
...         i = int(s.strip())
... except OSError as err:
...         print("OS error: {0}".format(err))
... except ValueError:
...         print("Could not convert data to an integer.")
... except:
...         print("Unexpected error:", sys.exc_info()[0])
...         raise
...
OS error: [Errno 2] No such file or directory: 'myfile.txt'

 

 

 


파이썬 스타일 가이드

 

PEP 8 -- Style Guide for Python Code

The official home of the Python Programming Language

www.python.org

 

  • 들여쓰기 단위 : 공백 4칸. 탭 사용 불가.

  • 한 줄 길이 : 최대 79자.

  • 함수, 클래스, 함수 내의 큰 코드 블록 사이는 빈 줄을 넣어 구분.

  • 가능하다면, 주석은 별도의 줄로 입력.

  • DocString 사용을 권장.

  • 연산자들 앞/뒤와 콤마 뒤에는 공백 입력.
    괄호 바로 안쪽에는 공백 불가.
    예) a = f(1, 2) + g(3, 4)

  • 명명 규칙
    . 상수 : UPPER_CASE_WITH_UNDERSCORES.
    . 패키지 : 짧은 소문자 이름 권고. 밑줄은 사용 불가.
    . 모듈 : 짧은 소문자 이름 권고. 밑줄은 사용가능하지만 지양할 것.
    . 클래스 : UpperCamelCase.
    . 함수와 메소드 : lowercase_with_underscores.
    . 메소드의 첫 번째 인수는 항상 self를 사용.
    . 클래스 메소드의 첫 번째 인수에는 항상 cls를 사용.

  • 파이썬의 배포판 코드는 항상 UTF-8이나 파이썬2의 ASCII를 사용할 것.

 

[Ref.] https://docs.python.org/ko/3/tutorial/index.html
        https://docs.python.org/3/tutorial/index.html

 

  CentOS 8 환경(VirtaulBox를 이용한 가상 환경)에서 Spark를 설치해보겠습니다.

  이 포스트를 작성하는 시점에 최종 릴리즈 버전은 2.4.6이지만,
  테스트 환경의 자바 버전이 JDK 7인 관계로 2.1.3 버전을 설치하고자 합니다.

  설치 환경 정보는 아래와 같습니다.

- Spark : 2.1.3
- Hadoop : 2.10.0
- OS : CentOS 8 (VirtualBox 6)
- Java : JDK 7 

 

1. Spark 설치 파일 다운로드

  Spark 설치 파일을 다운로드하기 위해 아래 spark 다운로드 사이트에 접속합니다.

  *  URL : https://spark.apache.org/downloads.html

  이번에 설치할 Spark 2.1.3 버전은 최신 릴리즈 버전이 아니므로 홈페이지 하단에 'Spark release archives'를 클릭하여 아카이브 페이지에 접속합니다.

 

  아카이브의 Spark-2.1.3 하위 디렉토리에 접속하면 위와 같은 설치 파일을 확인하실 수 있습니다.

  이 포스트에서는 Hadoop이 설치된 환경에서 Spark를 설치할 계획이므로 'spark-2.3.4-bin-without-hadoop.tgz' 파일을 사용하도록 하겠습니다.

 

  위 설치파일의 링크 주소를 복사한 뒤 wget을 이용해 파일을 로컬에 다운로드합니다.

 

> sudo wget https://archive.apache.org/dist/spark/spark-2.1.3/spark-2.1.3-bin-without-hadoop.tgz

  참고로 Hadoop 설치 방법은 아래 포스트에서 확인할 수 있습니다.

 

하둡(Hadoop) 설치하기[#1] - 설치 준비

데스크탑이 너무 느려져서 포맷을 했더니, VirtualBox에서 하둡 관리노드가 있던 가상 OS만 복구가 안되는 상황을 맞이하게 되었습니다.;; 이번 기회에 다시 한번 하둡을 설치하면서 여기에 그 과정

sparkdia.tistory.com

 

 

2. 설치 파일 압축 해제

  다운로드 한 파일을 압축해제합니다.

 

  > sudo tar -xvzf ./spark-2.1.3-bin-without-hadoop.tgz

 

  압축 해제된 파일의 소유자를 변경하고 디렉토리명을 spark-2.1.3으로 변경합니다.

  > sudo chown -R hduser:hadoop ./spark-2.1.3-bin-without-hadoop
  > sudo mv spark-2.1.3-bin-without-hadoop spark-2.1.3

 

 

3. 환경 변수 설정

  사용자 홈 디렉토리 내 .bashrc 파일을 열어 아래와 같이 환경변수와 PATH를 추가해줍니다.

export SPARK_HOME=/usr/local/spark-2.1.3
export PATH=$PATH:$SPARK_HOME/bin

 

  하둡이 포함되지 않은 설치 파일을 다운로드한 경우 하둡의 CLASSPATH를 설정해줘야 합니다.

  $SPARK_HOME/conf/spark-env.sh 파일을 생성하고 아래와 같이 입력합니다.

export SPARK_DIST_CLASSPATH=$(${HADOOP_HOME}/bin/hadoop classpath)

 

 

4. 실행 테스트 1

  ${SPARK_HOME}/bin./spark-shell을 실행하여 설정이 정상적으로 완료되었는지 확인해봅니다.

  > spark-shell

  위와 같은 Welcome 메시지가 출력되면 정상적으로 설정된 것입니다.

 

  만약, spark-shell 실행 시 아래와 같은 에러 로그가 출력된다면 SPARK_DIST_CLASSPATH 설정이 잘못된 것입니다.

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/hadoop/fs/FSDataInputStream

 

  위와 같은 에러가 발생한다면 점검을 위해 직접 쉘 프롬프트에서 classpath 설정 구문을 테스트해보는 것도 하나의 해결 방법이 될 것입니다.

  > export SPARK_DIST_CLASSPATH=$(${HADOOP_HOME}/bin/hadoop classpath)
  > echo $SPARK_DIST_CLASSPATH

 

  아래와 같이 실제로 hadoop 명령어가 제대로 작동되는지 확인하는 것도 하나의 해결 방법이 될 수 있습니다.

 

 > ${HADOOP_HOME}/bin/hadoop classpath

 

 

5. Spark 서비스 시작 및 종료

  ${SPARK_HOME}/sbin/start-all.sh을 실행하면 Spark 서비스를 시작할 수 있습니다.

  > ${SPARK_HOME}/sbin/start-all.sh

  jps 실행 시 Master와 Wroker 프로세스가 확인된다면 정상적으로 Spark 서비스가 시작된 것입니다.

 

  웹 브라우저에서 아래 URL(기본 포트 : 8080)을 통해 Spark Web UI를 확인하실 수 있습니다.

* URL : http://서버IP:8080

 

  Spark 서비스 종료는 ${SPARK_HOME}/sbin/stop-all.sh을 실행하면 됩니다.

  > ${SPARK_HOME}/sbin/stop-all.sh

 

 

6. 실행 테스트 2

  spark-submit을 통해서 작성된 Spark application을 실행할 수 있습니다.

  Spark에서 제공하는 SparkPi 샘플 프로그램을 yarn 환경에서 cluster 모드로 실행하는 명령어는 아래와 같습니다.

spark-submit --class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode cluster \
${SPARK_HOME}/examples/jars/spark-examples*.jar \
10

  위 명령어를 실행하면 아래와 같이 Hadoop resource manager 웹 페이지에서 spark application이 실행된 것을 확인하실 수 있습니다.

 

 

[Ref.] https://spark.apache.org/docs/2.1.3/hadoop-provided.html

 

'BigData > Spark' 카테고리의 다른 글

스파크(Spark) 3.0.0 설치 on Windows 10  (0) 2020.06.28
스파크(Spark) 실행하기 on Databricks  (2) 2020.06.22

 

  하이브에서는 다양한 형식의 파일을 읽고 저장할 수 있도록 테이블 별로 파일 형식을 지정할 수 있습니다.

 

 

하이브(Hive) 파일 형식

하이브에서는 테이블 생성 시 'STORED AS 저장형식명' 옵션을 사용하여 테이블별로 파일 형식을 지정할 수 있습니다. 파일 형식을 그냥 쉽게 표현하자면 하이브에서 테이블 형태로 보여지는 데이��

sparkdia.tistory.com

  하이브에서 기본적으로 제공하는 파일 형식 중에서 현재 가장 많이 사용되는 것이 ORC 파일 형식입니다.

  ORC 파일 형식은 컬럼 기반으로 데이터를 저장하며, 기존의 컬럼 기반 저장 방식을 사용하는 RCFile을 개선한 파일 형식입니다. ( ※ 컬럼 기반 저장 방식은 위 하이브(Hive) 파일 형식 포스트를 참고 바랍니다. )

 

   RCFile과 비교하였을 때 ORC의 장점은 아래와 같습니다.

- 각 태스크의 결과가 하나의 파일로 생성되어 네임노드의 부하가 감소됨.
- Datetime, Decimal, complex types (struct, list, map, and union)등의 하이브 데이터 타입을 지원함.
- 파일에 경량 인덱스가 저장됨.
. 필터링 조건에 부합하지 않는 로우 그룹은 skip함. 
. 특정 row로 바로 탐색이 가능.
- 데이터 타입 기반의 block-mode 압축. 
. Integer 컬럼은 run-length encoding을 사용함.
. String 컬럼은 dictionary encoding을 사용함.
- 하나의 파일을 여러 개의 레코드리더를 사용해 동시 읽기 가능함
- 마커 스캐닝 없이 파일 분할이 가능함.
- 읽거나 쓰기에 필요한 메모리의 용량을 제한 가능함
- 메타 데이터를 Protocol Buffers를 사용해 저장함으로써 필드의 추가/삭제가 가능함.

 

 

파일 구조

  ORC의 파일 구조는 아래와 같습니다.

 

Stripe

  컬럼 기반 저장 방식에서는 저장 시 테이블의 데이터를 우선 수평적으로 분할합니다. 분할 기준은 로우 그룹의 크기입니다. 분할된 각각의 로우 그룹 내 데이터를 컬럼 기준으로 저장하게 됩니다. 

  이렇게 분할된 로우 그룹이 Stripe이며, Index Data, Row Data, Stripe Footer로 구성됩니다.

 

Index data

  Index data에는 각 컬럼의 최솟값, 최댓값과 각 컬럼 내 로우의 위치를 저장하고 있습니다. 

  필터링 조건에 의해 테이블의 일부 데이터만 추출하는 경우, 이 Index data를 참고하여 조건에 맞지 않은 로우 그룹은 skip하고 조건에 맞는 로우 그룹만 액세스하게 됩니다.

 

Row data

  실제 테이블 데이터가 저장되어 있습니다.

 

Stripe footer

  스트림 위치 정보가 저장되어 있습니다.

 

File footer

  File footer에는 아래와 같은 보조 정보가 저장되어 있습니다.

  • 파일 내 stripe의 목록
  • stripe별 로우의 수
  • 각 컬럼의 데이터 타입
  • 컬럼 레벨별 통계 정보(건수, 최솟값, 최댓값, 합계)

 

Postscript

  파일의 가장 끝에 위치하며, 압축된 footer의 크기와 압축 파라미터 정보가 저장됩니다.

 

 

 

Integer 컬럼 직렬화 (Integer Column Serialization)

  ORC에서 Interger 컬럼은 두 개의 스트림, bit stream(해당 값이 not-null인지 표현)과 data stream(integer의 스트림)으로 직렬화(serialize)됩니다.

  Interger 데이터는 아래와 같이 숫자의 특징에 따른 encoding 방식이 적용되어 직렬화됩니다.

  •   interger 값이 작아 데이터의 크기가 경량인 경우 variable-width encoding을 사용.
  •   값이 반복되는 경우 run-length encoding을 사용.
  •   정수 값의 범위가 -128에서 127인 경우 run-length encoding을 사용.

  

Variable-width encoding 

  Variable-width encoding은 Google의 프로토콜 버퍼 기반입니다.

  가변 폭 데이터를 인코딩하므로 각 Integer 값의 byte 크기는 각각 상이할 것입니다. 인코딩되는 데이터의 크기가 서로 다른 값들을 구분하기 위해 각 byte의 최상위 bit를 사용합니다. 각 byte의 최상위 bit는 현재 byte가 인코딩하는 데이터의 마지막 바이트를 나타냅니다. 하위 7bit는 인코딩된 데이터를 나타냅니다.

 

Run-length encoding

  Run-length encoding은 반복되는 문자를 반복회수로 표현하는 압축 방법입니다.

  예를들어, 문자열 'AAAAABBBCCCCCCCCDDDEEEEEEEE'인 경우에 Run-length encoding을 적용하면 '5A3B7C3D8E'로 인코딩 됩니다.

 

 

 

문자열 컬럼 직렬화 (String Column Serialization)

문자열 컬럼은 컬럼의 unique한 값들로 구성된 딕셔너리를 사용하여 직렬화됩니다. 딕서너리 내 값들은 정렬되어 있어 predicate filtering의 속도가 높아지고 압축률이 향상됩니다.

  문자열 컬럼은 아래 4개의 스트림으로 직렬화됩니다. 

  •   bit stream : 값이 not-null인지 표현
  •   dictionary data : 문자열의 크기(byte)
  •   dictionary length : 각 항목의 길이
  •   row data : 로우의 값 

 

 

압축 (Compression)

  ORC 파일 포맷을 사용하는 테이블의 모든 스트림은 지정된 코덱을 사용하여 압축됩니다. 압축 코덱은 테이블 생성 시 TBLPROPERTIES 옵션 내 orc.compress라는 key 값을 정의하여 지정 가능합니다. 코덱은 ZLIB, SNAPPY을 사용하거나 none을 선택할 수 있습니다.

  아래 Hortonworks(현재는 Cloudera로 흡수되었죠.)에서 성능 테스트를 한 결과를 보면 ORC 파일 형식이 다른 파일 형식에 비해 상당히 높은 압축률을 보여주고 있음을 확인할 수 있니다. 

[출처] https://blog.cloudera.com/orcfile-in-hdp-2-better-compression-better-performance/

 

 

 

[Ref.] https://cwiki.apache.org/confluence/display/Hive/LanguageManual+ORC#LanguageManualORC-HiveQLSyntax

    https://blog.cloudera.com/orcfile-in-hdp-2-better-compression-better-performance/

 

  하이브에서는 테이블 생성 시 'STORED AS 저장형식명' 옵션을 사용하여 테이블별로 파일 형식을 지정할 수 있습니다.

 

  파일 형식을 그냥 쉽게 표현하자면
  하이브에서 테이블 형태로 보여지는 데이터를
  어떤 구조로 HDFS에 저장하며,
  어떤 압축 방식을 사용할 것인지 정의한 저장 방식이라고 보시면 됩니다.

 

  하둡 개발자 입장에서는 이전 포스트(맵리듀스(Map-Reduce) 상세 작동 방법)에서 언급했었던 InputFormat과 OutputFormat을 확장하여 구현한 것이며, 추가로 <key, value> 형태의 데이터를 레코드 형태로 보여주는 SerDe까지 구현 한 라이브러리라고 보시면 이해가 쉬울 것 같습니다.

 

맵리듀스(Map-Reduce) 상세 작동 방법

맵리듀스(Map-Reduce) 데이터 흐름 하둡 맵리듀스는 클러스터 환경에서 대량의 데이터를 병렬로 처리하는 응용 프로그램을 쉽게 작성할 수 있는 소프트웨어 프레임워크입니다. 맵리듀스 작업은 일

sparkdia.tistory.com

 

  파일 형식기본 값TEXTFILE이며(hive.default.fileformat 옵션으로 변경 가능), 그 외에도 하이브에서는 아래와 같이 다양한 파일 형식을 제공하고 있습니다.

TEXTFILE
SEQUENCEFILE
RCFILE
ORC
PARQUET
AVRO  
JSONFILE

  물론, 하이브는 오픈 소스이므로 사용자 정의의 파일 형식도 적용할 수도 있습니다.

  이 포스트에서는 Hive에서 기본적으로 제공하고 있는 파일형식에 대해 살펴보도록 하겠습니다.

 

1. TEXTFILE

  말 그대로 텍스트 파일입니다. 우리가 메모장이나 vi에디터를 통해서 읽을 수 있는 그 텍스트 파일 맞습니다.

  일반적으로 TEXTFILE 파일 형식은 External 테이블 생성 시 사용합니다. 원천 시스템에서 수집한 데이터나 테스트 등의 목적으로 사용자가 생성한 데이터를 Hive에서 조회하기 위해서는 데이터를 텍스트 파일 형태로 HDFS에 저장한 뒤 External 테이블을 생성하면 됩니다.

  텍스트 파일 내 데이터는 구분자로 필드와 로우가 구별되어야 하며, 테이블 생성 시 DELIMITED 옵션을 사용하여 구분자를 명시해주면 됩니다.

[출처] https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-StorageFormats

 

  TEXTFILE은 기본적으로 압축되지 않은 파일형식이기 때문에(GZip같은 툴로 압축될 수는 있습니다.) 다른 파일형식에 비해 저장 공간을 많이 필요로한다는 단점이 있습니다.
  지속적으로 ODS 영역에 보관할 원천 시스템 데이터라면 압축된 파일형식의 테이블을 생성하여 데이터를 이관하여 보관해야 합니다. 압축된 파일형식의 테이블을 사용하는 것이 쿼리 성능과 저장 공간 활용도를 높이는 방법입니다. 

 

 

2. SEQUENCEFILE 

  SEQUENCEFILE 파일 형식은 데이터를 압축하여 저장합니다.

  SEQUENCEFILE 파일 형식은 로우 기반으로 데이터를 저장합니다.

  위 특징 때문에 코드 테이블, 디멘전 테이블처럼 데이터와 컬럼이 적은 테이블에 적용합니다. 

 

 

3. RCFile (Record Columnar File)

  RCFile은 컬럼 기반으로 데이터를 압축하여 저장하는 파일 형식입니다.

  아래 테이블의 데이터를 예를 들어 로우 기반(Row-oriented)컬럼 기반(Column-oriented) 저장 방식 차이점을 살펴보도록 하겠습니다. 

EmpId Lastname Firstname Salary
10 Smith Joe 40000
12 Jones Mary 50000
11 Johnson Cathy 44000
22 Jones Bob 55000

 

  아래 그림은 위 예제 데이터를 로우 기반 테이블과 컬럼 기반 테이블에 각각 저장하는 경우 저장되는 데이터 순서를 나타내는 예제입니다.

  로우 기반 저장 방식은 아래 예제처럼 데이터의 각 로우 내 데이터를 순서대로 저장(Serialize) 합니다. (각 로우별 저장 순서는 테이블 조회 시 보여지는 로우의 순서와 상이할 수 있습니다.)

001:10,Smith,Joe,40000;002:12,Jones,Mary,50000;003:11,Johnson,Cathy,44000;004:22,Jones,Bob,55000;

  위 방식은 로우의 전체 컬럼 데이터를 한번에 액세스 하는 경우에 효과적입니다.

  대표적으로 Oracle이 로우 기반 방식으로 데이터를 저장합니다.

 

  컬럼 기반 저장 방식아래 예제처럼 한 컬럼의 데이터를 먼저 저장(Serialize)한 후 다음 컬럼의 데이터를 저장합니다.

10 : 001,12 : 002,11 : 003,22 : 004; Smith : 001, Jones : 002, Johnson : 003, Jones : 004; Joe : 001, Mary : 002, Cathy : 003, Bob : 004; 40000 : 001,50000 : 002,44000 : 003,55000 : 004;

  만약에 컬럼 내 동일한 데이터가 존재한다면, 아래와 같은 방법으로 동일한 데이터를 한번만 저장하여 저장 공간을 절약할 수도 있습니다.

...; Smith : 001, Jones : 002,004, Johnson : 003; ...

 

  컬럼 기반 저장 방식은 아래와 같은 장점이 존재합니다.

  • 열 단위인 경우 데이터가 더 균일하므로 압축률이 향상됩니다. 압축률의 향상되면 부수적으로 저장 공간이 절약되고 액세스 시 Disk I/O가 감소됩니다.
  • 쿼리 시 쿼리문에 명시된 컬럼이 저장된 블록만 액세스하면 되므로 I/O가 감소됩니다.
  • 다양한 데이터 액세스 패턴을 적용하는게 수월해집니다.

 

 

 

4. ORC (Optimized Row Columnar)

  ORC는 Full name 그대로 RCFile을 개선시킨 파일 형식입니다. 물론 RCFile과 마찬가지로 ORC도 컬럼 기반 저장 방식을 사용합니다.

  현재 파일 형식중에서 가장 많이 사용되는 파일 형식이라고 말할 수 있습니다.

  ORC에 관해서는 정리할 내용이 많은 관계로 아래 별도의 포스트로 작성하였으니 참고 바랍니다.

 

ORC(Optimized Row Columnar) in 하이브(Hive)

하이브에서는 다양한 형식의 파일을 읽고 저장할 수 있도록 테이블 별로 파일 형식을 지정할 수 있습니다. https://sparkdia.tistory.com/56 하이브에서 기본적으로 제공하는 파일 형식 중에서 현재 가장

sparkdia.tistory.com

 

 

5. PARQUET 

  PARQUET도 컬럼 기반 저장 방식을 사용하는 파일 형식입니다.

  하이브와 피그만 지원되는 ORC에 비해 더 다양한 하둡 에코 시스템을 지원하고 있습니다.

  • Apache Hive
  • Apache Drill
  • Cloudera Impala
  • Apache Crunch
  • Apache Pig
  • Cascading
  • Apache Spark

  그리고 PARQUET는 Google Protocol Buffers와 비슷한 모델을 사용합니다. 이로 인해 복잡하게 중첩된 데이터 구조(List, Maps, Sets)를 효율적으로 저장할 수 있습니다. (참고 : Dremel made simple with Parquet)

 

 


6. AVRO  

  아파치 에이브로(AVRO)는 데이터 직렬화(Serialization) 시스템입니다.
  하이브에서는 이 에이브로의 파일을 하이브에서도 액세스 할 수 있도록 AVRO SerDe를 제공해줍니다.

  에이브로는 하둡의 직렬화 방식(Writable)방식의 단점인 언어 이식성을 해결하기 위해 만들어진 프로젝트 입니다. 데이터 파일에 스키마 정보를 같이 저장하므로 사전에 스키마 정보를 알지 못해도 데이터를 읽는 시점에 스키마 정보를 파악할 수 있게 됩니다. 이러한 방식으로 데이터를 관리하므로 다양한 개발 언어를 이용해 데이터 셋을 공유할 수 있는 것입니다.

  상세한 내용은 아파치 에이브로 홈페이지를 참고하시길 바랍니다.

  

 


7. JSONFILE

  Hive 4.0 버전에서부터 JSONFILE 파일 형식도 지원한다고 하네요. 

  기존에는 사용자 정의의 파일 형식을 이용하거나 전처리 과정을 통해 CSV 파일 형태로 만든 뒤 TEXTFILE 파일 형식을 사용하여 데이터를 액세스했었는데, 이런 번거로움이 사라지겠네요.

 

 

  이상으로 하이브의 파일 형식에 대해 알아보았습니다.
  위 내용중에서 추가/보완해야 할 사항있으면 친철한 댓글 부탁드립니다.
  감사합니다.

 

 

[Ref.] https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-StorageFormats
  https://cwiki.apache.org/confluence/display/Hive/FileFormats
  https://cwiki.apache.org/confluence/display/Hive/LanguageManual
  https://en.wikipedia.org/wiki/RCFile
  https://blog.twitter.com/engineering/en_us/a/2013/dremel-made-simple-with-parquet.html
  Hadoop, The Definitive Guide

 

Windows 환경에서 Tableau를 설치해보겠습니다.

설치 파일 다운로드를 위해 우선 아래 URL에 접속합니다.

URL : https://www.tableau.com/ko-kr/products/desktop/download?signin=47cc9ac0f8435895c744899af5d60d2e

위 URL에 접속 후
아래와 같이 '비즈니스 이메일' 텍스트 박스에 이메일 주소를 입력 후
'무료 평가판 다운로드' 버튼을 클릭하면 설치 파일이 다운로드 됩니다.

 

  설치 파일 다운로드가 완료되었으면, 설치 파일을 실행합니다.

 

  설치 파일을 실행하면 아래와 같은 다이얼로그를 확인할 수 있습니다.

 사용권 계약 동의를 위해 '동의함' 체크 박스를 클릭 후 '설치' 버튼을 클릭합니다.

  다음 다이얼로그를 통해 설치 진행 상황을 확인하실 수 있습니다.

 

설치가 완료되면 자동으로  Tableau Desktop 버전이 실행됩니다.

 

  Tableau 를 처음 실행하면 아래와 같이 사용자 기본 정보를 입력해야 합니다.

 

  사용자 정보를 입력하면 아래와 같이 평가판 활성화 작업이 진행되며,

 

  활성화가 완료되면
  아래와 같이 Tableau가 정상적으로 실행된 것을 확인할 수 있습니다.

 

  초기 화면 하단에 '샘플 통합 문서'를 클릭하면 Tableau에서 제공하는 샘플 데이터를 확인할 수 있습니다.

 

 

 

 

 

맵리듀스(Map-Reduce) 데이터 흐름

하둡 맵리듀스는 클러스터 환경에서 대량의 데이터를 병렬로 처리하는 응용 프로그램을 쉽게 작성할 수 있는 소프트웨어 프레임워크입니다. 맵리듀스 작업은 일반적으로 입력 데이터를 독립적

sparkdia.tistory.com

  이전 포스팅에서 맵리듀스의 데이터 흐름을 간략하게 살펴보았습니다. 

  일반적으로 Mapper와 Reducer 인터페이스의 map과 reduce 메소드를 구현하는 것만으로도 간단하게 맵리듀스 프레임워크를 사용할 수 있습니다.

  만약, 입/출력 대상으로 텍스트 파일 외 다른 형식의 파일이나 데이터베이스를 사용하는 경우나 맵리듀스 내부의 상세 작업을 컨트롤하기 위해서는 추가적인 개발 작업이 필요합니다.

  이를 위해 맵리듀스의 작동 방법을 좀 더 상세하게 살펴보도록 하겠습니다.

[Ref.]  Hadoop, The Definitive Guide

 

  맵리듀스 Job은 클라이언트가 수행하는 작업의 기본 단위로, 맵 태스크(Map task)와 리듀스 태스크(Reduce task)로 나눠서 실행합니다.

 

1. Map task 단계

  Map task 단계에서는 입력 데이터를 읽어 <key, value> 구조의 입력 레코드를 구성한 뒤 중간 단계의 레코드로 변환하는 작업을 합니다.

   맵리듀스 프레임워크의 입력으로 다양한 형식의 입력 데이터가 사용 될 수 있습니다. 물론, 하둡에서 제공되는 기본 파일 형식외에는 입력 파일의 형식이 무엇이고 어떻게 레코드 형태로 변환할 수 있는지 정의하는 작업은 필요합니다. 이러한 작업은 InputFormat과 RecordReader라는 인터페이스를 확장 구현하여 정의 가능합니다.

 

InputFormat

  InputFormat은 맵리듀스 Job의 입력 명세서라고 표현할 수 있습니다.

  맵리듀스 프레임워크에서는 Job의 InputFormat을 이용해 아래와 같은 작업을 수행합니다.

  1) 작업의 입력 명세서를 검증
  2) 입력 파일을 분할하여 InputSplit을 생성한 뒤, 각 개별 Mapper에 할당
  3) Mapper가 처리할 입력 레코드를 InputSplit에서 어떻게 추출해야 할 지 명시하는 RecordReader를 구현

  InputFormat을 구현한 기본 클래스는 FileInputFormat이며, 이를 확장한 다수의 하위 클래스가 존재합니다. 대표적으로 기본 InputFormat인 TextInputFormat 클래스도 FileInputFormat의 하위 클래스입니다. FileInputFormat의 하위 클래스의 기본 동작은 입력 파일의 총 크기(Byte)을 기준으로 입력을 논리적 InputSplit 인스턴스로 분할하는 것입니다.

 

InputSplit 

  InputSplit은 Job의 입력을 고정 크기로 분리한 데이터 조각입니다. 입력 데이터를 다수의 Mapper가 병렬로 분산 처리할 수 있도록 다수의 논리적인 InputSplit으로 분리한 것입니다.

  하나의 InputSplit마다 맵 태스크를 생성하게 되며, InputSplit의 각 레코드는 RecordReader에 의해 <key, value> 형태인 입력 레코드로 변환되어 map 함수로 전달됩니다.

 

RecordReader

  RecordReader는 입력 데이터가 크기 기준(byte-oriented view)으로 분리된 InputSplit을 Mapper에서 <key, value> 쌍 형태로 처리할 수 있도록 레코드 기반의 형태(record-oriented view)로 변환하는 작업을 수행합니다.

  LineRecordReader는 TextInputFormat이 제공하는 기본 레코드 입력기입니다. LineRecordReader는 정의된 레코드  delimiter(기본 값은 개행 문자)에 의해 입력 데이터를 분리합니다. 이 때, 분리된 각 문자열이 레코드의 value가 되며, key는 파일에서 해당 문자열의 위치(Byte offset)입니다. 

 

Map

  맵에서는 입력 데이터를 읽어 map 함수에서 사용자가 정의한 연산을 수행합니다.

  맵 함수의 처리 결과는 우선 메모리 버퍼에 저장되며 버퍼의 내용이 한계치에 도달하면 일괄로 디스크에 저장(Spill)됩니다. 이 때, 맵의 출력 데이터는 키 값에 의해 정렬되고 리듀서 수에 맞게 파티션별로 나눠서 저장됩니다. 파티션 별로 출력 데이터를 나눠서 저장하는 이유는 리듀서별로 전송할 출력 데이터를 미리 구분하기 위해서입니다. 

  만약, 컴바이너 함수가 정의되어 있다면 맵의 출력 값이 컴바이너 함수의 입력 값이 되고, 컴바이너 함수의 결과 데이터에 대해 정렬과 파티셔닝 작업이 발생하게 됩니다. 컴바이너 함수를 사용하는 이유는 맵 결과 데이터의 크기를 감소시켜 리듀서로 전송할 데이터 양(네트워크 부하)을 줄이기 위함입니다.

 

 

2. Reduce task 단계

Reduce

  리듀스 태스크는 각 맵 태스크가 완료되는 즉시 맵의 출력 데이터를 리듀스가 실행되는 노드에 복사하기 시작합니다(Shuffle).

  맵의 출력 데이터가 복사될 때 리듀서는 다수의 맵 출력 데이터를 정렬 순서를 유지하면서 병합합니다.
  리듀스에서는 병합된 데이터를 읽어 reduce 함수에서 사용자가 정의한 연산을 수행합니다.

 

OutputFormat

  OutputFormat은 맵리듀스 Job의 출력 명세서라고 표현할 수 있습니다.

  맵리듀스 프레임워크에서는 Job의 OutputFormat을 이용해 아래와 같은 작업을 수행합니다.

    1) 작업의 출력 명세서를 검증 (예: 출력 디렉토리 존재 여부)
    2) 작업의 출력 파일을 작성하는 데 사용되는 RecordWriter 구현

 

RecordWriter

  RecordWriter는 출력 <key, value> 쌍을 출력 파일에 씁니다.

  RecordWriter을 구현한 기본 클래스는 LineRecordWriter입니다. LineRecordWriter은 key와 value를 tab 문자로 구분하며, 각 레코드는 개행 문자로 구분하여 데이터를 저장합니다.

 

 

  이상으로 맵리듀스의 작동 방법에 대해 알아봤습니다.
  맵리듀스의 작동 원리를 이해하게 되면, 맵리듀스 구현이 용이해지고 디버깅 작업이 좀 더 수월해질 수 있을것입니다. 또한,  Hive 등 하둡 에코 시스템에서의 데이터를 저장하고 관리하는 방법에 대한 이해도도 높아질 수 있으실 것입니다.

만약, 위 내용에 대해서 오류를 발견하시거나 궁금한 사항이 있으시면 적극적인 의견 부탁드립니다.

 

[Ref.] Hadoop, The Definitive Guide
       
https://hadoop.apache.org/docs/r1.2.1/mapred_tutorial.html

 

  하둡 맵리듀스는 클러스터 환경에서 대량의 데이터를 병렬로 처리하는 응용 프로그램을 쉽게 작성할 수 있는 소프트웨어 프레임워크입니다.

  맵리듀스 작업은 일반적으로 입력 데이터를 독립적인 청크로 분할(Split)하여 다수의 노드에서 병렬로 맵 작업을 수행합니다. 맵 작업의 결과물은 Shuffling 되어 리듀스 작업의 입력으로 전달되며, 리듀스 작업의 결과물이 최종 결과 데이터 셋이 됩니다.
  이러한 맵 리듀스 과정에서의 입/출력 데이터는 <key, value> 쌍의 집합 형태로 표현합니다.

  대표적인 맵리듀스의 예제인 단어세기 프로그램을 기준으로 맵리듀스의 논리적 데이터 흐름을 살펴보겠습니다.

  단어세기 프로그램은 입력 데이터에서 각 단어의 건수를 계산하여 출력하는 프로그램입니다.

  해당 프로그램은 먼저 입력 경로 내 존재하는 텍스트 파일을 읽어 문자열 데이터를 분할(Split)하여 각 맵에 분배하여 줍니다. 데이터를 분할하는 기준은 InputFormat으로 정의할 수 있습니다. InputFormat의 기본은 TextInputFormat이며, 이 경우 개행을 기준으로 데이터를 분할하므로 맵은 한 번에 한 줄의 문자열을 처리하게 됩니다.

  맵은 입력받은 문자열을 공백 기준으로 분리(Splitting)하여 <단어,1> 형태의 <key, value> 쌍을 생성합니다. 아래 목록은 맵 연산의 결과 예시입니다.
  <Hadoop, 1>
  <MapRecdce, 1>
  <software, 1> ...
  <MapRecdce, 1> ...

  맵의 출력은 키를 기준으로 정렬된 후 리듀스에 전달됩니다. 

  만약 프로그램 내 컴바이너(Combiner)가 정의되어 있다면 리듀스 연산에 앞서 컴바이너 연산이 선행됩니다. 이때 정렬된 맵의 출력 값은 컴바이너의 입력값이 되며, 컴바이너의 출력 값이 리듀스에 전달되게 됩니다.

  컴바이너는 네트워크 사용량을 줄이기 위한 목적으로 리듀스에 전달하는 데이터의 크기를 최소화하기 위해 사용됩니다. 일반적으로 연산 내용이 리듀스와 동일하며 로컬 내 맵 결과 데이터만을 대상으로 리듀스 작업을 미리 진행하는 단계라고 이해하시면 됩니다.

  아래는 컴바이너 연산까지 적용된 맵 연산 결과 예시입니다.
  <Hadoop, 1>
  <MapRecdce, 2>
  <software, 1> ...

  위에서 언급했듯이 맵 연산 결과 데이터는 리듀스의 입력 데이터로 전달됩니다. 이때 키 값이 같은 맵의 출력 결과물은 하나의 리듀스가 키 값의 건수를 계산할 수 있도록 동일한 리듀스로 전달됩니다(Shuffling). 

  리듀스에서는 입력 값으로 전달받은 맵 연산 결과 데이터를 읽어 단어별로 건수를 계산하게 됩니다. <key, value> 구조인 입력 값을 읽어 key가 동일한 데이터의 value를 합한 결과를 <key, value> 쌍 구조로 출력하게 됩니다.

  아래는 리듀스 연산 후 출력되는 최종 결과 데이터의 예입니다.
  <Hadoop, 10>
  <MapRecdce, 18>
  <job, 23> ...

 

  지금까지 살펴본 맵 리듀스 연산을 Java로 구현한 소스 코드는 아래와 같습니다.

package org.myorg;

import java.io.IOException;
import java.util.*;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.conf.*;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapred.*;
import org.apache.hadoop.util.*;

public class WordCount {

   public static class Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> {
     private final static IntWritable one = new IntWritable(1);
     private Text word = new Text();

     public void map(LongWritable key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
       String line = value.toString();
       StringTokenizer tokenizer = new StringTokenizer(line);
       while (tokenizer.hasMoreTokens()) {
         word.set(tokenizer.nextToken());
         output.collect(word, one);
       }
     }
   }

   public static class Reduce extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {
     public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
       int sum = 0;
       while (values.hasNext()) {
         sum += values.next().get();
       }
       output.collect(key, new IntWritable(sum));
     }
   }

   public static void main(String[] args) throws Exception {
     JobConf conf = new JobConf(WordCount.class);
     conf.setJobName("wordcount");

     conf.setOutputKeyClass(Text.class);
     conf.setOutputValueClass(IntWritable.class);

     conf.setMapperClass(Map.class);
     conf.setCombinerClass(Reduce.class);
     conf.setReducerClass(Reduce.class);

     conf.setInputFormat(TextInputFormat.class);
     conf.setOutputFormat(TextOutputFormat.class);

     FileInputFormat.setInputPaths(conf, new Path(args[0]));
     FileOutputFormat.setOutputPath(conf, new Path(args[1]));

     JobClient.runJob(conf);
   }
}

[Ref.] https://hadoop.apache.org/docs/r1.2.1/mapred_tutorial.html

  이전 포스트 "HDFS 아키텍처"에서 하둡은 분산 저장과 분산 처리를 하기 위한 프레임워크라고 소개를 했었습니다.

  Hadoop 1.x에서는 맵리듀스가 분산처리를 담당했었습니다.
  그러나 배치 처리를 위한 맵리듀스의 한계를 뛰어넘어 다양한 프레임워크에서 HDFS를 효율적으로 액세스 할 수 있는 환경에 대한 요구사항이 발생하게 되었습니다.

  이러한 요구사항에 대한 결과물이 Hadoop 2.x에서부터 도입된 YARN(Yet Another Resource Negotiator)입니다.
  YARN이 도입되면서 자원 관리와 잡 스케쥴링/모니터링 기능이 분리되어 다양한 프레임워크가 안정적으로 클러스터 내에서 작업을 실행할 수 있는 환경이 마련되었습니다.

 

[출처] hortonworks

 

YARN 아키텍처

  YARN 프레임워크는 데이터 연산을 위한 Resource Manager와 Node Manager로 구성되어 있습니다.

  Resource Manager는 중앙에서 클러스터 내 모든 사용 가능한 자원을 모든 응용프로그램 간에 중재하는 역할을 담당합니다.
  Node Manager는 노드별로 존재하며, 노드의 자원 사용량(CPU, 메모리, 디스크, 네트워크)을 모니터링하고 Resource Manager에 보고합니다.

[출처] https://www.edureka.co/blog/introduction-to-hadoop-2-0-and-advantages-of-hadoop-2-0/

 

  Resource Manager에는 Scheduler와 Application Manager라는 두 개의 메인 컴포넌트가 존재합니다.

  Scheduler는 제약 조건(용량, 큐, etc.)에 의거해 실행중인 여러 응용 프로그램에 자원을 할당하는 작업을 수행합니다. Scheduler는 응용 프로그램의 자원 요구 사항에 따라 스케줄링 기능을 수행할 뿐, 응용 프로그램의 실행 상태에 대해서는 관여하지 않습니다.

  Applications Manager는 클라이언트가 제출한 작업을 승인하고 응용 프로그램별 Application Master를 실행하기 위한 Container를 협상합니다. 응용 프로그램이 종료될 때까지 Application Master의 상태를 관리합니다. 

  Application Master는 애플리케이션 별 라이브러리의 인스턴스입니다. Resource Manager와 자원을 협상하여 Container를 할당받으며, Node Manager와 협력하여 Container와 자원 사용 상태를 모니터링합니다.

  Container는 클러스터에 할당된 자원을 의미합니다. Container 안에서 실제적으로 응용 프로그램의 태스크(Task)가 수행됩니다.

 

 

HADOOP 동작 방식

1) 클라이언트 프로그램이 Resource Mananger에게 응용 프로그램을 제출합니다. 제출된 응용 프로그램에는 응용 프로그램별 Application Master를 자체적으로 시작하는 데 필요한 명세서도 포함되어 있습니다.

2) Resource Manager는 Application Master를 시작하기 위한 Container를 협상한 뒤, Application Master를 시작합니다.

3) Application Master가 가동되면 Resource Manager에 등록됩니다. 등록 후 클라이언트 프로그램은 Resource Manager를 쿼리하여 Application Master와 직접 통신할 수 있게 됩니다.

4)  Application Master는 자원 요청(resource-request) 프로토콜을 통해 적절한 자원 Container를 요청합니다.

5) Container를 할당 받으면 Application Master는 Node Manager에 Container 실행 명세서를 제공하고 Container를 시작합니다. 일반적으로 실행 명세서에는 Container가 Application Master와 자체적으로 통신하는 데 필요한 정보가 포함되어 있습니다.

6) Container에서 실행되는 응용 프로그램 코드는 응용 프로그램 명세서(application-specific) 프로토콜을 통해 Application Master에 필요한 정보(진행, 상태 등)를 제공합니다.

7) 클라이언트는 응용 프로그램 명세서(application-specific) 프로토콜을 통해 Application Master로부터 상태 및 진행 상황 등의 정보를 전달받습니다.

8) 응용 프로그램과 필요한 모든 작업이 완료되면 Application Master는 Resource Manager에 등록을 해제하고 종료하게 됩니다.

[출처] https://blog.cloudera.com/apache-hadoop-yarn-concepts-and-applications/

 

 

 

[Ref.] : https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/YARN.html
            https://blog.cloudera.com/apache-hadoop-yarn-concepts-and-applications/

 

  하둡을 간략하게 정의하면 '분산 저장 & 분산 처리'라고 표현할 수 있습니다.
  대량의 데이터를 다수의 데이터 노드에 분산하여 저장을 하고, 다수의 프로세스가 작업을 분배하여 처리하는 방식이기 때문입니다.

  하둡에서의 분산 저장과 분산 처리 기술 중에서 분산 저장을 담당하는 것이 바로 HDFS(Hadoop Distributed File System)입니다.

  HDFS도 다른 파일 시스템처럼 계층적 파일 구조를 지원합니다. 디렉토리나 파일을 생성/삭제/이동할 수 있으며, 디렉토리 안에 하위 디렉토리를 생성할 수 있습니다. 또한, 용량이나 파일 개수 할당 제한도 설정 가능하며, 접근 권한 설정도 가능합니다.

HDFS 브라우저

 

  그럼 다른 파일 시스템과 비교하여 HDFS만의 특징은 무엇이 있을까요?

   - 높은 장애 결함 허용성(내결함성)
   - 저비용의 범용 하드웨어 기반으로도 효율적인 분산 파일 시스템 구성 가능
   - 대용량의 데이터 셋을 액세스 할 수 있는 높은 처리량
   - 파일 시스템 데이터를 스트리밍 액세스 방식으로 처리 가능 

  위와 같은 HDFS만의 특징으로 대용량의 데이터를 안정적으로 관리할 수 있게 됩니다.

  구체적으로 HDFS에서 대용량의 데이터를 관리하는 방법에 대해 알아보도록 하겠습니다.

 

  우선, HDFS의 아키텍처는 마스터/슬래이브 구조입니다.

네임노드(NameNode) 데이터노드(DataNode)

  HDFS 클러스터는 하나의 네임노드(마스터)와 하나 이상의 데이터노드(슬래이브)로 구성됩니다.

  네임노드는 파일 시스템 네임스페이스를 관리하고 클라이언트에 의한 파일 액세스를 조정하는 역할을 담당합니다.
  네임스페이스를 실행하여 파일과 디렉토리를 열거나 닫고, 이름을 변경하는 작업 등을 처리하고 데이터노드와 블럭의 매핑 정보를 관리합니다. 

  데이터노드는 파일 시스템 클라이언트로부터의 파일 읽기/쓰기 요청을 수행합니다. 또한 네임노드의 요청에 따라 블럭 생성/삭제/복제를 수행합니다.

  하둡 클러스터에 저장되는 파일은 하나 이상의 블럭으로 쪼개져 다수의 데이터노드에 분산되어 저장됩니다.

 

 

데이터 복제

  HDFS에서 장애 결함 허용이 가능한 이유는 데이터 복제 기능을 제공하기 때문입니다.

  HDFS는 대규모 클러스터의 여러 시스템(데이터노드)에 걸쳐 매우 큰 파일을 안정적으로 저장하도록 설계되었습니다. 각 파일은 연속된 블록으로 저장되는데, 내결함성을 위해 파일 블록은 복제되어 여러 데이터노드에 분산되어 저장됩니다. 블록 크기 및 복제 계수(replication factor)는 파일 별로 설정할 수 있습니다.

  네임 노드는 주기적으로 클러스터의 각 데이터노드에서 하트 비트 및 블록 보고서를 받습니다. 하트 비트를 수신하면 데이터 노드가 제대로 작동하고 있음을 나타냅니다. 만약, 네임노드가 특정 데이터노드로부터 하트 비트를 수신받지 못했다면, 네임노드는 해당 데이터노드를 사용불능(dead) 상태로 등록하고, 사용불능인 데이터노드에 저장되어 있던 복제본을 다른 데이터노드에 복제하여 블록의 복제본의 수가 복제 계수와 동일하도록 관리하게 됩니다.

  복제본의 배치 전략은 HDFS의 성능, 가용성, 안정성을 좌우하는 중요한 요소입니다.
  복제본은 여러 랙에 걸쳐 저장될 수 있습니다. 일반적으로 동일한 랙 안에 설치된 서버간의 네트워크 대역폭이 다른 랙에 설치된 서버와의 네트워크 대역폭보다 더 큽니다. 네트워크 성능을 고려하면 동일한 랙에 위치한 서버에 복제본을 저정하는 것이 효율적일수도 있겠지만, 하나의 랙안의 모든 서버가 동시에 장애가 발생할 수 있다는 사실을 간과할 수는 없습니다. 네트워크 성능과 안정성이 Trade-off 관계인 것입니다.

  일반적으로 복제 계수가 3인 경우에 로컬 장비(Writer가 위치한 데이터노드)에 하나의 복제본을 저장하고 로컬 장비와 동일한 랙에 위치한 다른 장비와 다른 랙에 위치한 장비에 나눠서 복제본을 저장하게 됩니다.

 

 

파일시스템 메타데이터 관리

  HDFS 파일시스템 네임스페이스는 파일에 대한 블록 매핑 및 파일 시스템 프로퍼티 정보를 포함하고 있습니다. 이 파일시스템 네임스페이스는 네임노드의 로컬 OS 파일시스템에 FsImage라는 파일로 저장되어 있습니다.

  HDFS 파일 시스템에 변경(예를 들어 신규 파일 생성)이 발생하면 변경 내역을 즉시 FsImage에 저장하지는 않습니다.
  변경 내역은 EditLog라는 파일(FsImage처럼 네임노드의 로컬 OS 파일 시스템에 저장)에 우선 기록되며, checkpoint가 발생하는 시점(dfs.namenode.checkpoint.period 설정 값에 따라 주기적으로 발생)에 EditLog에 기록된 변경내역이 FsImage에 일괄적으로 반영되게 됩니다.

 

 

 

[Ref.] : https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html

 

+ Recent posts