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


Archive»


 
 

Dynamo는 새롭게 소개된 AWS의 NoSQL서비스이다.
Key-Value 형태로 대용량의 데이타를 저장할 수 있으며, 고속의 데이타 access를 제공한다.

데이타 모델
먼저 데이타 모델을 살펴보자, RDBMS의 일반적인 테이블 구조와 유사하지만, 조금 더 유연성을 가지고 있다.
RDBMS와 똑같이 테이블이라는 개념을 가지고 있으며, 테이블은 테이블명과 각각의 ROW로 구성된다.
테이블은 Unique한 Primary Key를 가지고 있다. 이를 Key라고 정의한다.
테이블의 ROW에 해당하는 내용은 item이라고 부르는데, 각 item은 key에 의해서 구분된다.
RDBMS와는 다르게, 각 ROW는 똑같은 Column을 갖는 것이 아니라, 각  row 마다 다른 column을 가질 수 있다
그래서, 각 컬럼을 name = value 식으로 정의하는데, 이 각각을 attribute라고 정의한다.



이 개념을 도식화 해보면 위의 그림과 같다.
아마존 웹사이트에 나와 있는 예제를 한번 살펴보자.
ProductCatalog 라는 테이블이 있다고 하자, 이 테이블의 Primary Key는 Id라는 필드를 사용한다. 이 Primary Key 필드는 모든 item들이 가지고 있어야 한다.

Item 1
{ 
   Id = 101                                       
   ProductName = "Book 101 Title"
   ISBN = "111-1111111111"
   Authors = [ "Author 1", "Author 2" ]
   Price = -2
   Dimensions = "8.5 x 11.0 x 0.5"
   PageCount = 500
   InPublication = 1
   ProductCategory = "Book" 
} 
Item 2
{ 
Id = 201
ProductName = "18-Bicycle 201"
Description = "201 description"
BicycleType = "Road"
Brand = "Brand-Company A"
Price = 100
Gender = "M"
Color = [ "Red", "Black" ]
ProductCategory = "Bike"
}

위의 예를 보면 알겠지만, 모든 Item들은 Id라는 Key 필드를 가지고 있지만, 각각의 Column들은 내용이 다르다. Item 1에는 ISBN 필드가 있지만, Item 2에는 ISBN 필드가 없다.

Dynamo는 RDBMS와는 다르게 Index 필드가 없다. (다른 NoSQL도 Index가 없는 경우가 많지만) 대신 range query나 sorting을 지원하기 위해서 range key라는 추가적인 키를 제공한다. Primary Key를 정의시 Unique한 Key 필드 하나만 정의하거나 (이를 Hash Key라고 한다.) 또는 이 range key를 추가로 지정하면, 쿼리 결과가 ascending 순서로 sorting이 되며, 쿼리 역시 이 range key를 기반으로 특정 범위의 데이타만 query 할 수 있다.
이 range key는 table 생성시에 hash key와 함께 정의한다.

성능

내부적으로 SSD 디스크를 이용하기 때문에, 높은 IO 성능을 보장할 수 있으며 read / write 성능을 보장하는 옵션을 가지고 있다.
Read/Write Unit이라는 옵션인데, 1KB item 1개를 1초동안 쓰거나 읽는 단위가 1 Unit이다. 2 Write Unit은 1K 데이타를 1초동안 2개 Write할 수 있는 성능 지표이다.

Units of Capacity required for reads = Number of item reads per second x item size (rounded up to the nearest KB)
Units of Capacity required for writes = Number of item writes per second x item size (rounded up to the nearest KB)
ReadUnit = [초당 읽는 item(row) 수] * [ item 크기 (kb로 반올림) ]
WriteUnit = [초당 쓰는 item(row) 수] * [ item 크기 (kb로 반올림) ]

1 ReadUnit은 초당 1KB짜리 1개의 Item을 읽을 수 있는 성능 단위이다.
예를 들어, 21.5 kb 짜리 item을 초당 100개를 읽는 성능이 필요하다면, Read Unit = 100 * 22 (반올림) = 2200 이 필요하다.
쓰기 성능도, 마찬가지 방식으로 WriteUnit이라는 단위로 지정한다.
각각 최대 10,000 Unit 까지 지원하며, 이 이상을 원할 경우, Amazon Support에 신청하면, 더 높은 Unit으로 올릴 수 있다. (최대 한계는 나와있지 않음)
Unit의 개념은 성능을 보장한다는 개념에서 긍정적이지만, 반대로 성능을 제약한다는 문제를 가지고 있다.
즉 정해진 Unit 보다 많은 read나 write가 발생할 경우, Dynamo는 이를 처리하지 않고 error 처리를 해버린다.
"ProvisionedThroughputExceededException" 그래서 Spike 형태의 request가 들어올 때는 문제가 된다.
프로그램 로직상에서 "ProvisionedThroughputExceededException" 에 대한 처리가 필요한데, 이 에러가 발생하였을 경우에는 프로그램적으로 retry를 하도록 하는 로직을 포함하는 것을 권장한다.

