클라우드 컴퓨팅 & NoSQL/Riak

NoSQL Riak Overview #1/2

Terry Cho 2012. 2. 21. 18:21

Riak 계보

Riak은 이미들 잘 알고 있는 NoSQL 데이타 베이스이다. Basho.com이라는 회사에서 만들어서 배포하고 있고, 무료 버전인 Community version과 상용 기술 지원을 받을 수 있는 Enterprise version을 지원하고 있다.

NoSQL 계보는 크게 두 가지로 나눠지는데,  Google Big Table 논문을 기반으로 한 HBase,HyperTable 등과, Amazon Dynamo 논문을 기본으로 한 Cassandra등의 계열로 나뉘어 지며, Riak Dynamo 계열에 속한다.

데이타 모델에 있어서는 Key,Value 저장형식을 취하는데, Value JSON 문서가 저장되는 문서 저장형 데이타 베이스 형식을 취하며, 이는 MongoDB CouchDB와 유사한 데이타 모델을 갖는다.

이런 특성 때문인지, MongoDB로 부터 Migration되는 사용자가 많은 것으로 알려져 있다.

Riak의 기술적인 특징

Ring 구조 기반의 아키텍쳐

Riak은 앞에서 언급했듯이, Dynamo 계열의 구조를 가지기 때문에 데이타를 분산 저장 시키는 구조에 있어서 Ring 구조를 갖는다. Hash 알고리즘에 의해서, 데이타 Key에 따라서, 적정 노드를 찾아가는 구조로 되어 있다.


Riak의 클러스터링 단위는 크게 node vnode로 나눌 수 있는데, node는 물리적인 서버를 지칭하며, vnode는 논리적인 서버로 하나의 인스턴스 개념정도로 생각하면 된다. 하나의 물리서버에는 여러개의 논리서버 (vnode)를 설치할 수 있다.

Riak은 이 Ring 구조를 Runtime 시에도 동적으로 재 설정할 수 있다. (node를 추가하거나 뺄 수 있다. )이 과정에서 Riak이 자동적으로 데이타를 변형된 Ring 구조에 따라서 재배포 한다.

마스터 노드가 없는 아키텍쳐

Mongodb등은 데이타를 저장하는 노드와, Request를 라우팅하는 (mongos,mongod)로 구성된다. master-slave 개념이 있는 아키텍쳐인데 반해서, Riak은 별도의 마스터 노드를 가지지 않는다. 이 말은 Single Failure Point (단일 장애 지점)이 없다는 것을 의미한다. 각 노드로의 로드밸런싱은 앞단에 L4 스위치를 넣거나 Apache NginX와 같은 웹서버를 이용하여 소프트웨어 로드밸런서(Reverse Proxy)를 사용하여 구성할 수 있다.

※ 앞단의 로드밸런서는 Riak vnode의 장애상태를 판단할 수 없기 때문에, Riak에서 제공되는 Client SDK를 사용하면, 신속한 장애 감지가 가능하며, 이에 더하여 Connection Pooling등을 이용하여 성능 향상과 자원 사용의 효율성을 높일 수 있다.

데이타 복제 (Replication)

NoSQL 답게 내부적으로 데이타를 복제하는 구조를 가지고 있다. 몇개의 데이타를 복제하느냐를 “N-Value”라고 하는데, 일반적으로 디폴트 설정이 3개의 복사본을 가지는 것을 정책으로 한다.

일반적으로 근래에 나오는 분산 저장 시스템은 3개의 복사본을 가지는 것이 유행인데, 3개를 갖는 이유는 1개의 데이타 본에 대해서 작업을 진행하고 있을때, (확장이나 삭제) 장애가 났을때를 대비해서 2개의 노드가 Active상태인 것을 유지하기 위함이다.

당연히 노드간의 데이타 복제가 이루어 지는데, 이 데이타 복제는 실시간으로 이루어지는 것이 아니기 때문에 데이타의 consistency가 정확하게는 보장되지 않는다. 즉 데이타를 WRITE하고나서, 다른 노드에서 READ를 할 경우 복제가 이루어지기 전에 READ하면 예전 데이타가 READ될 수 있다는 의미이다. (millisecond 단위이기는 하지만)

CAP 이론

CAP 이론은 2002년 버클리 대학의 Eric Brewer 교수에 의해서 발표된 분산 컴퓨팅 이론으로, 분산 컴퓨팅 환경은 Consistency, Availability, Partitioning 3가지 특징을 가지고 있으며, 이중 두가지만 만족할 수 있다는 이론이다.


    Consistency는 분산된 노드중 어느 노드로 접근하더라도, 데이타 값이 같아야 한다는 기능적 특징이다. (데이타 복제 중에 Query가 되면, Consistency를 제공하지 않는 시스템의 경우 다른 데이타 값이 Query 될 수 있다)

    Availability는 클러스터링된 노드중, 하나 이상의 노드가 FAIL이 되더라도, 정상적으로 요청을 처리할 수 있는 기능을 제공하는 특징이다.

    Partition Tolerance는 클러스터링 노드간에 통신하는 네트워크가 장애가 나더라도 정상적으로 서비스를 수행할 수 있는 기능이다.

 

