Categories
NoSQL

[NoSQL/Cassandra] Cassandra 데이터모델에 대한 간단한 소개


새롭게 등장한 카산드라의 데이터 모델은 생각보다 간단하면서도 생각보다 복잡합니다. 기존의 RDBMS에 익숙한 개발자들은 카산드라의 데이터 모델을 공부하기 위해 많은 시간을 들여야 할지도 모릅니다. 이 글은 이러한 카산드라의 데이터모델에 대하여 간단하게 설명을 해보는 글입니다.

1. 카산드라는 키/밸류 모델에 기반합니다.

카산드라의 데이터베이스는 컬럼패밀리(ColumnFamily)라는것으로 이루어져있습니다. 이 컬럼패밀리라는것은 키/밸류 기반 데이터의 모음입니다. 이러한 용어가 어렵다면 그냥 간단하게 키/밸류 모델을 생각하시면 됩니다. 굳이 RDB의 개념과 비교를 해보자면 컬럼패밀리는 테이블(Table)로 키/밸류 쌍은 테이블의 레코드(Record)로 볼 수 있겠네요.

2. 카산드라는 두단계의 뎁스를 내포하는 키/밸류 모델입니다.

카산드라는 기존의 일반적인 키/밸류 모델을 좀더 확장한 모양세를 갖추고 있습니다. 첫번째 레벨인 레코드의 값들은 순서대로 저장된 키/밸류 데이터쌍입니다. 이러한 키/밸류 쌍은 컬럼(Column)이라고 부릅니다. 키/밸류쌍에서의 키는 곧 컬럼의 이름이 됩니다. 좀 다른말로 표현해보자면 컬럼패밀리의 레코드들은 키를 가지고 있고 한개이상의 컬럼으로 이루어져있습니다. 이러한 내포 구조는 최소한 한개의 컬럼을 가져야 하며 강제적인 조건입니다.

3. 컬럼이나 슈퍼컬럼의 이름은 두가지 방법으로 사용가능합니다. – 이름 또는 값(참조값)

첫번째로 컬럼이나 슈퍼컬럼의 이름은 말그대로 이름의 역할을 합니다. 예를 들면 사용자 정보를 담고 있는 레코드라면 컬럼의 이름이 사용자의 정보를 담고 있는 User의 이메일정보를 담고 있다고 하여 Email이 될 수 있습니다. 이것은 말그대로 어떤 값인지를 식별하는 용도로 사용될 수 있습니다.

두번째로 이름은 값을 저장하는 용도로도 사용될 수 있습니다. 블로그를 예로 들어보면 컬럼의 이름이 각각의 글에대한 구분자가 될 수 있습니다. 컬럼 네임을 데이터로 쓸수 있는 이유로는 카산드라에서는 컬럼의 숫자에 대한 제한이 없이 무한하다는 것이고 이름의 값은 Byte Array로써 어떠한 값이라도 인코딩하여 사용할 수 있다는 점입니다.

4. 컬럼(Column)이나 슈퍼컬럼(Super Column)들은 이름순으로 정렬되어 저장됩니다.

카산드라에서는 컬럼 또는 슈퍼컬럼의 이름을 어떻게 다룰것인지를 정의할 수 있습니다.(이름의 값은 단지 Byte Array로 저장됩니다만…) 이름은 Bytes Type, Long Type, Ascii Type, UTF8 Type, Lexical UUID Type, Time UUID Type 으로 다루어질 수 있습니다. 이제 설명은 최대한 많이 해본것 같으니 이해를 돕기 위해 간단한 트위터를 빗댄 예시를 보여드리도록 하겠습니다.
Tweets라는 컬럼 패밀리는 다음과 같은 트윗의 레코드를 가지고 있습니다. 레코드의 키는 Time UUID 타입이며 트윗을 받을때마다 생성됩니다. 하나의 레코드는 3개의 컬럼을 가지고 있으며(여기서 슈퍼컬럼은 사용하지 않습니다) 컬럼은 트윗에서 보여지는 간단한 내용을 입력해 보았습니다. 이러한 구조는 RDB에서 어떻게 데이터를 저장하는지와 비슷한 형태를 보여준다고 볼 수 있습니다.
다음으로 보여드릴 예제는 User_Timelines(사용자가 직접 포스팅한 트윗들을 저장하는곳)입니다. 레코드의 키는 사용자의 ID입니다.(Tweets컬럼패밀리안의 User_ID컬럼의 값을 참조합니다) 아래의 예시는 컬럼의 이름이 어떻게 값으로써 사용되는지(트윗의 ID가 컬럼의 이름으로 사용되고 있습니다)를 보여줍니다. 여기서 중요한점은 여기서 사용되는 컬럼의 이름의 타입이 시간순으로 유니크한 값을 갖는 Time UUID라는 것입니다. 이것이 의미하는것은 글이 작성된 순서대로의 정렬이 계속해서 유지된다는것입니다. 이것은 사용자별로 가장 최근의 N개의 트윗을 보여주는데에 유용합니다. 각각의 값들은 사용할곳이 없으므로 빈값(여기서는 -로 표기)으로 저장합니다.