일관성 보장 옵션
Dynamo와 같은 NoSQL 계열의 데이타베이스는 데이타를 여러개의 노드에 나눠서 저장하고, 백업을 위해서 다른 노드로 복제하기 때문에, 복제가 완료되기 전에 클라이언트가 다른 노드에서 데이타를 읽으면 예전의 데이타를 읽을 수 있다. 이런 문제가 일관성 문제인데, 일반적인 NoSQL은 이러한 일관성을 보장하지 않는다. 복제할 때까지 시간이 걸린다는 것을 가정하고, (시간 자체는 짧으나) 그 시간동안은 일관성이 보장이 안되는데, 이러한 일관성 보장 정책을 Eventually Consistent Read라고 한다. 반대로, 데이타를 쓴 다음 모든 노드에서 데이타를 읽었을때, 같은 데이타를 바로 리턴하게 할 수 있는데, 이 경우에는 데이타가 써진 후에, 다른 노드 까지 replicated될때까지 기다려야 한다. 그래서 당연히 Read 응답시간이 Eventually Consistency Read보다 느리다. 이러한 일관성 정책을 Strongly Consistent Read라고 한다. Strong Consistency의 경우 Eventually Contentency와 같은 성능을 보장하려면, 더 빨리 data write를 발생시켜야 하기 때문에, 내부적으로 더 높은 write unit이 필요하게 된다. 그래서, Eventually Consistency의 경우 Strong Consistency에 비해서 가격이 50% 정도 저렴하다.

Query
데이타베이스이기 때문에, 데이타에 대한 Query를 지원한다.
Primary Key를 가지고 get이 가능할 뿐더러, range key를 이용하여, subset을 query하는 range query가 가능하다.
쿼리 후 list(set) 형태의 데이타가 리턴되었을 경우, 한번에 리턴할 수 있는 데이타 set의 최대 크기는 1MB이다.
1MB가 넘을 경우에는 pagenation을 해야 하는데, 1MB 가 넘는 경우 LastEveluatedKey라는 값을 리턴하여, (일종의 DB cursor와 같은 역할) 다음번 read시 부터는 이 키 부터 리드할 수 있도록 pointing을 해준다
또는 명시적으로 Limit 라는 parameter를 이용하여, Query에서 리턴되는 수를 정할 수 있다. (SQL의 "top"과 같은 개념) 전체 쿼리 결과가 1000개라도 Limit 10 으로 하면, 소팅된 순서에서 상위 10개의 item 만 리턴한다.
다음으로는 "Count"라는 parameter가 있는데, 이 Count는 RDBMS의 select count(*)와 같은 개념이다. Query 결과로 리턴되는 총 Item의 수를 리턴한다.
주의할점은 Dynamo는 NoSQL이다. RDBMS와 다르다.
key 기반의 select와, range query는 지원하지만, group by, where, index등의 쿼리 기능은 없다. (데이타 모델 설계 자체를 RDBMS와 다르게 해야 한다.)

Scan
Scan 기능은 테이블의 모든 Item을 순차적으로 읽어오는 기능으로, Query와 마찬가지로, 한번 API call에 1MB까지만 읽어올 수 있고, LastEvaluatedKey 값을 이용해서 다음 데이타를 연속적으로 읽어올 수 있다. 처음부터 테이블을 Scan 하기 때문에, 당연히 많은 시간이 소요된다.

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

Amazon의 설치 배포 자동화 솔루션 Opsworks  (0) 2013.02.26
간단한 S3 Performance Test  (3) 2013.01.25
아마존의 SSD의 NoSQL 서비스 Dynamo  (0) 2012.12.07
Amazon S3 서비스 소개  (0) 2012.12.06
EMR 특징  (1) 2012.12.06
Dynamo 특징  (0) 2012.12.06

Cassandra Node CRUD Architecture

이번 글에서는 Cassandra 클러스터를 구성하는 각 노드에서 Local Read/Write가 어떤 원리로 이루어지는 지 설명한다.
Cassanda에 대한 기반 지식은 아래 예전 포스팅을 참고하기 바란다.


Insert Record

큰 흐름의 Write 시나리오는 다음과 같다.
  1. Cluster에 Write 요청을 받으면, Insert하고자 하는 Record의 Key 값에 따라 Cassandra의 어느 Node에 데이타를 저장할 지, Hash 값을 가지고 판단하여, 데이타를 저장할 Node를 찾는다.
  2. 해당 Node에 데이타를 저장한다.
  3. 저장된 데이타를 Replication 정책에 따라 다른 Node에 복제 한다.
여기서 설명할 Write 로직은 2번의 한 Node에 데이타를 내부적으로 어떻게 저장하는 가에 대해서 설명한다.
  1. Node로 Write Request가 들어오게 되면, 먼저 Local의 Commit Log에 Write Request를 기록한다. 이는 서버가 갑자기 죽어버리는 경우 데이타 유실을 막기 위해서, Write Request 전체를 기록해놓고, 서버 장애시 다시 Restart되었을 때, 데이타 저장소에 저장되지 않은 데이타를 이 Commit Log로 부터 읽어서 복구하기 위함으로, Oracle과 같은 RDBMS나 다른 NoSQL도 비슷한 구조를 사용하고 있다.
  2. Insert가 요청된 데이타는 DISK에 바로 기록되지 않고 메모리 내의 MemTable이라는 곳에 기록이 된다. (뒤에 설명하겠지만, READ시에는 이 MemTable과 Disk 양쪽을 뒤져서 데이타를 READ한다. )
  3. MemTable이 어느정도 꽉 차면, 이 MemTable 전체를 통채로 Disk에 Write하는데, 이 과정을 Flushing이라고 한다. 이때 Disk로 Write되는 파일을 SSTable (Sorted String Table)이라고 하는데, 이 파일은 한번 저장되면 절대 삭제나 변경이 불가능하다. 
  4. MemTable을 SSTable로 쓰고 나면 CommitLog를 비워준다.