Riak은 이중에서 A P를 지원하는 방향으로 구현되어 있다..

Riak의 데이타 구조

Bucket

Bucket RDBMS database로 보면 된다. 일종의 컨테이너로 여러개의 keyspace (RDBMS로 치면 table)를 담고 있으며, Bucket 마다 Property로 특성을 정의할 수 있다. (복제되는 Replica의 수나, 실제 저장되는 물리 스토리지 타입등)

Data Structure

Riak은 다른 NoSQL과 유사하게 Key-Value 형태의 저장 구조를 갖는다. (Hash Table을 생각하면 쉽게 이해할 수 있을 듯). Key는 해당 Bucket에서 Unique해야 하며, Value JSON Document 또는 Binary 데이타를 저장할 수 있다. 특이 사항은 데이타 구조에 Header 부분을 포함하고 있는데, Header는 사용자가 정의하는 메타데이타나 또는 Riak에서 제공하는 Index 지정, Object와의 Link 관계등을 정의한다.

 

Riak Architecture

지금까지 Riak의 일반적인 특성에 대해서 알아보았다. 조금 더 깊게 들어가서 Riak의 아키텍쳐 및 몇몇 기술적인 특징에 대해서 살펴보도록 한다.


<그림 Riak Architecture Stack>

Storage BackEnd

Riak은 데이타를 저장할 때, 물리적인 저장소를 선택할 수 있는데, 다음과 같이 크게 4가지 저장소 타입을 지원한다.


BitCask

Bitcask Basho에서 만든 스토리지로, Erlang을 베이스로 한다.
Key Index
를 메모리에 올리기 때문에 빠른 성능을 제공하지만 반대로, 다른 스토리지 백엔드에 비해서 많은 메모리를 필요로 한다
.
Bitcask
append only 데이타베이스로, 데이타에 대한 삭제나 변경이 불가능하기 때문에, 데이타의 삭제 변경이 일어나면 새로운 버전을 저장하고 History를 관리하는 형태를 취한다. 이 때문에, 예전 버전을 주기적으로 삭제하는 Garbage Collection 작업을 수행한다.

InnoStore

Innostore Oracle InnoDB Wrapping한 스토리지로 대용량 저장에 강점이 있다고 한다. InnoDB, 이미 MySQL Backend로도 검증이 된 만큼 신뢰성도 높고, Oracle쪽으로 부터 Commercial Support도 받을 수 있으니, 안정성 측면에서 조금 더 높은 보장이 가능하지 않을까 하는 개인적인 생각이다.

InnoStore는 기본적으로 Riak에서 그리 추천하지는 않으나, BitCask의 경우는 메모리를 많이 필요로 하는 반면 InnoStore는 메모리를 많이 필요로 하지 않기 때문에 이런 시나리오에서 사용이 가능하다. InnoStore 설계시 주의점중의 하나는 InnoDB DB 파일과 Log 파일을 별도의 Disk에 나눠서 저장해야 제 성능을 발휘 할 수 있다.

LevelDB

LevelDB Google에서 얼마전에 발표한 DB, BigTable 이론에 근간을 두고 구현되었다.

마지막으로 Memory가 있는데, Memory는 현재 문서상으로 봤을때는 테스트등의 용도로 주로 사용이 되며, Redis Memcached와 같은 메모리 캐쉬 용도로도 사용이 가능하리라 판단된다. (주관적인 생각)

Riak에서는 용도와 목적에 따라서 이 4가지 Storage BackEnd를 하나의 Riak Cluster에서 혼합하여 사용할 수 있도록 한다.

문서상에는 4개의 Storage BackEnd에 대한 특징이나 선택 방법에 대해서 명확한 가이드를 주고 있지는 않으며, Default로는 BitCask를 추천하고 있으며, Secondary Index를 사용하고자 할때는 BitCask + LevelDB를 추천하고 있다.

Riak KV

Riak KV Key Value Storage, Riak의 메인 데이타 저장 구조를 담당하는 부분이다. Key : Value Pair로 데이타를 저장한다. 데이타를 Query하는 면에서 두 가지 방식을 지원하는데, Secondary Index MR (Map & Reduce)를 지원한다.

Map & Reduce