슈퍼컬럼에 설명드리기 위해서 트윗에 얼마나 많은 URL이 사용되었는지 통계를 내려고 한다는 가정을 가져보겠습니다. 트윗이 작성될 때 사용자는 URL을 포함할 수 있는데 특정 사용자가 특정 URL을 얼마나 많은 트윗에서 언급하였는지에 대한 통계를 슈퍼컬럼을 사용하여 낼 수 있습니다.
User_URLs컬럼패밀리에서는 URL들을 키로써 저장하고 내포하는 컬럼들은 이것을 언급한 트윗의 ID를 저장하고 있는것을 알 수 있습니다.

참고:
http://maxgrinev.com/2010/07/09/a-quick-introduction-to-the-cassandra-data-model/

Categories
NoSQL

[Cassandra/PHP] Cassandra에 PHPCassa 활용하기


요즘 많은 관심을 받고 있는 NoSQL의 대표주자인 Cassandra의 경우에는 Digg, Facebook, Twitter등에서 사용이 되며 충분히 많은 관심을 받고 있는 대표적인 데이터베이스(?) 시스템입니다. 카산드라는 아마존의 Dynamo와 구글의 BigTable로부터 파생되었습니다. 카산드라는 일반적인 Key/Value 시스템들보다 훨씬 멋진 컬럼패밀리(ColumnFamily) 기반의 시스템을 제공합니다.

카산드라는 기존의 SQL시스템과 비교할때 다음의 3가지 장점을 제공합니다.

– 매우 단순한 캐패시티(Capacity) 확장
무한히 확장하는 부하가 가해질 경우 기존의 MySQL시스템에서도 충분히 이러한 부하를 처리할 수 있습니다. 이미 오랜 기간동안 증명이 된 부분이죠. 하지만 문제는 이것이 쉽지가 않다는 것입니다. 기존의 SQL시스템(RDBMS)에서는 관계(Relational)라는 것이 이러한 문제점을 더욱 키우게 됩니다. 예를 든다면 JOIN이라는 무서운 적이 있겠네요.

하지만 카산드라에서는 몇분의 시간을 할애하여 새로운 노드를 추가함으로써 말 그대로의 캐패시티 확장을 이룰 수 있습니다. 새로운 노드를 추가하고 DNS에 라운드로빈(Round-Robin)방식으로 로드밸런싱을 하는것만으로도 시스템을 확장할 수 있습니다. 물론 다른 다양한 로드밸런싱 처리도 가능하겠죠.

– 고정된 스키마
SQL을 이용한 프로젝트를 수행할경우에는 각 테이블의 스키마를 정의하여야 합니다. 그리고 프로젝트가 진행이 되어갈수록 새로운 필드를 추가하게 됩니다. 이 과정을 거치게 되면서 데이터 스키마는 변동을 가지게 되고 개발중인 어플리케이션 역시 기존의 데이터 처리를 하던 부분에 예외처리를 하던지 상호 호환성을 가지도록 다시한번 시간을 투자해 프로그램을 수정해야 합니다. 프로그램의 버전마다 테이블 스키마가 달라진다면 이것 역시 큰 문제가 될 수 있습니다.

카산드라는 일명 스키마리스(Schema-less)입니다. 스키마가 필요없다는 뜻이죠. 새로운 필드를 추가하고 싶다면 단지 추가하면 됩니다. 다른 데이터에 미치는 영향은 전혀 없습니다. 이것은 개발 시간을 단축시켜줍니다. 하지만 프로젝트가 좀 더 복잡해 질수는 있습니다.