이렇게 Write는 Memtable 내용을 통째로 dump 하는 방식으로 이루어 지며, 절대 수정되지 않는다. 내부 구현상에서도 file sequential write로 구현되기 때문에 disk seek time이 없어서 매우 빠른 write 성능 실현이 가능하다. (random access를 하면 매번 위치를 찾기 위해서 disk seek 과정을 거쳐야 하기 때문에 성능 저하가 발생한다.)

Select Record


데이타를 읽는 큰 흐름은 다음과 같다.
1. 클러스터로 들어온 Read 요청의 Key값을 이용하여 Hash를 생성하고, 이 Hash 값을 기반으로 클러스터 링(Ring)내에 데이타가 저장된 Node를 찾아낸다.
2. 데이타를 해당 노드로 부터 읽어온다.
3. 복제된 다른 노드로 부터도 데이타를 읽어 온후에, 이 값을 비교하여 리턴한다. 이 부분에 대한 자세한 설명은 Cassandra Consistency와 Quorum에 대한 개념을 읽어보도록 한다.

여기서는 각 노드에서 READ가 어떻게 실행되는지 위의 2번 과정에 대해서 설명한다.

  1. 노드에서 READ 요청을 받으면 먼저 MemTable 내에 데이타가 있는지 찾아보고, 있으면 그 데이타를 리턴하고 끝낸다.
  2. 만약 MemTable에 데이타가 없다면, 디스크를 검색해야 하는데, 디스크의 SSTable이 실제 데이타가 저장되어 있는 곳이다. 
    SSTable에 저장되는 정보는 용도에 따라서 크게 3가지로 나뉘어 진다.
    1) Bloom Filter File - Bloom Filter는 통계적 로직을 이용하여, 해당 Key의 데이타가 SSTable에 저장되어 있는지 없는지만 판단하여 리턴한다. 각 SSTable의 Bloom Filter의 데이타는 메모리에 로딩이 되어 있는데, SSTable을 접근하여 Disk IO를 발생시키기전에 먼저 해당 SSTable에 데이타가 있는지 없는지를 먼저 검사하는 것이다.
    2) Index File - SSTable에 데이타가 있는 것으로 판단이 되면, Index File을 검색한다. Index 파일은 해당 Key에 해당하는 데이타가 Data File의 어느 위치에 있는 지에 대한 포인팅 정보를 가지고 있다. 이 Index File에서 Data File상의 레코드의 위치(Offset 정보)를 얻는다.
    3) Data File - 실제로 데이타가 저장되는 파일로 Index File에 의해서 얻은 Offset 정보를 가지고 레코드를 찾아서 리턴한다.
※ 근래 버전에는 SSTable에 Secondary Index를 지원하기 위한 Bitmap Index 파일등 기타 파일들이 추가되어 있다.

여기서는 언급하지 않았지만, 실제고 Read Operation의 성능 향상을 위해서 Index와 Data Record는 메모리에 캐슁 된다.

Record Update/Delete


앞에서 Insert & Select 구조에 대해서 알아보았다. 그렇다면 나머지 Update & Delete 는 어떤 방식으로 수행 될까?
먼저 Update의 경우에는 Delete & Insert 방식으로 내부 구현되어 있다.
Delete의 경우, 앞에서도 잠깐 언급했듯이, 한번 Write된 SSTable은 그 내용을 변경할 수 없다. (immutable) 그래서 tombstom 이라는 marking 방식을 이용하는데, 해당 record를 insert하고, tombstob 마크 (이 레코드는 삭제 되었다)라고 마킹을 한다.


위의 그림과 같이 각 레코드는 Deleted Mark와 Time Stamp를 가지고 있는데, 삭제된 레코드는 이 Delete Mark를 "True"로 표시해서 Insert하게 된다.

그러면 여기서 새로운 의문점이 생기는데, Delete란 기존의 데이타를 지우는 것이고 SSTable은 immutable (변경 불가)라고 했으니 기존의 데이타가 해당 SSTable이나 다른 SSTable에 남아 있지 않은가?

이에 대한 처리를 하기 위해서 Timestamp가 존재하는 것인데, 여러 SSTable에 걸쳐서 동일 데이타가 존재할 경우 이 Timestamp를 이용하여 최신의 데이타를 사용하게 되고, 최신의 데이타의 [Deleted Mark]가 True로 되어 있으면 데이타가 삭제 된것으로 간주한다.

Compaction
이렇게 Delete시에도 실제로 파일에서 데이타를 지우지 않고 계속 Insert만 한다면 어떻게 될까? 실제 삭제된 데이타가 계속 Disk에 남아 있기 때문에 디스크 용량이 낭비될 수 밖에 없다. 언젠가는 실제로 데이타를 지워야 하는데, Cassandra는 이를 Compaction이라는 작업을 통해서 해결한다.
SSTable내에는 유효한 데이타 뿐만 아니라 실제로 삭제된 데이타가 존재한다. 이런 공간을 없애야 하는데, 
두 개의 SSTable을 병합하면서 삭제된 레코드는 빼고 새로운 SSTable을 만든다. 새로운 SSTable에는 삭제된 레코드가 존재하지 않는다.
SSTable은 Sorting이 된 상태이기 때문에 병합 역시 매우 빠르게 이루어진다. 