MR은 데이타 검색에 관련된 작업을 여러개의 vnode로 나눠서 작업을 한후 (Map), 결과를 합쳐서 리턴해주는 형태(Reduce)를 사용한다. MR을 사용할때, Map Function Reduce Function을 정의해서 request에 함께 보내는데, Map Reduce Function Erlang 또는 JavaScript 기반으로 작성이 가능하다.

(참고 : Riak MR - http://wiki.basho.com/MapReduce.html)

Map 예제) 데이타에서 “High” 필드가 600 이상인 값을 찾는 Map Function (JavaScript 버전)

Function(value, keyData, arg) {

  var data = Riak.mapValuesJson(value)[0];

  if(data.High && data.High > 600.00)

    return [value.key];

  else

    return [];

}

실제로 Riak에 호출할때는 REST+JSON을 이용하여 HTTP Request를 다음과 같이 보낸다.

{"inputs":"goog",

 "query":[{"map":{"language":"javascript",

                  "source":"function(value, keyData, arg) { var data = Riak.mapValuesJson(value)[0]; if(data.High && parseFloat(data.High) > 600.00) return [value.key]; else return [];}",

                  "keep":true}}]

}

 

Map & Reduce 예제) 다음 예제는 Map Reduce를 포함한 예제이다.
Map
을 이용하여, 매월별 매출의 편차(최고-최소:data.High-data.Low)를 계산하고, Reduce 단계에서 매월 매출 편차가 가장 큰 값을 찾는 예제이다.

/* Map function to compute the daily variance and key it by the month */

function(value, keyData, arg){

  var data = Riak.mapValuesJson(value)[0];

  var month = value.key.split('-').slice(0,2).join('-');

  var obj = {};

  obj[month] = data.High - data.Low;

  return [ obj ];

}

 

/* Reduce function to find the maximum variance per month */

function(values, arg){

  return [ values.reduce(function(acc, item){

             for(var month in item){

                 if(acc[month]) { acc[month] = (acc[month] < item[month]) ? item[month] : acc[month]; }

                 else { acc[month] = item[month]; }

             }

             return acc;

            })

         ];

}

 

Secondary Index

이번 릴리즈에서 Riak의 새로운 강력한 기능 중에 하나가 Secondary Index를 지원하는 기능이다. 쉽게 이야기 하면, RDB로치자면 Primary Key 뿐만 아니라, 테이블 상에 여러개의 Column Index를 걸어서 특정 Record Query할때 빠른 응답 시간을 보장한다. (Riak은 하나의 KeyStore에 여러개의 Index를 동시에 걸 수 있다.)

Riak에서 Index를 정의하는 방법은 REST PUT/POST 메서드를 이용해서 데이타를 입력하거나 수정할때, 헤더 부분에 “x-riak-index-{인덱스명}_{데이타타입}:{인덱스 데이트} 형식으로기술 한다. 다음은 user 라는 Key Value Store“twitter”“email”이라는 필드를 인덱스로 지정하는 예제이다.

curl -X POST \

-H 'x-riak-index-twitter_bin: rustyio' \

-H 'x-riak-index-email_bin: rusty@basho.com' \

-d '...user data...' \

http://localhost:8098/buckets/users/keys/rustyk

 

Index를 이용해서 데이타를 쿼리 하는 방법은 HTTP GET /{버킷명}/index/{인덱스명}/{인덱스값} 으로 서술하면 된다. 아래는 “users”라는 버킷에서 “twitter_bin” index에서 “rustyio”라는 값을 가지는 Object를 쿼리 해오는 예제이다.

localhost:8098/buckets/users/index/twitter_bin/rustyio

2012 2월 현재 Riak Secondary Index는 몇 가지 제약사항을 가지고 있다. 먼저 여러개의 Index를 동시에 사용하여 검색을 할 수 없으며, 검색 결과에 대한 Sorting이나 Paging은 지원되지 않는다. (Range Search는 가능-예를 들어 나이가 20~30세 사이를 갖는 Object에 대해서 Index를 가지고 검색 가능)

Index 정보는 파일 형태로 Object가 저장되는 동일한 vnode에 저장되며, Index 정보는 내부적으로 Google LevelDB storage backend에 저장된다. Key/Value의 경우 Ring Topology를 기본으로 Key값에 대해서 Hash 값을 기반으로 특징 vnode를 찾아가기 때문에, 그 특정 vnode request를 받는데 반해서, Index 기반의 Query의 경우, Index 값이 어느 vnode에 저장되었는지 찾을 수 가 없다. (앞서 설명하였듯이, Object에 대한 Index Object가 저장된 동일한 vnode에 저장이 되기 때문에, Index값을 기반으로 Hash 등의 검색이 불가능 하다. ) 이런 이유로 index Query를 수행하게 되면 전체 vnode Query가 분산 되서 수행되기 때문에, Index 기반의 Query는 전체 노드에 부담을 준다. (1/N vnode에서 수행됨)