– 처리량
SQL에서는 원하는 데이터를 추출하고 보여주는 다양한 방식을 제공합니다. 예를 들면 조인(JOIN), 서브쿼리(SUBQUERY)와 같은 다양한 방법을 이용할 수 있습니다. 하지만 이와 같은 데이터 조합 방법은 같은 데이터를 계속해서 요청하고 처리한다는 단점을 가지고 있습니다. 또한 SQL에서는 이 경우에 테이블 혹은 로우(Row)를 Lock을 걸기 때문에 동시 처리능력이 떨어지는 단점이 있습니다. 또한 수많은 사용자가 동시에 이러한 처리를 요청한다면 저 수많은 데이터의 재처리가 더욱 많이 발생할 것입니다.

카산드라는 이와같은 데이터 모델과는 크게 다릅니다. 말이 어렵겠지만 그냥 데이터를 있는 그대로 사용하면 됩니다. 카산드라와 PHP를 함께 사용하기 위한 라이브러리는 여러가지가 있습니다. 그중에서도 특히 phpcassa를 추천합니다.

1. thrift PHP Native 설치하기

thrift는 Facebook에서 만든 멀티플랫폼 환경에서 사용할 수 있는 Cassandra를 사용하기 위해 설치되어야 하는 플랫폼입니다. 기본적으로 대부분의 모든 언어에서 사용할 수 있는 라이브러리셋을 제공합니다. 하지만 좀더 빠르게 사용하기 위해서는 PHP의 Native 모듈로 설치하는 것을 추천합니다. [이곳]에서 thrift를 다운받도록 합니다.
[code]cd thrift/ext/thrift_protocol
phpize
./configure
make
sudo make install[/code]
위와 같이 컴파일 하여 설치한후에 /etc/php.d/thrift_protocol.ini 와 같이 php설정파일을 추가합니다.
[code]extension=thrift_protocol.so[/code]
혹시 위의 경로에 소스가 없을 경우 lib/php/src/ext/thrift_protocol 을 참고하시기 바랍니다.

2. phpcassa 사용하기

[이곳]에서 전체 소스를 다운받습니다. 받은 파일을 프로젝트 폴더의 phpcassa디렉토리로 옮겨놓습니다. 이제 프로젝트 소스파일의 상단에 다음과 같은 코드를 추가하면 Cassandra서버에 접속할 수 있게 됩니다.
[code]require_once(‘phpcassa/connection.php’);
require_once(‘phpcassa/columnfamily.php’);[/code]

– 커넥션 연결
phpcassa가 현재 실행중인 카산드라의 연결 인스턴스를 만들기 위해서는 다음의 과정이 필요합니다.
[code]$conn = new Connection(‘Keyspace’);[/code]
위의 코드는 디폴트로 설정된 호스트와 포트를 사용합니다. 물론 이 호스트와 포트를 특정지을수도 있습니다.
[code]$conn = new Connection(‘Keyspace1’, array(array(‘host’ => localhost, ‘port’ => 9160)));[/code]

– 컬럼패밀리 가져오기
카산드라에서 컬럼패밀리는 로우(Row)나 컬럼(Column)의 집합입니다. 이것은 RDB에서의 테이블과 비슷한 역할을 합니다. 스키마로 이미 지정된 하나의 컬럼 패밀리를 가져오는 방법은 다음과 같습니다.
[code]$column_family = new ColumnFamily($conn, ‘Standard1’);[/code]

– 데이터 삽입
컬럼패밀리에 하나의 로우를 입력하기 위해서는 다음과 같은 메서드를 사용하면 됩니다.
[code]$column_family->insert(‘row_key’, array(‘col_name’ => ‘col_val’));[/code]
위와 같이 한번에 하나씩 데이터를 입력하는 방법말고도 한번에 한개이상의 컬럼을 삽입하는 것도 가능합니다.
[code]$column_family->insert(‘row_key’, array(‘name1’ => ‘val1’, ‘name2’ => ‘val2’));[/code]