결론
간략하게 각 Node의 CRUD 메커니즘에 대해서 알아보았다.
이를 소개하는 이유는 Cassandra를 사용할 때 내부 메커니즘을 이해함으로써, 어떤 형태의 데이타 설계나 API 사용이 올바른지에 대한 이해를 돕고 제대로된 Cassadra의 사용을 돕기 위함이다.
Cassandra는 기본적으로 빠른 write에 최적화가 되어 있고, delete를 tombstorm 방식을 이용하기 때문에, 이 tombstorm이 다른 노드에 복제 되기 전까지는 데이타의 불일치성이 발생한다.
또한 Key/Value 저장 방식에 최적화 되어 있기 때문에, 설사 Index를 사용한다 하더라도 Range Query나 Sorting등에는 그다지 적절하지 않으며 굉장히 빠른 Write 성능과 Commit Log 기반의 장애시 데이타 복구 능력을 보장함을 알 수 있다.

참고 자료



이 구성은 Cassandra나 Riak과 같은 Dynamo 계열에 공통 적용 가능하다. 다른 것들도 마찬 가지일테지만.

1. RAID 5 사용 : NoSQL 클러스터는 Quorum 사용을 통해서 노드에 (서버) 대한 FAIL을 방지 하지만 디스크 장애 자체에 대해서는 보장이 불가능하다. 고로 비용 대비 적정한 RAID 5 사용이 권장
2. IO Scheduler : NOOP 사용. NOOP은 IO Scheduling을 다른 계층이 한다는 것을 전제 한다. 즉 중간에 RAID 구성이나 iSCSI 를 사용하는 경우를 전제한다. 테스트용이나 개발용으로 사용하면서 RAID 구성등을 하지 않는다면, NOOP을 사용할 필요가 없다.
3. ext4 또는 XFS 파일 시스템 사용 : ext3는 1 volume의 max 사이즈가 2 TB를 넘지 못한다. ext4의 경우 16TB, XFS는 무제한(64bit 경우)
4. Log File과 Data File 디렉토리 분리 : Cassandra는 Transaction Log와 Data Log 파일이 분리되어 있다.물리디스크도 분리해서 Disk IO를 최적화 해야 한다.
 

=== 2012.04.12 첨언 ===

RAID 구성은 많은 수의 노드를 가지는 경우, 디스크 FAIL을 노드 FAIL로 간주하고, 전체 클러스터의 성능을 높이기 위해서 RAID5를 사용하지 않고 Stripping으로 구성하는 것이 경제성이나 (RAID 5 parity 디스크 사용 안함), 성능 (Stripping이기 때문에, 전체 IO 성능 향상) 관점에서 유리하다.


짧으나마 NoSQL 경험해보고 배운 내용을 정리해보면

1. RDB는 Entity를 정의하고 데이타 모델링을 정의한 후에, 쿼리와 APP을 개발한다. 반대로 NoSQL은 App을 먼저 디자인하고, 필요한 쿼리 결과를 먼저 정의 한후에, 그에 맞춰서 데이타 모델링을 해야 한다.

2. 절대 Normalization은 하지 말고, DeNormalization을 할것. 데이타 중복을 허용하여 성능을 높이고, 데이타안에 데이타를 넣는 (Composition) 모델등을 사용하여 Query 수를 줄여야 한다.

3. 내 애플리케이션의 서비스 특성과 이에 맞는 NoSQL을 선택한다. BigTable 계열, Cassandra 계열, Document DB 계열등 많은 계열의 NoSQL이 있고, 그 특성도 매우 다르다 (언뜻 보면 다 같아 보이지만). 서비스를 이해하고, 사용하고자 하는 NoSQL을 완전히 이해한 다음에 시작해야 실수를 막을 수 있다.

4. NoSQL 쿼리가 실제 몇개의 물리 노드에 걸쳐서 수행되는지에 대한 이해가 있어야 제대로된 쿼리 디자인이 가능하다.

5. NoSQL 디자인은 DB 와 APP 뿐만 아니라 인프라 (네트워크,디스크)에 대한 디자인을 함께 해야 한다.

6. 대부분의 NoSQL DB는 인증이나 인가 체계가 없어서 보안에 매우 취약하기 때문에 별도의 보안 체계를 마련해야 한다. (방화벽이나 Reverse Proxy 등)

분산 환경 기반의  NoSQL은 예전 포스팅에서도 설명했듯이 크게 Google의 BigTable 논문을 기반으로한 시스템과, Amazon의 Dynamo를 기반으로 한 시스템 두가지로 나뉘어 진다.
Dynamo 계열의 NoSQL의 장단점을 간단히 정리해보면

Dynamo 계열 NoSQL의 개요

1. Ring과 Consistent Hasing
먼저 Dynamo 계열 (Cassandra, Riak) 의 NoSQL의 특징은 Ring 토폴로지를 기본으로 하고 있다. Ring 구성이란, 전체 데이타를 1~N (2^160과 같이 큰 범위로) 이라는 특정 레인지로 정의한후 전체 데이타 저장 구조를 Ring 형으로 정의한 후에, 이 Ring을 피자 조각을 나누듯이 여러 Slice로 나눈다. 이를 Partition이라고 하는데, 각 Partition은 데이타를 저장하는 구간 정보를 가지고 있다. 예를 들어 전체 Ring이 1000개의 데이타를 저장한다고 하고, 각 Partition의 수를 10개로 지정하면 첫번째 파티션은 0~999, 두번째는 1000~1999 까지의 키를 가지는 데이타를 저장한다. 이르 통해서 저장하고자 하는 데이타의 키를 알면 어느 파티션에 저장할 수 있는지 쉽게 찾아갈 수 있기 때문에, 각 Partition을 저장하는 하드웨어(Node)에 부하를 분산 시킬 수 있는 구조를 갖는다. 이런 방식을 Ring 기반의 Consistent Hashing이라고 이야기 한다.

