블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

NMF 알고리즘을 이용한 유사 문서 검색과 구현(2/2)

sklearn을 이용한 구현


조대협 (http://bcho.tistory.com)


http://bcho.tistory.com/1216 를 통하여 tf-idf를 이용하여 문서를 벡터화 하고, nmf를 이용하여 문서의 특성을 추출한 다음, 코싸인 유사도를 이용하여 유사 문서를 검색하는 알고리즘에 대해서 알아보았다. 이번글에서는 이 알고리즘을 직접 sklearn을 이용해서 구현해보도록 하자. sklearn은 이용하면 분산 학습을 이용한 대규모 데이타 처리는 불가능하지만, 작은 수의 문서나 모델에는 사용이 가능하다. 무엇보다 sklearn의 경우 대부분의 모델을 라이브러리화 해놓았기 때문에, 복잡한 구현이 없이 쉽게 사용이 가능하다.


전체 소스 코드는 https://github.com/bwcho75/dataanalyticsandML/blob/master/NMF%20based%20document%20recommendation/NMF%20based%20similar%20document%20recommendation.ipynb 에 공유되어 있다.


샘플 데이타

여기서 사용할 데이타 셋은 sklearn 테스트 데이타셋에 있는 20개의 뉴스 그룹의 이메일 데이타를 사용한다. 총 20개의 토픽으로 약 18000개의 포스팅으로 구성되어 있다.


이메일 텍스트 형식으로, 제목과, 날짜등의 헤더 정보와 이메일 내용으로 구성되어 있다. 첫번째 코드에서는 이 데이타를 읽어서 제목과 본문만을 추출하여, Pandas data frame에 저장하도록 한다.


from sklearn.datasets import fetch_20newsgroups
import StringIO
import pandas as pd

newsgroups_train = fetch_20newsgroups(subset='train')

def parseDocument(data):
   buf = StringIO.StringIO(data)
   line=buf.readline()
   data=[]
   subject=''
   while line:
       if(line.startswith('Subject:')):
           subject = line[8:].strip()
       elif (line.startswith('Lines:')):
              lines = line[6:]
              while line :
                   line = buf.readline()
                   data.append(line)
       line=buf.readline()
   text = ''.join(data)
   
   return subject,text


textlist = []
df = pd.DataFrame(columns=['text'])
for data in newsgroups_train.data[0:1000]:
   subject,text = parseDocument(data)
   df.loc[subject]=text
df.head()


제목은 ‘Subject:’로 시작하는 줄에 들어 있고, 본문은 ‘Lines:’로 시작하는 줄에 있다. 이 내용들만을 추출하여 pandas data frame에 저장하였다. 본문은 data frame 상에 ‘text’라는 이름으로된 컬럼에 저장하였다.

Tfidf 를 이용한 단어의 벡터화 구현

단어를 벡터로 변환하기 위해서 앞에서 설명한 tfidf 모델을 이용한다. sklearn에 이미 구현이 되어 있기 때문에 어렵지 않게 구현이 가능하다.


from sklearn.feature_extraction.text import TfidfVectorizer

vectorizer = TfidfVectorizer()
vectors = vectorizer.fit_transform(df['text'].tolist())
print(vectors.shape)


간단하게, TfidfVectorizer를 로딩한다음에, fit_transform을 이용하여, 문장의 본문이 있는 데이타 프레임의 ‘text’ 컬럼을 배열 형태로 변환하여 리턴해주면 된다.


NMF를 이용하여 본문에서 특성 추출

문서가 tf-idf를 이용하여 벡터화가 되었으면, NMF를 이용하여, 각 문서별로 특성을 추출한다.

NMF역시 sklearn에 NMF라는 모듈로 미리 정의되어 있다. 단지, 몇개의 특징으로 압축을 해낼것인지만 지정하면 되는데, 여기서는 n_components 인자를 이용하여 총 40개의 특징으로 특성을 압축하여 추출하도록 하였다.


from sklearn.decomposition import NMF

vector_array = vectors.toarray()
nmf = NMF(n_components=40)
nmf.fit(vector_array)
features = nmf.transform(vector_array)


추출된 피쳐는 features 변수에 저장하였다.

피쳐 정규화

추출된 피쳐가 피쳐마다 또는 문서마다 변화의 폭이 클 수 있기 때문에, Normalizer를 이용하여 0~1사이로 스케일링을 한다. 이 정규화 역시 간단하게 아래와 같이 Normalizer 모듈을 이용하면 된다.


from sklearn.preprocessing import Normalizer

normalizer = Normalizer()
norm_features=normalizer.fit_transform(features)

print(norm_features[0:2])


정규화된 피쳐가 배열 형태이기 때문에, 사용 편의상 데이타 프레임에 로딩한다.


df_features = pd.DataFrame(norm_features,index=df.index.tolist())


df_features 변수에 문서별 특징과 문서 제목을 가지고 데이타 프레임을 만들어서 생성하였다.

인덱스는 문서의 이름이 될것이고 0~39 컬럼은 각문서별 특징이 된다. 출력해보면 대략 다음과 같은 모양이 된다.


문서 유사도 계산

문서별로 특징을 계산이 끝났으면 특정 문서와 유사한 문서를 찾도록 해보자.

앞의 글에서도 설명했지만, 문서의 유사도는 코싸인 유사도를 사용한다.

공식을 다시 기억해보면


여기서 A는 문서 A의 특성 행렬, B는 B 의 특성 행렬이 된다. |A|와 |B|는 각 문서 특성 행렬의 벡터의 길이인데, 앞에서 정규화 Normalization 을 하면서 각 문서의 행렬의 크기가 1이 되었기 때문에, 여기서 코싸인 유사도는 A*B / |A|*|B| = A*B / 1*1 = A*B 가된다.

즉 두 문서의 특성 행렬을 곱한 값이 코싸인 유사도가 된다.


데이타 프레임을 이용하면, 하나의 문서 특성 행렬을 전체 문서에 대해서 곱할 수 있다. .dot 함수를 이용하면 되는데,


article = df_features.loc['WHAT car is this!?']


“WHAT car is this!?” 라는 문서의 유사한 문서를 찾아보도록 하자. df_features에서 “WHAT car is this!?” 의 특성 행렬을 찾아내서 article 변수에 저장하고


similarities=df_features.dot(article)


전체 문서의 특성행렬에서 각 문서의 특성 행렬과 article 문서의 특성행렬을 곱한다. 그러면 article 문서에 대해서 각문서에 대한 유사도가 계산이된다.


top=similarities.nlargest()


이 값을 큰 순서대로 소팅해서 top 이라는 변수에 저장해놓고, 유사도가 높은 문서데로 문서의 제목과 유사도를 출력해본다.


texts = df.loc[top.index]['text'].tolist()
i = 0
for text in texts:
   print('TITLE :'+top.index[i]+" Similarities:"+ str(top[i]))
   #print(text+'\n')
   i = i+1



다음은 실행 결과이다.

TITLE :WHAT car is this!? Similarities:1.0
TITLE :Re: WHAT car is this!? Similarities:0.999080385281
TITLE :Re: New break pads & exhausts after 96K km (60K mi) on '90 Maxima? Similarities:0.980421814633
TITLE :Insurance Rates on Performance Cars SUMMARY Similarities:0.945184088039
TITLE :Re: What is " Volvo " ? Similarities:0.935911211878


간단하게 tf-idf와 NMF를 이용한 문서 유사도 측정을 구현해봤다. 조금 더 높은 정확도와 대규모 학습을 위해서는 이보다는 Word2Vector를 이용한 문서의 벡터화와, 딥러닝을 이용한 문서의 유사도 분석을 하면 훨씬 정확도를 높일 수 있다. 전체 기본 개념은 유사하다고 보면 된다.


DBSCAN (밀도 기반 클러스터링)


조대협(http://bcho.tistory.com)

기본 개념

이번에는 클러스터링 알고리즘중 밀도 방식의 클러스터링을 사용하는 DBSCAN(Density-based spatial clustering of applications with noise) 에 대해서 알아보도록 한다.

앞에서 설명한 K Means나 Hierarchical 클러스터링의 경우 군집간의 거리를 이용하여 클러스터링을 하는 방법인데, 밀도 기반의 클러스터링은 점이 세밀하게 몰려 있어서 밀도가 높은 부분을 클러스터링 하는 방식이다.

쉽게 설명하면, 어느점을 기준으로 반경 x내에 점이 n개 이상 있으면 하나의 군집으로 인식하는 방식이다.


그러면 조금 더 구체적인 개념과 용어를 이해해보자

먼저 점 p가 있다고 할때, 점 p에서 부터 거리 e (epsilon)내에 점이 m(minPts) 개 있으면 하나의 군집으로 인식한다고 하자. 이 조건 즉 거리 e 내에 점 m개를 가지고 있는 점 p를 core point (중심점) 이라고 한다.

DBSCAN 알고리즘을 사용하려면 기준점 부터의 거리 epsilon값과, 이 반경내에 있는 점의 수 minPts를 인자로 전달해야 한다.


아래 그림에서 minPts = 4 라고 하면, 파란점 P를 중심으로 반경 epsilon 내에 점이 4개 이상 있으면 하나의 군집으로 판단할 수 있는데, 아래 그림은 점이 5개가 있기 때문에 하나의 군집으로 판단이 되고, P는 core point가 된다.



아래 그림에서 회색점 P2의 경우 점 P2를 기반으로 epsilon 반경내의 점이 3개 이기 때문에, minPts=4에 미치지 못하기 때문에, 군집의 중심이 되는 core point는 되지 못하지만, 앞의 점 P를 core point로 하는 군집에는 속하기 때문에 이를 boder point (경계점)이라고 한다.



아래 그림에서 P3는 epsilon 반경내에 점 4개를 가지고 있기 때문에 core point가 된다.



그런데 P3를 중심으로 하는 반경내에 다른 core point P가 포함이 되어 있는데, 이 경우 core point P와  P3는 연결되어 있다고 하고 하나의 군집으로 묶이게 된다.


마지막으로 아래 그림의 P4는 어떤 점을 중심으로 하더라도 minPts=4를 만족하는 범위에 포함이 되지 않는다. 즉 어느 군집에도 속하지 않는 outlier가 되는데, 이를 noise point라고 한다.


이를 모두 정리해보면 다음과 같은 그림이 나온다.


정리해서 이야기 하면, 점을 중심으로 epsilon 반경내에 minPts 이상수의 점이 있으면 그 점을 중심으로 군집이 되고 그 점을 core point라고 한다. Core point 가 서로 다른 core point의 군집의 일부가 되면 그 군집을 서로 연결되어 있다고 하고 하나의 군집으로 연결을 한다.

군집에는 속하지만, 스스로 core point가 안되는 점을 border point라고 하고, 주로 클러스터의 외곽을 이루는 점이 된다.

그리고 어느 클러스터에도 속하지 않는 점은 Noise point가 된다.

장점

DBSCAN 알고리즘의 장점은

  • K Means와 같이 클러스터의 수를 정하지 않아도 되며,

  • 클러스터의 밀도에 따라서 클러스터를 서로 연결하기 때문에 기하학적인 모양을 갖는 군집도 잘 찾을 수 있으며


    기하학적인 구조를 군집화한 예 (출처 : https://en.wikipedia.org/wiki/DBSCAN )

  • Noise point를 통하여, outlier 검출이 가능하다.

예제 코드

코드의 내용은 앞과 거의 유사하다.


model = DBSCAN(eps=0.3,min_samples=6)


모델 부분만 DBSCAN으로 바꿔 주고, epsilon 값은 eps에 minPts값은 min_samples 인자로 넘겨주면 된다. 이 예제에서는 각각 0.3 과 6을 주었다.


전체 코드를 보면 다음과 같다.


import pandas as pd
iris = datasets.load_iris()

labels = pd.DataFrame(iris.target)
labels.columns=['labels']
data = pd.DataFrame(iris.data)
data.columns=['Sepal length','Sepal width','Petal length','Petal width']
data = pd.concat([data,labels],axis=1)

data.head()



IRIS 데이타를 DataFrame으로 로딩 한 다음, 학습에 사용할 피쳐를 다음과 같이 feature 변수에 저장한다.


feature = data[ ['Sepal length','Sepal width','Petal length','Petal width']]
feature.head()


다음은 모델을 선언하고, 데이타를 넣어서 학습을 시킨다.


from sklearn.cluster import DBSCAN
import matplotlib.pyplot  as plt
import seaborn as sns

# create model and prediction
model = DBSCAN(min_samples=6)
predict = pd.DataFrame(model.fit_predict(feature))
predict.columns=['predict']

# concatenate labels to df as a new column
r = pd.concat([feature,predict],axis=1)


다음은 모델을 선언하고, 데이타를 넣어서 학습을 시킨다.

학습이 끝난 결과를 다음과 같이 3차원 그래프로 시각화 해보자. 아래 시각화는 3차원인데, 학습은 4차원으로 하였다. 그래서 다소 오류가 있어 보일 수 있다. 다차원 데이타를 시각화 하기위해서는 PCA나 t-SNE와 같은 차원 감소 (dimensional reduction) 기법을 사용해야 하는데,  이는 다음 글에서 다루도록한다.


from mpl_toolkits.mplot3d import Axes3D
# scatter plot
fig = plt.figure( figsize=(6,6))
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)
ax.scatter(r['Sepal length'],r['Sepal width'],r['Petal length'],c=r['predict'],alpha=0.5)
ax.set_xlabel('Sepal lenth')
ax.set_ylabel('Sepal width')
ax.set_zlabel('Petal length')
plt.show()







마지막으로 Cross tabulazation 을 이용하여 모델을 검증해보면 다음과 같은 결과를 얻을 수 있다.

ct = pd.crosstab(data['labels'],r['predict'])
print (ct)



이 코드에 대한 전체 내용은 https://github.com/bwcho75/dataanalyticsandML/blob/master/Clustering/5.%20DBSCANClustering-IRIS%204%20feature-Copy1.ipynb 에서 확인할 수 있다.

Hierarchical clustering을 이용한 데이타 군집화


조대협 (http://bcho.tistory.com)


Hierarchical clustering (한글 : 계층적 군집 분석) 은 비슷한 군집끼리 묶어 가면서 최종 적으로는 하나의 케이스가 될때까지 군집을 묶는 클러스터링 알고리즘이다.

군집간의 거리를 기반으로 클러스터링을 하는 알고리즘이며, K Means와는 다르게 군집의 수를 미리 정해주지 않아도 된다. 참고로 이 글에서 사용된 예제 코드는 https://github.com/bwcho75/dataanalyticsandML/blob/master/Clustering/3.%20Hierarchical%20clustering-IRIS%204%20feature.ipynb 에 저장되어 있다.


예를 들어서 설명해보자

“진돗개,세퍼드,요크셔테리어,푸들, 물소, 젖소" 를 계층적 군집 분석을 하게 되면

첫번째는 중형견, 소형견, 소와 같은 군집으로 3개의 군집으로 묶일 수 있다.


이를 한번 더 군집화 하게 되면 [진돗개,셰퍼드] 와 [요크셔테리어,푸들] 군집은 하나의 군집(개)로 묶일 수 있다.


마지막으로 한번 더 군집화를 하게 되면 전체가 한군집(동물)으로 묶이게 된다.


이렇게 단계별로 계층을 따라가면서 군집을 하는 것을 계층적 군집 분석이라고 한다.

계층적 군집 분석은 Dendrogram이라는 그래프를 이용하면 손쉽게 시각화 할 수 있다.





계층형 군집화에 대한 좀 더 상세한 개념은 https://www.slideshare.net/pierluca.lanzi/dmtm-lecture-12-hierarchical-clustering?qid=94d8b25a-8cfa-421c-9ed5-03c0b33c29fb&v=&b=&from_search=1 를 보면 잘 나와 있다.


skLearn을 이용한 계층 분석 모델 구현

개념을 잡았으면 실제로 계층 분석 모델을 구현해보자.

데이타는 K Means에서 사용했던 IRIS 데이타를 똑같이 사용한다.

이번에는 4개의 피쳐를 이용해서 사용한다.


from sklearn import datasets
import pandas as pd
iris = datasets.load_iris()

labels = pd.DataFrame(iris.target)
labels.columns=['labels']
data = pd.DataFrame(iris.data)
data.columns=['Sepal length','Sepal width','Petal length','Petal width']
data = pd.concat([data,labels],axis=1)


다음은 IRIS 데이타를 이용하여 dendrogram을 그려보자

# Perform the necessary imports
from scipy.cluster.hierarchy import linkage, dendrogram
import matplotlib.pyplot as plt

# Calculate the linkage: mergings
mergings = linkage(data,method='complete')

# Plot the dendrogram, using varieties as labels
plt.figure(figsize=(40,20))
dendrogram(mergings,
          labels = labels.as_matrix(columns=['labels']),
          leaf_rotation=90,
          leaf_font_size=20,
)
plt.show()


먼저 linkage 함수를 import 한 다음 linkage 함수에 data를 넘겨주면 Hierarchical clustering을 수행한다. 이때 method=’complete’로 정했는데, 이 부분은 뒤에서 설명한다.

Hierarchical clustering 한 결과를 dendrogram 함수를 이용하여 dendrogram 그래프를 표현해 보면 다음과 같이 출력된다.




계층 분석 방식

앞의 코드에서, linkage 함수에서 method 를 사용했다. 이에 대해서 알아보자.

Hierachical clustering의 기본 원리는 두 클러스터 사이의 거리를 측정해서 거리가 가까운 클러스터끼리 묶는 방식이다.  그러면 두 클러스터의 거리를 측정할때 어디를 기준점으로 할것인가를 결정해야 하는데 다음 그림을 보자.



출처 : https://www.multid.se/genex/onlinehelp/hs515.htm


앞의 코드에서 사용한 complete linkage 방식은 두 클러스터상에서 가장 먼 거리를 이용해서 측정하는 방식이고 반대로  single linkage 방식은 두 클러스터에서 가장 가까운 거리를 사용하는 방식이다.

average linkage 방식은 각 클러스터내의 각 점에서 다른 클러스터내의 모든 점사이의 거리에 대한 평균을 사용하는 방식이다.


이 linkage 방식에 따라서 군집이 되는 모양이 다르기 때문에, 데이타의 분포에 따라서 적절한 linkage  방식을 변화 시켜가면서 적용해가는 것이 좋다.


계층 분석을 통한 군집의 결정

계층 분석은 최종적으로 1개의 군집으로 모든 데이타를 클러스터링 하는데, 그렇다면 n개의 군집으로 나누려면 어떻게 해야 하는가?

아래 dendrogram을 보자 y축이 각 클러스터간의 거리를 나타내는데, 위로 올라갈 수 록 클러스터가 병합되는 것을 볼 수 있다.




즉 적정 y 값에서 클러스터링을 멈추면 n개의 군집 까지만 클러스터링이 되는데, 위의 그림은 y 값을 3에서 클러스터링을 멈춰서 총 3개의 클러스터로 구분을 한 결과이다.


이렇게 계층형 분석에서 sklearn을 사용할 경우 fcluster 함수를 이용하면, 특정 y값에서 클러스터링을 멈출 수 있다. 다음 코드를 보자.


from scipy.cluster.hierarchy import fcluster

predict = pd.DataFrame(fcluster(mergings,3,criterion='distance'))
predict.columns=['predict']
ct = pd.crosstab(predict['predict'],labels['labels'])
print(ct)


앞의 코드에서 계층형 클러스터링을 한 mergings 변수를 fcluster 함수에 전달하고 두번째 인자에 y의 임계값을 3으로 지정하였다. Predict 컬럼에는 원본 입력데이타에 대한 예측 결과 (어느 클러스터에 속해있는지를 0,1,2로 입력 데이타의 수만큼 리턴한다.)를 리턴한다.


이를 원본 데이타의 라벨인 labels[‘label’]값과 Cross tabulation 분석을 해보았다.




세로축이 예측 결과, 가로측이 원래 값이다.

원래 label이 0인 데이타와 1인 데이타는 각각 잘 분류가 되었고, 2인 데이타는 34개만 정확하게 분류가 되었고 16개는 원본 레이블이 1인 데이타로 분류가 되었다.


지금까지 Hierachical clustering model에 대해서 알아보았다. K Means와 같은 군집화 모델이라도 내부 알고리즘에 따라서 군집화 결과가 다르기 때문에, 샘플 데이타의 분포를 보고 적절한 클러스터링 모델을 고르는 것이 필요하다. 다행이 sklearn의 경우 복잡한 수식 이해 없이도 간단한 라이브러리 형태로 다양한 클러스터링 모델 사용할 수 있도록 해놨기 때문에, 여러 모델을 적용해가면서 적정한 데이타 분류 방식을 찾아보는 것이 어떨까 한다.




MySQL 클러스터링을 위한 Galera Cluster

아키텍쳐 | 2015.11.18 23:52 | Posted by 조대협

MySQL Galera Replication


조대협 (http://bcho.tistory.com)


RDBMS 오픈소스 중에서 단연 가장 많이 사용되는 것은 MySQL인데, 근래에 웹 스케일이 커지면서, 단일 인스턴스로만 서비스가 불가능한 용량까지 가게 되서, 이 MySQL에 대한 클러스터링 스케일링에 대한 이슈가 많아졌다. 이에 Tungsten, MySQL Replication, NDB, Galera 등 다양한 클러스터링 방법이 있는데, 그중에서 갈레라 클러스터링 (Galera Clustering)에 대해서 간단하게 정리하고자 한다.


MySQL Replication


갈레라 클러스터링을 이해하기에 앞서서 먼저 가장 널리(그리고 쉽게) 사용되는 MySQL Replication 방식에 대해서 알아보자. MySQL Replication 방식은 Master/Slave 방식의 구성이 일반적이며, 이 구성의 경우 특정 노드는 쓰기를 담당하고 나머지 노드는 읽기를 담당하는 형태로 구성이 된다.

통상적으로 데이타 베이스 트랜젝션의 60~80%가 읽기 트렌젝션이기 때문에, 이러한 구조를 사용하더라도 충분히 성능의 향상을 기대할 수 있다.


다음 그림은 MySQL Replication 의 간단한 구조도 이다.

 




먼저 좌측의 Master Node에 쓰기 트렌젝션이 수행되면 Master node는 데이타를 저장하고, 트렌젝션에 대한 로그를 내부적으로 BIN LOG라는 파일에 저장한다. (시간 순서대로 수행한 업데이트 트렌젝션이 기록되어 있다.)


Slave Node에서는 이 BIN LOG를 복사해온다. 이 복사 작업을 IO Thread라는 스레드가 수행하는데, 이렇게 읽어온 내용은 Replay Log라는 파일에 기록이 된다. 이렇게 기록된 내용은 SQL Thread라는 스레드가 읽어서, 하나씩 수행을 해서  MySQL 데이타 파일에 기록을 한다.


쉽게 설명하면, insert 쿼리를 master node에서 실행했으면 그 쿼리가 master node의 bin log에 기록이 되고, 이 내용은 slave node에 복사가 된후에, slave node에서 같은 쿼리가 수행이 되서 복제가 반영되는 방식이다.


방식이 단순해서 신뢰도가 높은 반면, 단점으로는

  • 읽기와 쓰기 노드를 분리해야 하며,
  • 데이타 복제가 동기 방식이 아닌 비동기 방식으로 적용된다. 바꿔서 말하면, master node에 적용한 데이타 변경사항이 slave에 반영될때까지 일정 시간이 걸린다는 것으로, master와 slave node간의 순간적인 데이타 불일치성이 발생할 수 있다는 것이다.


Galera cluster


Galera cluster는 http://galeracluster.com/ 에서 제공되는 오픈소스로, 동기방식의 복제 구조를 사용하고 있다.

간단하게 구조를 살펴보자 아래 그림을 보면 




각각의 노드가 있을때, 아무 노드에나 쓰기나 업데이트가 발생하면, 모든 노드에 데이타를 복사를 완료하고 나서, 업데이트 내용이 파일에 저장된다. 아키텍쳐상의 구조를 보면, 위의 그림과 같이 각 MySQL 노드에는 WSREP 라는 모듈이 있다. 이 모듈은 데이타베이스에 복제를 위한 범용 모듈로 여기에 마치 드라이버처럼 Galera replication module을 연결해주면 데이타 변경이 있을때 마다, 이 Garela replication module이 다른 mysql node로 데이타를 복제한다.


약간 더 구체적인 구조를 살펴보면 노드간의 데이타 복제는 다음과 같은 흐름을 따르게 된다. 





노드에 트랜젝션이 발생하고 COMMIT이 실행이되면, 디스크에 내용을 쓰기 전에 다른 노드로 복제를 요청하고 다른 노드에 복제 요청이 접수되었을때, 해당 노드의 디스크에 실제로 데이타를 쓰게 된다.


이러한 특성으로, 전체 노드에 데이타가 항상 일관성있게 저장되고, 모든 노드가 마스터 노드로 작동을 하며, 특정 노드가 장애가 나더라도 서비스에 크게 문제가 없다. 

(MySQL Replication의 경우 마스터 노드가 장애가 나면 슬레이브 노드중 하나를 마스터로 승격을 해야하는 등 다소 운영 프로세스가 갈레라에 비해서는 복잡하다.)


상당히 좋아 보이는 구조이기는 한데, 반대로 가지는 단점도 만만하지 않다.


성능

먼저 성능적인 부분에서, 데이타를 디스크에 저장하기 전에, 다른 모든 노드에 데이타 복제 요청을 해야 하기 때문에, 비동기 방식의 MySQL Replication에 비해서, 쓰기 성능이 떨어지는 것으로 보인다.


장애 전파

이렇게 다른 노드에 복제 요청을 하는 클러스터 구조의 경우, 장애를 다른 노드로 전파 시킬 가능성이 높은데, 예전에 대표적인 웹 애플리케이션 서버인 웹로직의 경우 유사한 세션 클러스터링 구조를 사용했다. 

이 경우 복제를 요청했을때 복제 요청을 받은 노드가 장애 상황 특히 느려지거나 일시적으로 멈췄으때, 복제를 요청한 노드가 응답을 받지 못하고 대기하게 되고, 이 대기한 노드에 다른 노드가 복제를 또 요청하면, 같은 이유로 복제가 지연 되면서 클러스터를 타고 장애가 전파되는 현상을 야기하게 된다.

그래서 갈레라 클러스터의 경우 LOCK문제가 생기거나 슬로우 쿼리들이 많이 발생할때 장애를 전파시킬 수 있는 잠재적인 문제를 가지고 있다.


스케일링의 한계

갈레라가 모든 노드에 데이타를 복제하고 트렌젝션을 끝내는 만큼, 전체적인 노드수가 많아지게 되면, 복제를 하는데 그만큼 시간이 많이 걸림에 따라, 하나의 클러스터에서 유지할 수 있는 노드의 수가 한계가 있어져서, 횡적 스케일링의 한계가 올 수 있다. 


이런 단점에도 불구하고, 모든 노드에 읽기 쓰기가 가능한 멀티 마스터 구조와 모든 노드의 데이타를 일관적으로 유지 시켜준다는 장점과 쉬운 설정 방법으로 인하여 MySQL 클러스터를 구성한다면 한번쯤 검토해봐야 하는 솔루션이 아닌가 한다.


(아쉽게도 국내 사례는 그다지 많지 않은듯...)


몇가지 참고 사항

  • 갈레라 클러스터는 서로 다른 MySQL 버전간에도 클러스터로 묶을 수 있다.
  • 갈레라 클러스터에서 노드가 떨어졌다가 붙으면 일정 부분은 GTID (Global Transaction ID)를 이용하여, 데이타가 복제 되지 않은 델타 부분만 복제가 가능하지만, 시차가 오래되 버리면 풀 백업본을 가져다 엎어야 한다. (풀백업은 복구는 시간이 많이 걸림)




'아키텍쳐' 카테고리의 다른 글

MySQL 클러스터링을 위한 Galera Cluster  (3) 2015.11.18
요구 사항 정의 기법  (0) 2013.11.13
소프트웨어 개발팀의 구조  (0) 2013.11.01
Technical Debt  (1) 2013.10.30
License Key Management  (0) 2013.08.01
암호화 알고리즘 속도 비교 (대칭키)  (0) 2013.07.17

Node.js vs Vert.x 비교


간단하게 정리해본 Node.JS Vert.x의 장단점 비교,

두 서버 모두 C10K 문제를 해결 하기 위한 Single Thread 기반의 고성능 비동기 서버이다.

C10K 문제는 대용량 (10,000개이상의 동시 커넥션을)처리 하기 위한 문제로 전통적인 Tomcat등의 WAS에서는 이 문제를 해결할 수 없다 들어온 request는 무조건 큐잉이 되었다가 뒷단의 멀티 쓰레드에 의해서 작업이 처리되는데 이 멀티 쓰레드의 수 만큼만 동시 사용자를 처리할 수 있는 개념이다.

반대로 이 두 서버들은 일단 Connection이 연결되면, 연결된 socket들을 물고 있다가, Single Thread Event Loop가 고속으로 돌면서, socket에 들어온 메세지들을 처리하고 빠지는 구조이기 때문에 하나의 Thread임에도 불구하고, 동시에 여러 사용자를 처리할 수 있는 장점을 가지고 있다.

Vert.x Node.js에 영감을 받고 만들어진 서버이기 때문에, 그 특성이 매우 비슷하지만, 장단점이 극명하게 들어난다.

 

Node.js

Vert.x

내부 엔진 기반

Google Chrome V8 자바스크립트 엔진

libuv 기반의 비동기 처리 IO

Netty 기반의 NW IO

Hazel Case 기반 클러스터링

구현언어

C

Java

사용가능 언어

Javascript(애플리케이션), C (네이티브 모듈)

Python,JavaScript,Java,Groovy,Scala

외부 모듈

40,000개 이상

100개이하

클러스터링

한 하드웨어에 여러개 node.js를 띄울 수 있음. Node.js 인스턴스간 상태 Share 불가

한 하드웨어에 여러개의 vertx를 띄울 수 있음. node간의 상태 공유 메세징 가능
(HazelCast
기반)

에코시스템

매우 풍부. 레퍼런스,서적,교육,컨설팅 기관

공식 서적 2 (2권다 100페이지 이하))
컨설팅,교육 업체 없음

성능

열세
(
node 인스턴스당 CPU 코어 1개 이상 사용이 불가함)

우세
(JVM
기반으로 하나의 vert.x인스턴스에 여러개의 verticle 인스턴스를 띄워서 CPU 사용률을 극대화 할 수 있음)

한 마디로 정리하면, Vert.xHazelCast기반의 IMDG (In memory data grid)를 가지고 있어서, 클러스터링 기능이 좋으며, JVM기반으로, 여러 개의 Verticle을 동시에 띄어서 CPU 사용률을 극대화 함에 따라 더 높은 성능을 낼 수 있으며, 여러 프로그래밍 언어를 지원한다. 기술적으로는 Vert.x가 우세적인 면이 있으나, 아직 에코 시스템이 제대로 형성되지 않아서 기술 지원이나 자료를 구하기가 어려워, 기술 습득이나 운영 유지보수에서는 Node.JS가 우세이다.

 



 

비동기 네트워크 서버 프레임웍 Vert.x

조대협

 

* 서문

Vert.x는 NodeJS와 같은 비동기 소켓서버 프레임웍이다.

Vert.x에 대한 이해를 돕기 위해서, Tomcat과 같은 WebApplication Server(이하 WAS)에 대해서 먼저 간단하게 짚고 넘어가자.

 


<그림. Tomcat의 쓰레드 구조>

Tomcat의 경우에는 HTTP request가 들어오면, request가 앞의 request Queue에 쌓이게 된다.

쌓이게된 request들은 Thread Pool에 있는 Thread에게 하나씩 할당되어, request를 처리하고, 작업이 끝나면, request가 들어온 connection으로 response를 보낸후, 작업을 끝낸다. 작업이 끝난 Thread는 다시 Thread Pool로 들어간다.

이런 구조에서, Tomcat이 순간적으로 동시에 처리할 수 있는 Connection의 수는 Thread Pool의 Thread 수만큼이 되는데, 일반적으로 Tomcat은 50~500개의 Thread정도가 적절하다.

즉, Tomcat 서버 하나는 동시에 최대 500여개의 Connection을 처리할 수 있다고 보면 된다.

 

요즘 들어, 서비스의 규모와 용량이 커짐에 따라서 동시에 여러개의 Connection을 처리해야 하는 기능이 필요하게 되었는데, 이러한 WAS로는 수십만,수백만개의 Connection을 동시에 처리하는 것이 불가능하다.

또한, HTTP 뿐만 아니라, TCP와 같은 다른 request를 처리해야 하는 여러가지 Protocol 지원 문제도 있고

단순히 request/response 기반의 HTTP 요청 뿐만 아니라, HTTP long polling/Streaming과 같은 Push성 서비스를 구현하려면, 동시에 유지되어야 하는 Connection수가 많아야 한다. (모든 클라이언트가 Conenction을 물고 있기 때문에)

이러한 요구 사항을 반영하기 위해서 나온 서버들이 nodejs와 같은 비동기 소켓 서버이다.

 

Vert.x는 nodejs의 자바 버전정도로 보면 되는데, 아주 재미있는 것이 구조에서 부터 기능까지 node.js에 비해서 발전된 모습이 매우 많다.

Vert.x는 nodejs와 마찬가지로 single thread model이다.

WAS와 같이 Thread pool을 이용하는 것이 아니라, 하나의 Thread로 모든 작업을 처리한다. Single Thread 모델을 사용할 경우 Thread의 Context Switching 오버헤드를 줄일 수 있어서 성능에 도움이 되고, 또한 Multi threading에서 고민해야 하는 locking 처리나 공유 데이타 처리에 대해서 전혀 걱정할 필요가 없다.

요즘 트렌드인 것 같은데, 요즘 유행하는 고속 서버들 nginx 웹서버나, redis와 같은 IMDB역시 single thread 모델을 사용한다.

Vert.x는 Single Thread로 도는데, 이를 Event Loop(EL)이라고 한다. Vert.x에 일단 클라이언트가 연결되면, EventLoop가 각 연결된 개별 socket들에 대해 event를 검사한후에, event가 있으면 그 function을 수행해준다.



<그림.Vert.x의 Single Thread 구조>

예를 들어서, 클라이언트 A,B,C와 소켓 SA,SB,SC 로 연결이 되어 있다면. Single Thread에서 Event Loop가 돌면서, 첫번째는 SA에 대한 loop를 돌고, 두번째는 SB,세번째는 SC에 대한 루프를 돈다. 루프를 돌다가 소켓에서 메세지가 들어오거나 연결이 끊기거나 기타 이벤트가 있으면, 그 이벤트를 처리한다.



< 그림. Event Loop의 개념>

모든 연결된 소켓에 대해서 Event loop가 도는데, 보통 하나의 Event Loop를 처리하는 Thread에서 10,000~20,000개 정도의 Connection을 처리하는데는 큰 무리가 없다. (백기선氏 감사)

물론, 이벤트 발생 비율이 많고, 이벤트를 처리하는 로직에 소요되는 시간이 높다면 당연히 성능은 떨어진다. 전체 클라이언트로 메세지를 Broadcast 하면, 당연히 성능이 내려가겠지만, 일반적인 P2P 형식의 이벤트 처리에는 큰 무리가 없다.

 

* Vert.x 특징 들여다 보기

그러면 Vert.x의 몇가지 기술적인 특성을 살펴보도록 하자.

 

1. 내부 컴포넌트 구성

Vert.x를 만든 사람들이 대단하다고 느끼는 점은, 다 처음부터 개발한 것이 아니라 대부분의 모듈을 기존의 오픈소스들을 기반으로 해서 개발하였다는 것이다.

고속 네트워크 처리를 위해서는 국내 개발자 이희승씨가 만들어서 더 유명한 Apache Netty를 서버 엔진으로 사용하고 있고, Vert.x 노드간의 통신을 지원하기 위해서, 데이타그리드 솔루션인 HazelCast를 사용하고 있다.

 클러스터링과, 네트워크 고속 처리 부분을 신뢰가 가는 오픈 소스를 기반으로 했으니, 안정성이 좋은 것은 두말할 나위가 없다.

 

2. 다양한 언어 지원 (Node.js는 Javascipt만 된다.)

Vert.x에서 돌아가는 프로그램을 Verticle이라고 하는데, 쉽게 생각해서 Java의 서블릿(Servlet)으로 생각하면 된다.

흥미로운 점은 Verticle은 JVM 위에서 구동하는 여러가지 언어로 구현이 가능하다는 것이다. 현재까지 Java,JavaScript,Python,Groovy,Scala 를 지원하고 있다.

실제로 써보면 알겠지만, 정말 재미있다. Python으로 구현했다가 필요하면 Java로 구현했다가. :)

 

3. 클러스터링 지원

Vert.x는 클러스터링 지원이 가능하다. 여러개의 Vert.x 서버(instance)를 띄워서 구동이 가능하고, Vert.x 서버간에 통신이 가능하다. (메세지를 보낼 수 있다.)

단, Vert.x 인스턴스간의 데이타 공유는 아직까지는 불가능하다. 백엔드에 HazelCast 데이타 그리드 (일종의 클러스터된 공유 메모리)를 사용하고 있으니, 조만간에 Vert.x 인스턴스간 데이타 공유가 가능하지 않을까 싶다.

Vert.x의 기능을 사용하지 않더라도, 내장되 HazelCast 기능을 사용하면, 인스턴스간의 데이타 공유 구현은 가능하다.

※ 참고로, Vert.x에 embedded된, HazelCast는 Community 버전이다. (무료 버전). HazelCast는 자바 기반이기 때문에, 대용량 메모리를 사용하게 되면, Full GC가 발생할때, 시스템의 순간적인 멈춤 현상이 발생하기 때문에, 이를 감안해서 사용하거나, 또는 상용 버전을 사용하면 Direct Memory라는 개념을 사용하는데, 이는 Java Heap을 사용하지 않고, Native 메모리를 바로 접근 및 관리 함으로써, 대용량 메모리를 GC Time이 없이 사용할 수 있다. HazelCast도 기능상으로 보면 대단히 좋은 솔루션이기 때문에, 한번 살펴보기를 권장한다.

 

4. 멀티 인스턴스 지원을 통한 Node.JS보다 빠른 성능

Vert.x의 ELP는 Single Thread에서 동작하지만, 동시에 여러개의 ELP를 띄울 수 있다. Multi Thread를 띄워서, 동시에 여러개의 Verticle을 실행할 수 있다는 이야기다. 자칫하면 WAS의 Multi threading 모델과 헷갈릴 수 있는데, 여러개의 Thread를 띄우더라도, 각 Thread는 독립적은 ELP를 가지고 동작하고, WAS의 MultiThread 모델과는 다르게, 각 Thread간에 객체 공유나 자원의 공유가 없이 전혀 다르게 독립적으로 동작한다.

이렇게 하나의 Instance에서 여러개의 Thread를 띄울 수 있기 때문에, Multi Core Machine에서는 좋은 성능을 낼 수 있다. Node.js의 경우 Single Thread기반의 ELP를 하나만 띄울 수 있기 때문에, Core수가 많아서, 이를 사용할 Thread가 없다. 그래서 Core가 많은 Machine에서 성능이 크게 늘어나지 않는 반면에, Vert.x는 동시에 여러개의 Thread에서 여러개의 ELP를 수행하기 때문에 성능이 더 높게 나온다. 물론 Node.js도 여러개의 Process를 동시에 띄워서 여러개의 Core를 동시에 사용할 수 는 있지만 Process의 Context Switching 비용이 Thread의 Context Switching 비용보다 크기 때문에, 여러개의 Thread 기반으로 동작하는 Vert.x가 성능에서 유리할 수 밖에 없다.

아래는 Node.JS vs Vert.x의 성능을 비교한 자료로

http://vertxproject.wordpress.com/2012/05/09/vert-x-vs-node-js-simple-http-benchmarks/



small static file을 서비스 하는 성능 측정 결과이다.

http://vertxproject.files.wordpress.com/2012/05/chart_3-5.png?w=640

 

5. Embedded Vertx

Vert.x는 자체가 서버로써 독립적으로 동작할 수 있을 뿐만 아니라 라이브러리 형태로도 사용이 가능하다. 즉 Tomcat같은 WAS에 붙어서 기동이 될 수 있다. 하나의 JVM에서 Tomcat 서비스와 Vert.x 서비스를 같이 수행하는 것이 가능하다. 예를 들어서 일반적인 HTTP Request는 Tomcat으로 처리하고, Socket.IO나 WebSocket과 같은 Concurrent connection이 많이 필요한 Request는 Vert.x 모듈을 이용해서 처리하게할 수 있다.

 

* Vert.x 개념 잡기

그럼 이제 부터 Vert.x의 개념에 대해서 알아보자. Vert.x를 공부하면서 가장 힘들었던 점이 이 부분이다. 메뉴얼이나 기타 설명된 자료들이 아주 함축적이어서, 직접 테스트와, Thread dump, Class Loader등을 통해서 분석한것과 Vert.x google group를 통해서 분석한 내용들을 정리 하였다.

 

1. Vert.x instance

Vert.x instance는 하나의 Vert.x 서버 프로세스로 보면 된다. 하나의 Vert.x JVM 프로세스를 하나의 Vert.x instance라고 이해하면 된다.


2. 일반 Verticle (aka. standard verticle, ELP verticle)

먼저 Verticle이다. Verticle은 Vert.x에서 수행되는 하나의 프로그램을 이야기 한다. 앞서 설명했듯이, 자바의 Servlet과 같은 개념으로 이해 하면 된다.


3. Verticle instance

Verticle 코드가 로딩되서 객체화 되면, 이를 Verticle instance(하나의 Verticle Object)라고 한다.

Verticle은 ELP안에서 수행이 되며, 항상 같은 쓰레드에서 수행이 된다. Verticle instance는 절대 multi thread로 동작하지 않고, single thread에서만 동작한다. (중요)

개념 설명을 돕기 위해서 아래 그림을 보자.

 



하나의 Vert.x instance는 하나의 JVM위에서 동작한다.

Verticle Code는 각 Verticle instance당 할당된 Class Loader에 의해서 로딩되고, 객체화가 된다. 위의 그림은 Verticle A,B,C 3개가 있고, 각각의 클래스 로더에 의해서 로딩된 경우이다. 그리고 ELP Thread가 하나인 경우인데, 이 경우, Verticle Instance 들은 Queue에서 기다리게 되고, 순차적으로 ELP Thread를 점유하면서 순차적으로 실행된다. 단 수행되는 순간에는 Single Thread 모델이기 때문에, 하나의 Verticle만 그 순간에는 수행된다.(즉, Verticle이 100개이고, ELP가 1개라도 모든 100개의 Verticle은 이 ELP 한개에서 순차적으로 수행된다.)

Vert.x에서는 CPU Core수에 따라서 자동으로 ELP Thread를 생성해준다.

ELP Thread 수를 지정하려면 vertx.pool.eventloop.size를 System Property로 주면 된다. 참고로 ELP Thread의 수는 CPU수 보다 작아야 Thread Context switcing이 발생하지 않아서 조금 더 높은 성능을 낼 수 있다.


<그림. Vert.x 소스코드중 ELP 쓰레드를 생성하는 코드>

Vert.x 엔진 코드 상으로 보면 CPU Core수의 2배로 ELP Thread를 생성하는데, 실제로 테스트 해보니, 8 Core에서 ELP Thread가 2개 밖에 안생긴다. (몬가 이상한걸?)

 

동시에 여러개의 ELP Thead를 생성할 수 있기 때문에 같은 Verticle이라도 여러개의 Thread에서 수행할 수 있다. 여기에서 헷갈리지 말아야 하는 것이. 같은 Verticle Code일지는 몰라도, Verticle Instance는 각기 다른 인스턴스이다.

아래 그림에서와 같이 Verticle A Code에 대한 Verticle instance를 3개를 생성하게 되면, 각각 다른 Class Loader에 의해서 Verticle instance가 생성된다. (자바의 같은 Servlet코드를 다른 3개의 war 파일로 패키징되는 것과 같음). 그래서 JVM 입장에서는 이 3개의 Verticle Instance는 전혀 다른 Object가 되고, 각각 다 독립된 ELP에서 다르게 수행이 된다. 그래서 전혀 Verticle Instance간에 공유하는 것이 없고 결과적으로 Thread Safe하게 수행된다.

 



Verticle을 배포할 때 instance수를 정할 수 있는데, 위의 그림은 Verticle A는 3개, B는 2개, C,D,E,F는 각각 1개의 Verticle Instance를 배포한 예이다. 각 Instance들은 ELP의 Queue로 들어가서 항상 같은 Thread에서만 수행된다.

 

4. Worker Verticle

앞서 설명했듯이, 일반 Verticle은 개별 socket에 대해서 ELP를 돌면서 Event 처리를 한다. 그래서 해당 Event 처리에 오랜 시간이 걸리면 전체적으로 많은 성능이 떨어지기 때문에, 문제가 된다.

예를 들어 하나의 event 처리에 100ms 가 걸리는 작업이 있다면, 연결된 Connection이 100개가 있을 경우 전체 socket에 대해서 한번 이벤트 처리를 하는데 10초가 걸리고, 처리가된 소켓이 다음 이벤트를 받을 수 있을 때 까지 10초가 걸린다. 즉 클라이언트 입장에서 연속적으로 요청을 받았을때, 두번째 요청에 대해서 응답을 받는 것은 10초후라는 이야기가 된다. 이는 Single Thread 모델이기 때문에 발생하는 문제인데, Vert.x에서는 이런 문제를 해결하기 위해서 Worker Verticle 이라는 형태의 Verticle을 제공한다.

이 Worker Verticle은 쉽게 생각하면, Message Queue를 Listen하는 message subscriber라고 생각하면 된다. Vert.x 내부의 event bus를 이용해서 메세지를 보내면, 뒷단의 Worker Verticle이 message Queue에서 메세지를 받아서 처리를 한 후에 그 결과 값을 다시 event bus를 통해서 caller에게 보내는 형태이다. (Asynchronous call back 패턴)

event bus로 request를 보낸 Verticle은 response를 기다리지 않고, 바로 다음 로직을 진행하다가, Worker Verticle에서 작업이 끝난 이벤트 메세지가 오면 다음 ELP가 돌때 그 이벤트를 받아서 응답 메세지 처리를 한다.

DB 작업이나, 시간이 오래 걸리는 작업은 이렇게 Worker Verticle을 이용해서 구현할 수 있다.





5. Worker Verticle instance & Thread pooling

이 Worker Verticle의 재미있는 점은 Thread Pool을 지원한다는 것이다. ELP Verticle과 마찬가지로, Worker Verticle instance는 독립된 클래스 로드로 로딩되어 생성된 객체로 쓰레드에서 각각 다른 독립된 instance가 수행되기 때문에, Thread safe하다.

단 Worker Thread의 경우 공용 Thread Pool에서 수행된다. ELP Thread의 경우 Verticle은 항상 지정된 Thread에서 수행되는데 반해, Worker Verticle Instance는 Thread Pool의 아무 쓰레드나 유휴 쓰레드에서 수행이 된다.


6. Event Bus

Event Bus는 일종의 Message Queue와 같은 개념으로, Verticle 간에 통신이나, Vert.x Instance간의 통신이 가능하게 한다.

Verticle간의 통신이란 Java로 만든 Verticle에서 Python으로 만든 Verticle로 메세지 전달이 가능하다. 또한 Event Bus를 이용하면 서로 분리되어 있는 (다른 JVM 프로세스로 기동되는 또는 서로 다른 하드웨어에서 기동되고 있는) Verticle 간에 메세지를 주고 받을 수 있다.

이 메세지 통신은 1:1 (Peer to Peer) 통신 뿐만 아니라, 1:N (Publish & Subscribe) 형태의 통신까지 함께 지원한다.

여타 일반적인 Message Queue와 다른 특성은 일반적인 Message Queue의 경우 Fire & Forget 형태의 Message exchange pattern을 사용하는데 반해서, Event Bus는 Call back pattern을 사용한다. 

즉, JMS나 RabbitMQ의 일반적인 경우 클라이언트가 메세지 큐에 메세지를 넣고 바로 리턴하는데 반해서, Event Bus를 이용하면, 클라이언트가 메세지를 넣고 바로 리턴이 된 다음, 큐를 사용하는 Consumer가 메세지를 처리한 후에, 메세지를 보낸 클라이언트에게 다시 메세지를 보내서, 처리를 하도록 할 수 있다.

예를 들어 하나의 HTTP request가 들어왔을때, event bus에 메세지를 넣고 해당 클라이언트는 내부적으로 리턴이 된다.(HTTP response는 보내지 않고, Connection은 물고 있다.) 그 후에 Worker Verticle에서 작업을 처리한 후 Call back rely를 보내면, 클라이언트에서 이 이벤트를 받아서 HTTP connection으로 response를 보낼 수 있다.

이 구조가 있기 때문에, Single Thread모델임에도 불구하고 뒷단에서 비동기 메세지를 처리하는 방식을 이용하여 long transaction도 핸들링할 수 있게 된다.


7. Shared data 처리

하나의 Vert.x instance안에서만 공유 가능하다. (다른 Vert.x 인스턴스간에는 아직까지는 불가능)

ConcurrentMap<String, Integer> map = vertx.sharedData().getMap("demo.mymap");

을 통해서 Map을 사용하거나

Set<String> set = vertx.sharedData().getSet("demo.myset");

을 통해서 Set를 사용할 수 있다.


8. Module

모듈은 하나의 Runnable Application으로, 자바의 일종의 WAR 파일이라고 생각하면 된다.

mod.json (WAR 파일의 web.xml 과 같은 메타 정보 description) 에 메타 정보를 정의한 후에, 클래스와 jar 파일등의 라이브러리, 그리고 기타 애플리케이션에서 사용할 파일들을 같이 묶어서 패키징 한 형태이다.

이 Module은 Maven repository 시스템등에 저장 및 배포될 수 있다. (vagrant나 docker 컨셉과 비슷한듯. 이제 Runtime application도 repository를 사용하는 추세인가 보다)


9. Pumping

네트워크 Flow Control을 해주는 기능으로,  읽는게 쓰는거 보다 빠르면(예를 들어 socket에서 읽고, 파일에 쓰는 ) Queuing이 많이되고, 메모리 소모가 많아져서 exhausted된다. 이를 막이 위해서 쓰다가 Q가 차면 socket read를 멈추고 있다가 write q가 여유가 생기면 다시 읽는 것과 같은 flow control이 필요한데, 이를 pump로고 한다. 


10. HA (High Availibility)

Vert.x는 HA 개념을 지원한다. Vert.x에서 HA란, HA mode로 Vert.x instance를 구성했을때, 해당 Vert.x instance가 비정상 종료 (kill등) 되면, 해당 instance에서 돌고 있던 Vert.x Module을 다른 Vert.x Instance로 자동으로 옮겨서 실행해주는 기능이다.

실행시 -ha 옵션을 줘서 실행하는데, 하나의 클러스터 내에서도 hagroup을 지정하여 그룹핑이 가능하다. 예를 들어 클러스터에 10개의 노드가 있을때, 4개는 업무 A용 HA그룹으로 나누고, 6개는 업무 B용으로 나누는 식으로 업무별로 나눌 수 도 있고

Machine이 3개가 있을고, 2개의 업무를 수행하는데, 각 Machine마다 Vert.x instance를 2개씩 돌린다고 했을때

Machine 1: vertx.instance=hagroup_A , vertx.instance2=hagroup_B 

Machine 2: vertx.instance=hagroup_A , vertx.instance2=hagroup_B 

Machine 3: vertx.instance=hagroup_A , vertx.instance2=hagroup_B 

이런식으로 구성을 하게 되면, Machine 단위로 HA가 넘어가도록 구성을 할 수 있다.


11. Clustering

HA 없이 클러스터링 구성만을 할 수 있는데, event_bus로 메세지를 주고 받을려면, 해당 vert.x instance들이 같은 cluster내에 들어 있어야 한다.

※ 설정은 conf/cluster.xml 을 이용해서 하는데, 이 파일은 hazelcast의 클러스터링 설정 파일이다. Default로는 multicast를 이용해서 cluster의 멤버를 찾도록 하는데, 인프라 특성상 multicast가 안되는 경우에는 (아마존과 같은 클라우드는 멀티캐스트를 지원하지 않는 경우가 많음). 직접 클러스터 노드들의 TCP-IP 주소를 적어두도록 한다.

또한 서버의 NIC가 여러개인 경우에는 어떤 NIC를 사용할지 IP주소로 명시적으로 정의해줘야 한다. (유선,무선랜이 있는 경우 등). 직접 주소를 정해주지 않는 경우, HazelCast의 경우 NIC리스트에 있는 순서중 첫번째 있는 주소를 클러스터 주소로 사용한다.


* Hello Vert.x

간단한 Vertx 테스트


1. 설치 : Vertx의 설치는 어렵지 않다. http://vertx.io/install.html 를 따라서 하면 되고, 현재 2.1M5 (2014년2월6일 현재) 버전이 최신 버전이다.

주의할점이 인터넷에 돌아다니는 문서 (특히 한글문서)중에서 1.3x로 설치 가이드 하고 있는 문서들이 있다. 특정 코드나 예제가 안돌아 갈 수 있으니, 반드시 최신 버전을 확인하고 설치하기 바란다.


2. 코드 테스트

아래와 같이 server.js 라는 코드를 만들고

var vertx = require('vertx');


vertx.createHttpServer().requestHandler(function(req) {

  req.response.end("Hello World!");

}).listen(8080, 'localhost');


다음으로 vertx run server.js를 수행한후

http://localhost:8080 으로 접속하여 Hello World! 가 제대로 나오는지 확인을 한다.


3. 지원 모듈 확장

추가 언어 지원 모듈이나, 웹서버 같은 모듈을 인스톨 할려면 vertx install {모듈명} 을 사용해서 인스톨 하면 되는데, 이 경우 maven이나 외부 repository를 통해서 모듈 파일을 읽어와서 설치한다.

이때 회사 방화벽이나 Proxy 설정이 되어 있는 경우, 모듈 파일을 못 가지고 오는 경우가 있다. 이 경우에는 Local Maven directory나 사내에 Nexus와 같은 Maven repository를 설치하고 거기에 모듈을 저장한 후에 설치하도록 한다.

모듈 repository 설정은 conf/repo.txt에 하도록 되어 있다. maven과 bintray repository를 지원한다.

자세한 모듈 설치 방법은 http://vertx.io/mods_manual.html#installing-modules-manually-from-the-repository 를 참고하기 바란다.

Vertx 모듈 목록은 http://modulereg.vertx.io/ 를 참고하기 바란다.


* TCP Push 서버 구현 아키텍쳐

  다음은 간단하게, Vert.x를 이용하여 TCP/Push 서비스를 구현하는 아키텍쳐를 소개하고자 한다.

구현하고자 하는 시나리오는 TCP 클라이언트들이 접속되어 있을때, Vert.x 서버로 HTTP request를 보내면, 해당 클라이언트로 푸쉬 메세지를 보내주는 방식이다.


1. Option A. Vert.x 의 클러스터링 기능을 이용하는 방식

첫번째 구현 방식은 Vert.x의 클러스터링 자체를 이용하는 것이다.

클라이언트가 접속이 되면, 각 클라이언트 socket 마다. event bus handler를 binding해놓는다. client의 id가 되어도 되고, mac address 또는 ip/port 등도 사용할 수 있다.

그리고, 각 Vert.x Instance 마다 Push request를 받는 HTTP/JSON 인터페이스를 개발했다고 하자.

여러개의 Vert.x 인스턴스를 클러스터로 구성하여 배포 하고, Vert.x <--> 클라이언트 간에는 L4나 HAProxy등의 로드밸런서를 둬서 부하를 분산한다.

Push request를 받는 HTTP 단 앞에서 로드밸런서를 둔다.

Request는 client id와 push에 보낼 메세지를 보내주면 HTTP Request는 L4에 의해서 아무 Vert.x 서버에나 라우팅이 될 것이다. HTTP Verticle에서는 client id를 event bus address로 사용하여, event를 보내면, Vert.x 클러스터링에 의해서 해당 클라이언트가 연결된 Vert.x 인스턴스로 이벤트가 발송 되고, 이 이벤트를 받은 Event Bus 핸들러에서 Client로 push 메세지를 보내게 된다.

이 아키텍쳐의 장점은 Vert.x의 기능을 그대로 사용하기 때문에 구현이 간단하다는 것이다.

단 Event Bus에 치중한 아키텍쳐이기 때문에, Event Bus의 구현체인 Hazel Cast의 사용량이 많아지게 되고, GC를 유발하여, 순간적인 시스템 멈춤(full gc)를 유발할 수 있다.

시스템이 크지 않으면 큰 문제 없겠지만, 수백만 커넥센등을 동시 처리할 경우에는 HazelCast의 상용 버전에서 지원하는 Elastic Memory라는 기능을 고려해볼 필요하가 있다. 이 기능은 데이타를 Java Heap 영역에 쓰는 것이 아니라, Direct로 메모리에쓰고 관리하기 때문에, GC를 유발하지 않기 때문에, 이 아키텍쳐에서 오는 문제점을 예방할 수 있다.


2. Option B. - Custom router를 사용하는 방법

다른 옵션으로는 Vert.x의 클러스터링 기능을 사용하지 않는 방식이다.

클러스터를 사용하지 않는 방법이다. Event Bus 방식과는 달리, Push 요청은 반드시, Client와 TCP로 연결된 Vert.x 인스턴스들로 보내져야 한다. 이를 위해서는 앞단에서 Push Request를 받는 HTTP 서버 앞의 로드밸런서에서, Client가 어느 Vertx Instance에 접속되어 있는지를 알아야 한다. 이를 위해서 일종의 Custom Load Balancer를 Vert.x로 만들어서, TCP Client가 접속될때, 이 Customer Load balancer의 라우팅 테이블에 클라이언트가 어느 Instance로 접속되었는지를 저장해놓고, HTTP Push 요청이 들어 왔을 때, 이 라우팅 테이블을 기반으로 하여, 해당 Vert.x Instance로 요청을 보내는 방법이 있다.



* Vert.x에 대한 몇가지 잡담

  • 다들 gradle을 쓰더라.
  • StackOverflow보다는 google groups를 이용하자.
  • 옛날 Tuxedo와 구조가 유사함. HazelCast는 BBL. Verticle instance는 Tuxedo Service와 유사하다.
  • Single Thread 기반의 ELP를 사용하기 때문에, 코딩 잘못하면 까딱하다가 시스템을 보내버릴 수 도. Multithread 모델에서는 문제가 된 Thread만 stuck되고 다른 Thread들은 작업을 하겠지만, Single Thread에서는 한번 Stuck되면, 뒤의 작업들이 줄줄이 막혀 버린다.
  • 써보니 정말 편하더라. EventHandler만 구현하면 되기 때문에 코딩양도 적고, 컴파일 없이 바로 실행해볼 수 있기 때문에 개발 및 테스트가 편하다. (진입 장벽이 낮다.)
  • 메뉴얼 만드는 사람들은 지원하는 모든 프로그래밍 언어로 만들어야 하기 때문에, 참 힘들겠더라.
  • Tim Fox가 VMWare에서 다시 RedHat으로 옮겼다더라.

 

요 몇일간 Vert.x에 대해서 공부를 한것을 글로 정리하고 싶어서 포스팅 했는데, 생각보다 내용도 많아지고.. 어려워 졌다!!

그렇지만, Vert.x의 경우 상당히 유용하고, 비교적 Learning curve가 높지 않으니 고성능, 대용량 서버 개발이 필요하다면 반드시 살펴보기를 권장한다.