Riak Search

Riak Search Value의 내용을 가지고 검색을 가능하게 해주는 Full Text Search (FTS) 기능이다. Riak Search Apache FTS Lucene (http://lucene.apache.org)을 기본으로 구현되었고, Riak Erlang으로 구현되어 있고 원래 Lucene Java로 구현되어 있기 때문에 Riak에 최적화하기 위해서 Lucene의 일부를 Erlang으로 재구현하였다.

 설명에 앞서서 앞에 아키텍쳐 다이어그램에서는 Riak Search Riak KV와 분리해서 표현하였지만, 물리적으로 Riak Search Riak KV의 일부이다. (KV를 설치하면 안에 Search가 들어가 있다.) 이는 Riak의 디자인 사상과도 결부되는데, Search를 별도의 모듈로 분리하지 않고 합침으로써 운영에서 많은 컴포넌트를 관리해야 하는 부담을 덜어준다.

먼저 Search에 대해서 이해하기 이해서 Search 가 어떻게 Search Index를 저장하는지를 이해할 필요가 있다. Riak Search Index 저장 방식은 “Term Partitioning” 이라는 기법을 사용한다. Key/Value안에 있는 Value Text Parsing Term 단위로 나눈다. 이때 Term 단위로 잘라내는 것을 Analyzer가 담당하는데, 단순하게 탭이나 스페이스 단위로 잘라서 Term을 추출할 수 도 있고, 아니면 검색 Dictionary 기반을 사용하는 등 여러가지 방법을 사용할 수 있으며, 사용자 요구 사항에 따라서 직접 Analyzer를 제작할 수 도 있다.
http://wiki.basho.com/Riak-Search---Indexing.html

Term이 추출이 되면 이 Term Index로 사용하는데, Term을 전체 Cluster에 걸쳐서 분산해서 저장한다. Key-Value 저장 방식과 거의 동일한 방식으로 나눠서 저장하며 N-Value에 따라서 복제본 역시 같은 형태로 저장한다.

이렇게 Term Partitioning을 사용함으로써, Query시에 Search Keyword가 들어오면 단일 단어의 경우 어느 vnode에 저장되는지 Hash값을 통해서 찾을 수 있기 때문에, 전체 노드에 걸쳐서 Search 연산이 일어나지 않는다. (Secondary Index와 비교되는 부분). wild card 문자를 사용하거나 AND,OR등의 기타 연산문을 사용할 경우 경우에 따라 1~1/N개의 vnode에 걸쳐서 Query가 분산되어 수행될 수 있다.

Map & Reduce, Secondary Index, Search 비교

 

MapReduce

Riak Search

Secondary Indexs

Query Types

Map Reduce Function을 이용하여 쿼리 구현

Lucene SOLR 쿼리를 사용하여, 텍스트 검색, wildcard 모델과 Boolean 검색(AND,OR) 사용

동일값 비교(Equality), 범위 기반의 쿼리(Range Query)지원

Index Locality

N/A

Search Index N개의 vnode에 분산되어 저장됨 Riak KV와 동일한 백앤드를 사용

Object가 저장된 동일한 vnode Index가 저장됨
Data Storage Backend
LevelDB를 사용함

Vnodes Queried

Depends on input

Map & Reduce 로직에 따라서 Query가 수행되는 vnode가 결정됨

1 per term queries; 1/N for trailing wild card

하나의 “Term”으로 검색할 경우, “Term Index”가 저장된 vNode에서 수행됨.

Wildcard 검색을 할 경우 전체(1/N) vNode에 걸쳐서 수행됨

1/N per request (aka coverage query)

전체(1/N) vnode에 걸쳐서 분산 수행됨

Supported Data Types

Map Reduce를 위해서 Erlang JavaScript 사용, JSON 사용

Integer, Date and Text

Binary and Integer

Suggested Use Cases

 

Value 내의 Full Text Search

Tag 기반 검색

Poor Use Cases

많은 수의 Object에서 복잡한 연산을 하는 케이스

Searching for common terms in documents

 

Limitation

 

 

Backend Storage LevelDB 를 사용해야함

동시에 여러개의 Index를 사용하는 Query는 불가능함

Anti-Entropy Fault Tolerance

 

 

 

Extraction

 

 

 

 

'클라우드 컴퓨팅 & NoSQL > Riak' 카테고리의 다른 글

Riak 장점 다시 정리  (2) 2012.03.12
Riak Performance  (0) 2012.03.12
Riak vs CouchDB  (0) 2012.03.12
Riak관련 스터디 메모  (0) 2012.02.21
Riak Quick Review  (0) 2011.12.19