2. N-Value & Quorum
이 경우 특정 파티션을 저장하고 있는 Node가 장애가 났을때, 특성 파티션의 데이타 유실이 발생할 수 있는데, 이를 방지하기 위해서 Node간의 데이타 복제를 수행한다. 몇 개의 복제본을 갖느냐를 정해야 하는데, 이 복제본의 수를 보통 "N-Value" 또는 "Quorum" 이라고 정의하며, 이 Quorum의 수는 일반적으로 3개 정도로 지정한다.
이 N-Value를 3으로 지정하는 이유는 여러가지가 있겠지만, 장애 대응면에서 최소한 하나의 복제본을 가져야하기 때문에 2개의 복제본이 필요하고, 예측된 작업(패치,서버 교체)시에도 장애를 대응하기 위해서 최소한 두개의 복제본을 유지해야 하기 때문에 일반적으로 3개의 복제본을 생성한다. (이 N-Value는 NoSQL 설정에서 조정할 수 있다.)

3. R-Value, W-Value
앞에 설명한 것 처럼, N-Value의 복제본을 가지게 되는데, Dynamo Architecture는 R-Value와 W-Value라는 특성을 유지 한다. 이 값은 "성능과, 데이타 정합성간의 Trade-Off"를 위한 값인데, 데이타 복제는 실시간으로 이루어지지 않는다. 약간의 Delay가 발생한다 (수 밀리세컨드, 데이타 센터간에는 조금더 길 수 있다.)
N-Value를 3이라고 가정하자. 첫번째 Node에 Write를 한후에,  두번째 Node와 세번째 Node에 데이타가 복제 되어야 한다. 이 복제과정에서 데이타를 읽을때, 이 3 노드 중에서 데이타를 몇개의 노드에서 데이타를 읽어올지를 결정하는 것이 R-Value이며. 동시에 몇개의 Node에 Write할것인가를 결정하는 것이 W Value이다.
만약에 W-Value가 2이면 Write시에 동시에 두개의 Node에 Write한다. R-Value가 1보다 클 경우 R-Value 노드 에서 데이타를 읽어오고, 두 개의 데이타가 다를 경우 최근 데이타를 사용한다.

이런 이유로 R-Value + W-Value > N-Value이면 Data Consistency가 보장된다.
예를 들어 N=3일때, R=2,W=2이면, Write시 적어도 두개의 복제본에 썼기 때문에, 하나가 복제가 안되어 있다하더라도, R-Value가 2이기 때문에, 꼭 하나는 새로운 데이타가 읽어지게 되고, 새로운 데이타를 Winning하는 정책 때문에 항상 최신의 데이타를 읽을 수 있다. (여기서 새로운 데이타를 판단 가능하게 하는 방법을 Vector-Clock이라고 한다. 이는 나중에 따로 포스팅 예정)

참고 : http://wiki.apache.org/cassandra/ArchitectureOverview 

Consistency

See also the API documentation.

Consistency describes how and whether a system is left in a consistent state after an operation. In distributed data systems like Cassandra, this usually means that once a writer has written, all readers will see that write.

On the contrary to the strong consistency used in most relational databases (ACID for Atomicity Consistency Isolation Durability) Cassandra is at the other end of the spectrum (BASE for Basically Available Soft-state Eventual consistency). Cassandra weak consistency comes in the form of eventual consistency which means the database eventually reaches a consistent state. As the data is replicated, the latest version of something is sitting on some node in the cluster, but older versions are still out there on other nodes, but eventually all nodes will see the latest version.

More specifically: R=read replica count W=write replica count N=replication factor Q=QUORUM (Q = N / 2 + 1)

  • If W + R > N, you will have consistency

  • W=1, R=N
  • W=N, R=1
  • W=Q, R=Q where Q = N / 2 + 1

Cassandra provides consistency when R + W > N (read replica count + write replica count > replication factor).

You get consistency if R + W > N, where R is the number of records to read, W is the number of records to write, and N is the replication factor. A ConsistencyLevel of ONE means R or W is 1. A ConsistencyLevel of QUORUM means R or W is ceiling((N+1)/2). A ConsistencyLevel of ALL means R or W is N. So if you want to write with a ConsistencyLevel of ONE and then get the same data when you read, you need to read with ConsistencyLevel ALL.



4. Masterless Architecture
또다른 특징 중에 하나는 Masterless 아키텍쳐이다. Ring을 구성하고 있는 아무 Node에나 요청을 보내도 처리가 되고, 전체 설정 정보를 가지고 있는 마스터 노드나 Admin 노드가 없다.
10개의 노드를 가지고 있는 Ring의 아무 Node에나 Request를 하더라도, 각 Node는 해당 데이타가 다른 어느 Node에 저장되어야 하는지를 Consistent Hash를 통해서 알 수 가 있고, 해당 노드로 Request를 Routing한다. 이때 자기가 데이타를 가지고 있지 않더라도 첫번째 요청을 받은 Node가 데이타를 처리하는 이 노드를 Coordinator Node라고 정의한다.