– 데이터 가져오기
카산드라에서 데이터를 가져오는 방법은 데이터를 삽입할때보다 훨씬 많은 방법을 제공합니다.
[code]$column_family->get(‘row_key’);
// returns: array(‘colname’ => ‘col_val’)[/code]
위의 메서드는 하나의 키에 대한 컬럼값을 가져오게 됩니다. 추가적인 인자 없이 메서드를 실행할 경우 로우안의 모든 컬럼데이터를 가져오게 됩니다($column_count만큼, 기본값은 100). 만약에 이름을 알고 있는 특정 컬럼만을 데이터로 가져오고 싶다면 $columns 인자를 사용하면 됩니다.
[code]$column_family->get(‘row_key’, $columns=array(‘name1’, ‘name2’));
// returns: array(‘name1’ => ‘foo’, ‘name2’ => ‘bar’)[/code]
로우의 컬럼들의 일부분만을 가져오기 위해서는 $column_start와 $column_finish 파라미터를 사용하면 됩니다. 둘중에 하나 또는 둘다의 값을 빈값으로 사용할 수 있습니다. 이경우 $column_finish는 꼭 설정해주어야 합니다. 컬럼의 이름이 1부터 9까지 있다고 가정할 때 다음과 같이 사용할 수 있습니다.
[code]$column_family->get(‘row_key’, $columns=null, $column_start=’5′, $column_finish=’7′);
// returns: array(‘5’ => ‘foo’, ‘6’ => ‘bar’, ‘7’ => ‘baz’)[/code]
한번에 다수의 로우를 가져오기 위해서는 다음과 같은 방법을 사용하면 됩니다.
[code]$column_family->multiget([‘row_key1’, ‘row_key2’]);
// returns: array(‘row_key1’ => array(‘name’ => ‘val’), ‘row_key2’ => array(‘name’ => ‘val’))[/code]
키를 이용한 특정 범위의 로우를 한번에 가져오기 위한 방법으로는 다음과 같은 방법이 있습니다. 이경우에도 $key_finish는 꼭 설정하여야 합니다. ‘row_key1’부터 ‘row_key9’까지의 로우가 존재하고 있다고 가정할 경우 다음과 같은 방법을 사용할 수 있습니다.
[code]$rows = $column_family->get_range($key_start=’row_key5′, $key_finish=’row_key7′);
// returns an Iterator over:
// array(‘row_key5’ => array(‘name’ => ‘val’),
//       ‘row_key6’ => array(‘name’ => ‘val’),
//       ‘row_key7’ => array(‘name’ => ‘val’))

foreach($rows as $key => $columns) {
    // Do stuff with $key or $columns
    print_r($columns);
}[/code]

– 카운팅(Counting)
특정 로우의 컬럼이 얼마나 많이 있는지 알아내기 위해서는 다음과 같은 방법을 사용합니다.
[code]$column_family->get_count(‘row_key’);
// returns: 3[/code]
키뿐만 아니라 컬럼의 이름을 정확히 알고 있을 경우 특정 범위만큼의 컬럼의 수를 알아내는것도 가능합니다.
[code]$column_family->get_count(‘row_key’, $columns=array(‘foo’, ‘bar’));
// returns: 2
$column_family->get_count(‘row_key’, $column_start=’foo’);
// returns: 3[/code]
또한 다수의 로우의 컬럼 숫자를 병렬로 알아오는 것도 가능합니다.
[code]$column_family->multiget_count(array(‘fib0’, ‘fib1’, ‘fib2’, ‘fib3’, ‘fib4’));
// returns: array(‘fib0’ => 1, ‘fib1’ => 1, ‘fib2’ => 2, ‘fib3’ => 3, ‘fib4’ => 5)

$column_family->multiget_count(array(‘fib0’, ‘fib1’, ‘fib2’, ‘fib3’, ‘fib4’),
                               $columns=array(‘col1’, ‘col2’, ‘col3’));
// returns: array(‘fib0’ => 1, ‘fib1’ => 1, ‘fib2’ => 2, ‘fib3’ => 3, ‘fib4’ => 3)

$column_family->multiget_count(array(‘fib0’, ‘fib1’, ‘fib2’, ‘fib3’, ‘fib4’),
                               $columns=null, $column_start=’col1′, $column_finish=’col3′)
// returns: array(‘fib0’ => 1, ‘fib1’ => 1, ‘fib2’ => 2, ‘fib3’ => 3, ‘fib4’ => 3)[/code]

– 슈퍼 컬럼(Super Columns)
카산드라는 컬럼의 그룹격인 일명 슈퍼컬럼을 제공합니다. conf/schema-sample.txt 파일의 내용을 보면 다음과 같은 설정을 확인할 수 있습니다.

– name: Super1
  column_type: Super

phpcassa에서 슈퍼컬럼을 사용하기 위해서는 추가적인 레벨의 배열의 사용하면 됩니다.
[code]$column_family = new ColumnFamily($conn, ‘Super1’);
$column_family->insert(‘row_key’, array(‘supercol_name’ => array(‘col_name’ => ‘col_val’)));
$column_family->get(‘row_key’);
// returns: array(‘supercol_name’ => (‘col_name’ => ‘col_val’))[/code]