지금까지 대략적인 Dynamo 아키텍쳐의 특성에 대해서 알아보았다. 그러면 어떤 장단점이 있을까?

장점
1. High Availibility & Partition Tolerence
위와 같은 특성 때문에, 분산 시스템의 CAP 이론에서 A와 P에 최적화 되어 있다. 특정 노드가 장애가 나더라도 서비스가 가능하며 (A-Availibility) Node간의 네트워크 통신이 끊어지더라도 서비스가 가능하다 (P-Partition Tolerance : Vector Clock을 이용하여 데이타의 정합성을 처리가 가능하고 각 노드가 독립적으로 서비스가 가능

2. No Sigle Failure Point. No Master Node
그리고 Masterless 아키텍쳐로 인해서 Single Failure Point (SFP)가 없다. 이는 대규모 분산 환경에서 아주 큰 장점 중의 하나인데, 무제한 확정된 클러스터라도, 특정 노드 장애에 대해 종속이 되어 버리면 시스템의 안정성에 많은 영향을 미친다.

단점
반대로 단점은
1. Cannot Change Ring Size
일반적인 Dynamo 기반의 아키텍쳐는 Ring Size (Partition 수)를 변경할 수 없다. 데이타가 이미 Partition 별로 분산 저장되어 있기 때문에 파티션의 개수를 변경하면 다시 데이타를 변경된 파티션 수 에 따라 재 분배해야 한다. 이는 데이타의 이동을 초래하고, 많은 IO 부하를 유발하기 때문에 운영환경에서는 거의 불가능하다고 봐야 한다.

2. Data InConsistency
앞에서 설명한 바와 같이 데이타 복제가 실시간이 아니기 때문에 데이타에 대한 불일치가 발생한다. 물론 R,W Value를 조정해서 Consistency를 보장받는 방안은 있지만, 이 값을 높일 경우 동시에 여러 노드에 Read 또는 Write를 해야 하기 때문에 성능저하가 발생할 수 있고, 또한 Node간에 네트워크가 단절되는 Partitioning이 발생했을 때도 서비스는 되기 때문에, 다시 장애가 극복되었을때는 당연히 Data InConsistency가 발생하게 된다.

3. Sibling (Data Conflict 발생)
특히 네트워크 Partitioning이 발생하거나 또는 동시에 두개의 Client가 Write를 했을때, Vector-Clock 값이 똑같아서 어느 데이타가 더 최근 데이타인지 판단할 수 없는 Data Conflict (Sibling현상)이 발생한다. (Sibling의 자세한 개념은 나중에 Vector Clock 설명에 같이 추가)

NoSQL Riak Overview #1/2

클라우드 컴퓨팅 & NoSQL/Riak | 2012.02.21 18:21 | Posted by 조대협

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
NoSQL Riak Overview #1/2  (0) 2012.02.21
Riak관련 스터디 메모  (0) 2012.02.21
Riak Quick Review  (0) 2011.12.19

Riak관련 스터디 메모

클라우드 컴퓨팅 & NoSQL/Riak | 2012.02.21 12:09 | Posted by 조대협

Vnode
- vnode - process (One Erlang process per partition in the ring)
  partition - data
- Vnode는 MR 처리를 하는 Worker가 따로 있음
- VNode Master
 : Receieve msg from request corrdinator
- FSM (Finate State Machine)
  1) Type 1 : Preference list-based : single key
  2) Coverate based : entire keyspace


W+R > N : Consistency 를 보장할 수 있다.
(W : Write Replica 수)
(R : Read Replica 수)

Java SDK 에 Load Balancing  Logic 이 들어있다.


Vector Clock
- Every node has an ID (changed in ID)
- Send last-screen vector clock in every "put", "delete" request
- Riak tracks updates
 1) auto-reslves stale version
 2) Lets you decide conficts

※ Vector Clock 이 동일한 값이 나오는 경우가 Sbling 이 발생하고, 사용자가 풀어서 결정해야 한다.
- Conflict는 Network partition이 발생했을 때, 발생할 가능성이 높음
- vClock 을 update할 때, 인위적으로 같은 vClock을 동시에 넣을때 발생함
  (vClock을 GET하고, 다시 PUT할때)

Search

==============
Storage Backend
1. Bitcask
- append only KV store
- All key is stored in memory
- copaction을 통해서 garbage collection. (Stop 하지 않고 async로 발생, 언제 발생할지도 설정할 수 있다)
- 1 compaction process가 추가로 필요하다.

2. Innostore
-  Write Ahead log + B tree
- 순차대로 데이타가 insert될때, 유리 하다.
-  메모리에 Key를 안 올린다. 그래서 Bitcask에 비해 메모리 사용률이 적다.

3. LevelDB
- Append Only
- Mutiple leve of SSTable-like structure
- Secondary Index를 지원함. (BitCask는 지원 안함)
- Performance 가 Bitcask에 비해서 more variable
=======


/etc/riak/vm.config --> Erlang VM Configuration
/etc/riak/app.config --> Riak Config

====
FileSystem : ext3/4, ZFS 파일 시스템을 사용할것
Innostore 사용시 log 데이타 disk를 분리할것


===

Riak MR --> Memory를 사용하고, Disk를 사용 안함 (smallish scale)
 (무거운 MR은 Riak Hadoop Connector를 사용해서 Hadoop에서 실행)

>Pre-reduce : MAP 단계에서 Final Reduce가 발생하기 전에, pre-reduce수행
>reduce의 수행 횟수를 조정할 수 있음 : (Paging에 활용 가능) 1000개 합치는거가 100개씩 10번 수행되면 1번씩 수행해서 10개 페이지로 나눌 수 있음 (reduce_phase_only_1 옵션)
>reduce_phase_batch_size


- Riak의 하나의 Client 처럼 동작함

===

Secondary Index
--> 1/N node에서 수행되는게 문제가 아니라, 하나의 Server로 network traffice이 모이고, 나가기 때문에, Network IO (bandwidth)가 더 문제가 된다.
--> TCP retry time out 시간을 50ms 로 줄이는게 났다.
--> 결과는 Sorting되서 오지 않는다. (왜냐하면, 여러 vNode에서 오기 때문에)

=====
Riak Search
뒷단에 Index 저장은 별도의 데이타 구조체를 사용한다. (Cassandra와 유사함)

====
모니터링


=====

☆ Ring Size는 Cluster 초기 설정시에만 정할 수 있고 나중에는 바꿀 수 없다.
--> 6개월후에는 업그레이드 될것, (Dynamo 아키텍쳐의 약점)
--> Manual로는 가능

Erlang은 Each Process가 heap을 별도 가지고 있다.
Java 처럼 Heap을 share하지 않기 때문에 GC 문제도 안나고
모니터링도 Physical memory만 하면 된다.

=====
덤프 툴 같은 것도 있고
Erlang Shell로 붙어서 모니터링도 가능

----------------


데이타 모델링 관점
서버 셋업,운영 (장애 대처 및 튜닝)


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

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

NoSQL 계보 정리

클라우드 컴퓨팅 & NoSQL/NoSQL 일반 | 2011.11.14 17:45 | Posted by 조대협

Google의 BigTable에서 시작된 것들
- HBase (Java)
- HyperTable (C++)
주로 대규모 분산처리 특히 Map&Reduce에 알맞고, 동시 대규모 클라이언트를 지원하는데 뛰어 나다

Amazon Dynamo 로 부터 시작된 것들
- Voldemork
- Riak

FaceBook에서 시작된것
- Cassandra
Write에 Optimize되었으며, Read는 Write에 비해 느림. 대규모 데이타 저장에 최적화됨

그밖에 Mongo 계열
-MongoDB 쉽다. 그리고 AutoSharding과 Balacing 제공. 10gen에서 Commercial Support
-CouchDB : MongoDB와 특성은 유사하나 내부 기술 구조는 다름

Introduction of Cassandra

카산드라는 구글의 BigTable 컬럼 기반의 데이타 모델과 FaceBook에서 만든 Dynamo의 분산 모델을 기반으로 하여 제작되어 Facebook에 의해 2008년에 아파치 오픈소스로 공개된 분산 데이타 베이스 입니다. 기존의 관계형 데이타 베이스와 다르게 SQL을 사용하지 않는 NoSQL의 제품중의 하나이며, 대용량의 데이타 트렌젝션에 대해서 고성능 처리가 가능한 시스템이다.(High-Scale). 노드를 추가함으로써 성능을 낮추지 않고 횡적으로 용량을 확장할 수 있다.

 얼마전에 트위터도 MySQL에서 Cassandra로 데이타베이스를 전환하였다고 한다..

자바로 작성되었음에도 불구하고, 데이타베이스라는 명칭에 걸맞게 여러 프로그래밍 언어를 지원합니다. Ruby,Perl,Python,Scala,Java,PHP,C# 

데이타간의 복잡한 관계 정의(Foreign Key)등이 필요없고, 대용량과 고성능 트렌젝션을 요구하는 SNS (Social Networking Service)에 많이 사용되고 있습니다. 성능이나 확장성과 안정성이 뛰어나지만 안타깝게도 Global Scale (여러 국가에 데이타 센터를 분리 배치하여 배포하고, 데이타 센타간 데이타를 동기화 하는 요구사항) 은 지원하지 않습니다. Global Scale이 필요하다면, MySQL기반의 geo replication Sharding이 아직까지는 가장 널리 쓰이는 아키텍쳐 같습니다

Data Model

카산드라의 데이타 모델은 다음과 같다.

전통적인 관계형 데이타 베이스와 다른 구조를 가지고 있다.먼저 데이타 모델에 대한 개념을 잡아보면

Column
컬럼은 컬럼 이름과, 값으로 이루어진 데이타 구조체이다.

{name: “emailAddress”, value:”cassandra@apache.org”}
{name:”age” , value:”20”}

Column Family

컬럼 패밀리는 컬럼들의 집합이다. 관계형 데이타 베이스의 테이블을 생각하면 되는데, 약간 그 개념이 다르다. 차이점은 나중에 설명하기로 하고, 컬럼 패밀리는 하나의 ROW를 식별하기 위한 Key를 갖는다. 하나의 Key에 여러개의 컬럼이 달려 있는 형태가 컬럼 패밀리이다.

하나의 Row를 예를 들어보면

Cassandra = { emailAddress:”casandra@apache.org” , age:”20”}

과 같은 형태이다. Cassandra가 해당 Row에 대한 Key가 되고, emailAddress age라는 이름의 두개의 컬럼을 가지고 있으며 각 컬럼의 값은 “casandra@apache.org” “20”이다.

여러개의 Row를 가지고 UserProfile이라는 이름의 컬럼 패밀리를 보면