– 컬럼의 이름과 값의 타입
현재의 최신버전인 카산드라 0.7에서는 컬럼 이름을 위한 Comparator Type과 컬럼의 값의 Validator Type을 설정할 수 있습니다. 다음과 같은 타입을 사용할 수 있습니다.

BytesType – no type
IntegerType – 32 bit integer
LongType – 64 bit integer
AsciiType – ASCII string
UTF8Type – UTF8 encoded string
TimeUUIDType – version 1 UUID (timestamp based)
LexicalUUID – non-version 1 UUID

컬럼이름의 Comparator 타입의 설정은 컬럼이 어떻게 정렬될 것인지에 대해 영향을 끼칩니다. 위에서 일반적인 컬럼패밀리와 슈퍼컬럼패밀리에 대해서 알 수 있었습니다. 슈퍼컬럼패밀리에서의 경우에는 하위컬럼들에도 별도로 다른 Comparator 타입을 지정할 수 있습니다.

– name: StandardInt
  column_type: Standard
  compare_with: IntegerType

– name: SuperLongSubAscii
  column_type: Super
  compare_with: LongType
  compare_subcolumns_with: AsciiType

카산드라는 위와 같은 타입들에 대해 바이너리 형태로서 이해를 합니다. 하지만 phpcassa에서는 이러한 데이터 타입의 Pack/Unpack을 자동으로 지원합니다. 결과적으로 다음과 같은 방법으로 IntegerType의 컬럼을 자유롭게 추가할 수 있습니다.
[code]$column_family = new ColumnFamily($conn, ‘StandardInt’);
$column_family->insert(‘row_key’, array(42 => ‘some_val’));
$column_family->get(‘row_key’)
// returns: array(42 => ‘some_val’)[/code]
위에서 언급했듯이 카산드라는 컬럼의 값에 대한 Validator를 제공합니다. 이러한 Validator는 완전한 컬럼패밀리에 설정될 수 있으며 개별의 컬럼에 설정될수도 있습니다. 다음의 다양한 예시를 보시는것이 도움이 될 것 같습니다.

– name: AllLongs
  column_type: Standard
  default_validation_class: LongType

– name: OneUUID
  column_type: Standard
  column_metadata:
    – name: uuid
      validator_class: TimeUUIDType

– name: LongsExceptUUID
  column_type: Standard
  default_validation_class: LongType
  column_metadata:
    – name: uuid
      validator_class: TimeUUIDType

phpcassa는 이러한 컬럼들의 자동적인 Pack역시 지원합니다.
[code]$column_family = new ColumnFamily($connection, ‘LongsExceptUUID’)
$column_family->insert(‘row_key’, array(‘foo’ => 123456789, ‘uuid’ => CassandraUtil::uuid1()));
$column_family->get(‘row_key’);
// returns: array(‘foo’ => 123456789, ‘uuid’ => UUID(‘5880c4b8-bd1a-11df-bbe1-00234d21610a’))[/code]
위와같은 자동화된 Pack/Unpack이 불필요할 경우 다음과 같은 방법으로 설정을 끌 수도 있습니다.
[code]$column_family = new ColumnFamily($conn, ‘Standard1’,
                                  $autopack_names=False,
                                  $autopack_values=False);[/code]

– 인덱스
카산드라 0.7부터는 2차 인덱스기능을 제공합니다. 이것을 이용하면 특정한 표현식을 사용하여 원하는 데이터만을 가져오는 방법을 매우 효율적으로 사용할 수 있습니다.

– name: Indexed1
  column_type: Standard
  column_metadata:
    – name: birthdate
      validator_class: LongType
      index_type: KEYS

다음의 예시는 인덱스가 걸려있는 birthdate컬럼의 값이 1984인 로우를 가져옵니다.
[code]$column_family = new ColumnFamily($conn, ‘Indexed1’);
$index_exp = CassandraUtil::create_index_expression(‘birthdate’, 1984);
$index_clause = CassandraUtil::create_index_clause(array($index_exp));
$rows = $column_family->get_indexed_slices($index_clause);
// returns an Iterator over:
//    array(‘winston smith’ => array(‘birthdate’ => 1984))

foreach($rows as $key => $columns) {
    // Do stuff with $key and $columns
    print_r($columns)
}[/code]

참고:
http://thobbs.github.com/phpcassa/tutorial.html
http://blog.nowvu.com/2010/08/19/cassandra-and-php-become-friendly-with-phpcassa/

Exit mobile version