UserProfile={
  Cassandra={ emailAddress:”casandra@apache.org” , age:”20”}
  TerryCho= { emailAddress:”terry.cho@apache.org” , gender:”male”}
  Cath= { emailAddress:”cath@apache.org” , age:”20”,gender:”female”,address:”Seoul”}
}

과 같이 표현할 수 있다. 여기서 주목할만한 점이 각 Row의 데이타 스키마가 다르다는 것이다. Cassandra Row emaillAddress age라는 컬럼을 가지고 있고, Terry.Cho emaillAddress gender라는 컬럼을 가지고 있다. 이 처럼 카산드라는 각 Row마다 다른 형태의 데이타 스키마를 가질 수 있는데, 이러한 특징은 “Schemeless”라고 한다.(키에 바인딩되는 데이타 구조는 같은 컬럼 패밀리라도 각 키별로 다를 수 있다.)

KeySpace

KeySpace는 논리적으로 ColumnFamily를 묶어주는 개념입니다. 단지 묶어만 줄뿐 데이타 구조나 관계에서는 별다른 영향을 주지 않습니다.

Super Column & Supper Column Family

앞에서 설명드렸던 컬럼에서 컬럼의 Value String이나 Integer와 같은 Primitive형 뿐만 아니라 컬럼 자체가 다시 들어갈 수 있습니다. 예를 들어 이런 구조입니다.

{name:”username” 
 value: firstname{name:”firstname”,value=”Terry”} 
 value: lastname{name:”lastname”,value=”Cho”} 
}

username이라는 컬럼 안에 firstname lastname이라는 두개의 컬럼이 들어가 있는 구조입니다.

마찬가지 형태로 Column Family 안에도 Column Family가 들어가는 Super 구조가 가능합니다.

UserList={ 
   Cath:{ 
       username:{firstname:”Cath”,lastname:”Yoon”}
       address:{city:”Seoul”,postcode:”1234”}
           }
    Terry:{ 
       username:{firstname:”Terry”,lastname:”Cho”}
       account:{bank:”hana”,accounted:”1234”} 
           }
 }

UserList라는 Column Family 안에, 각각 Cath Key username address라는 Column Family를 가지고 있고, Terry라는 Key username account라는 Column Family를 가지고 있습니다.  

Data Model for Java Developer

간단하게 카산드라의 데이타 구조에 대해서 살펴보았는데, 자바 개발자분이시라면 HashTable이 떠오를겁니다. 데이타 모델을 HashTable과 비교해서 설명해보면 다음과 같은 형태가 됩니다.코드로 이야기 하면 대략 다음과 같은 형태가 되겠지요


앞서 들었던 Column Family의 데이타 구조를 자바 코드로 표현하면 다음과 같은 구조가 됩니다.

UserProfile={
  Cassandra={ emailAddress:”casandra@apache.org” , age:”20”}
  TerryCho= { emailAddress:”terry.cho@apache.org” , gender:”male”}
  Cath= { emailAddress:”cath@apache.org” , age:”20”,gender:”female”,address:”Seoul”}
}

자바 코드

class Keyspace{
           HashTable keyspaces = new HashTable();          

           createColumnFamily(String name){
                     keyspaces.put(name,new HashTable);
           }

           putValue(String columnFamily,String key,Object value){
                     Hashtable cf = keyspaces.get(columnFamily);
                     cf.put(key,value);
           }
}

 

class TerryVO{ // Terry is a Key
           String emailAddress; // each column
           String gender;
           // setter & getter
}

 class CathVO{ // Cath is a Key

           String emailAddress;
           String age;
           String gender;
           // setter & getter 
}

KeySpace myspace;
myspace.createColumnFamily("UserProfile");
myspace.putValue("UserProfile","TerryCho",new TerryVO("terry.cho@apache.org","male");
myspace.putValue("UserProfile","Cath",new CathVO("cath@apache.org","20","female")

 자바 개발자분들이시라면 쉽게 이해하실 수 있을것 같고
구조를 분석하다보니 오라클의 데이타 그리드 솔루션은 Coherence와 데이타 구조가 매우 유사합니다. 요즘 이게 유행인가 보네요

Cassandra Test

개념을 이해했으면 실제 테스트를 한번 해보도록 하겠습니다.

먼저 아파치 카산드라 프로젝트(http://incubator.apache.org/cassandra/) 에서 카산드라를 다운 받습니다. 압축을 푼후에 bin/cassandra.bat를 실행시킵니다. (클러스터로 기동할 수 도 있으나 여기서는 단순하게 하나의 노드만 뛰어보도록 합니다.)

이제 카산드라 커맨드 라인 인터페이스(CLI)를 시키고(/bin/cassandra-cli.bat) 다음 카산드라 노드에 연결합니다. 포트는 디폴트로 9160 포트가 지정되어 있으며 /conf/storage-conf.xml에서 Listen Address Port를 변경할 수 있습니다.  

/conf/storage-conf.xml 파일에는 default Keyspace1이라는 이름으로 Keyspace가 정의되어 있습니다. Keyspace1에 지정되어 있는 Column Family(CF) 형식은 다음과 같습니다.


Standard2 CF Terry이라는 Key Gender라는 Column Male이라는 값을 넣고 다시 조회해보겠습니다.


다음번에는 Java Code를 이용하여 카산드라에 접근하는 방법에 대해서 알아보도록 하겠습니다.

참고 할만한 자료