Tag Archives: Cassandra

[NoSQL/Cassandra] Java용 가장 유명한 클라이언트 Hector 소개

사용자 삽입 이미지

Introduction

그리스 신화에서 카산드라의 형제로 나오는 헥터(Hector)는 가장 위대한 전사였고 트로이를 건설한 인물로 묘사됩니다. 하지만 현재의 IT세계에서는 이둘을 확장성이 뛰어난 데이터베이스와 여기에 쓰이는 자바 클라이언트 라이브러리에 이름을 붙였군요. 실제로 Cassandra를 사용함에 있어 자바를 선택하셨다면 가장 범용적으로 사용되는 라이브러리가 바로 이 Hector입니다. 가장 꾸준히 업그레이드 되고 있고 레퍼런스 역시 잘 구축되어있습니다.

지금까지 나온 다양한 Raw Thrift 클라이언트들은 대체적으로 실제의 클라이언트가 요구하는 필수적인 기능들은 부족한 상황입니다. 이러한 갭을 채우기 위해서 Hector를 만들었습니다.

Cassandra를 위한 하이 레벨 객체 지향 인터페이스를 제공합니다. 기존의 클라이언트들은 항상 멋지고 깔끔한 작업이 가능하도록 하지는 못했습니다. 좀 더 높은 레벨의 깔끔한 API를 제공하는 자바 클라이언트가 바로 이 Hector입니다.

Hector는 Failover를 지원합니다. Cassandra는 데이터를 분산하여 저장하며 어떤 경우에는 매우 잘 동작하겠지만 하나의 노드 또는 다수의 노드가 다운될 수 있습니다. 그러나 기존의 Thrift는 이러한 문제에 아무런 처리를 제공하지 않습니다. 그러나 Hector는 특정 노드가 죽을 경우 자동으로 실패한 명령을 수행할 수 있는 사용가능한 다른 호스트를 검색하고 사용자에게 에러를 알립니다. Failover 를 위한 3가지 정책을 제공하는데 FAIL_FAST(재시도 없음, 에러 발생시 바로 실패, 스마트하지 않음), ON_FAIL_TRY_ONE_NEXT_AVAILABLE(포기하기전에 다른 호스트에 한번 더 시도), ON_FAIL_TRY_ALL_AVAILABLE(포기하기 전에 모든 호스트에 시도)를 제공합니다.

Hector는 커넥션 풀링을 제공합니다. 이 기능은 대규모 어플리케이션에 필수적인 부분입니다. 이런 어플리케이션은 일반적으로 사용되는 DAO패턴을 통해 엄청난 횟수의 작은 규모의 읽기/쓰기가 발생합니다. 클라이언트들은 매번의 요청에 대해 새로운 커넥션을 열만큼의 여력을 가지지 못합니다(TCP 핸드쉐이크 오버헤드). 이에 Hector는 커넥션풀링을 제공하며 매우 멋진 프레임워크 입니다.

Hector는 JMX를 지원하면 이를 이용해서 실행 레벨에서 Metric, 사용가능한 커넥션 수, 유휴 커넥션수, 에러통계등의 정보를 가져올 수 있습니다. Hector는 Command 디자인패턴을 지원하며 사용자는 자신의 비지니스 로직에만 집중할 수 있도록 설계되어있습니다.

Getting Started (5 minutes)

Fully Mavenized
이 저장소를 사용하기 위해서는 다음의 의존성 선언을 당신의 POM파일에 추가하십시오
[code]<dependency>
    <groupId>me.prettyprint</groupId>
    <artifactId>hector-core</artifactId>
    <version>0.8.0-2</version>
</dependency>[/code]
Initializing a Cluster
이제 처음으로 Cassandra 클러스터를 나타내는 클러스터 객체를 생성할 것입니다. 여기서 정의하는 클러스터 이름은 Hector에서 식별하기 위해 사용되며 실제 클러스터의 이름과는 무관합니다. 코드를 좀더 깔끔하게 보이기 위해 API 패키지 전체를 임포트 하였습니다.
[code]import me.prettyprint.hector.api.*;
    …..
Cluster myCluster =
    HFactory.getOrCreateCluster(“test-cluster”,”localhost:9160″);[/code]
스키마를 정의해 봅시다.
[code]ColumnFamilyDefinition cfDef =
HFactory.createColumnFamilyDefinition(
“MyKeyspace”,
“ColumnFamilyName”,
ComparatorType.BYTESTYPE);

KeyspaceDefinition newKeyspace =
HFactory.createKeyspaceDefinition(
“MyKeyspace”,                
ThriftKsDef.DEF_STRATEGY_CLASS,  
replicationFactor,
Arrays.asList(cfDef));

// 클러스터에 스키마 추가
// 두번째 파라미터인 “true”는 모든 클러스터에 변화가 완료될때까지 블록이 됨을 의미
cluster.addKeyspace(newKeyspace, true);[/code]
한번 스키마가 생성되고 나면 이전의 호출은 스키마가 이미 존재함을 알리는 예외를 발생시킵니다. 이 문제를 해결하기 위해서 위의 코드를 createSchema()라는 메소드로 래핑하여 다음과 같이 변경하였습니다.
[code]KeyspaceDefinition keyspaceDef = cluster.describeKeyspace(“MyKeyspace”);

// Keyspace가 존재하지 않는다면 생성합니다.
if (keyspaceDef == null) {
    createSchema();
}[/code]
이제 생성된 Keyspace를 다음과 같은 명령으로 꺼내 쓸 수 있게 되었습니다. “MyKeySpace”는 반드시 이전에 생성된 Keyspace여야 합니다.
[code]Keyspace ksp = HFactory.createKeyspace(“MyKeyspace”, myCluster);[/code]

Creating a template
템플릿은 Keyspace를 조작하기 위한 객체입니다. 이론적으로 비지니스 로직에 대한 접근을 용이하게 하기 위해 사용됩니다.
[code]import me.prettyprint.cassandra.service.template.ColumnFamilyTemplate;
    ……
ColumnFamilyTemplate<String, String> template =
new ThriftColumnFamilyTemplate<String, String>(
ksp,
columnFamily,
StringSerializer.get(),        
StringSerializer.get());[/code]
Accessing data
* Update
[code]// <String, String> 는 키와 값을 뜻함
ColumnFamilyUpdater<String, String> updater =
    template.createUpdater(“a key”);
updater.setString(“domain”, “www.datastax.com”);
updater.setLong(“time”, System.currentTimeMillis());

try {
    template.update(updater);
} catch (HectorException e) {
    // do something …
}[/code]
* Read
[code]try {
    ColumnFamilyResult<String, String> res =
        template.queryColumns(“a key”);
    String value = res.getString(“domain”);
    // 이전에 저장했던 “www.datastax.com” 가 반환됨
} catch (HectorException e) {
    // do something …
}[/code]
* Delete
[code]try {
    template.deleteColumn(“key”, “column name”);
} catch (HectorException e) {
    // do something
}[/code]

참고 :
http://prettyprint.me/2010/02/23/hector-a-java-cassandra-client/ 
https://github.com/rantav/hector/wiki/Getting-started-%285-minutes%29
https://github.com/rantav/hector/downloads  (다운로드)

[NoSQL/Cassandra] Java용 클라이언트 Pelops 소개

Pelops

그리스 신화중에 승리의 왕 아가멤논 트로이의 몰락 이후에 카산드라를 잡아가게 되는데 카산드라는 Pelops, Teledamus 두명의 아들이 있었습니다. 이 Pelops 자바 라이브러리는 Cassandra 데이터베이스를 아름다운 방법의 코드(?)로 이용할수 있도록 해주기 때문에 카산드라의 아름다운 아들인 Pelops의 이름을 따 명명 되었습니다.

이 라이브러리의 코드는 이곳에서 다운받을 수 있습니다 : https://github.com/s7/scale7-pelops

목표

Pelops는 복잡한 상업 프로젝트들의 데이터베이스에 대한 광범위한 사용을 위해 사용되는 코드의 질적 향상을 위해 만들어졌습니다. 이 라이브러리의 주요한 목표는 다음과 같습니다.
– Cassandra의 API 심플하지만 아름다운 방법으로 누구에게나 충실하게 즉시 이해될 수 있도록 해야 한다.
– 데이터를 처리하는 코드로 부터 커넥션풀링과 같은 로우레벨에 대한 관심을 완전히 분리시켜야 한다.
– “드레싱 코드(dressing code?)”를 제거하여 의미있는 데이터 처리를 위해 명확하고 분명한 형태의 코드를 유지 하도록 해야 한다.
– 함수 오버로딩 또는 강력한 하이레벨의 메소드를 통해 개발 속도를 가속할 수 있어야 한다.
– 운영중인 노드의 숫자에 기초한 로드밸런싱 전략을 구현할 수 있어야 한다.
– 어플리케이션 레벨의 로직의 문제를 감추지 않는 강력한 에러 핸들링 및 복구 기능을 포함해야 한다.
– 변화에 의한 문제가 발생하는것을 유발하지 않고 카산드라의 새로운 릴리즈나 기능을 따라갈 수 있어야 한다.
– 이러한 클라이언트 코드에 대한 오래 지속될수 있는 패러다임을 정의할 수 있어야 한다.

5분만에 세팅하고 구동하기

Pelops와 Cassandra를 함께 구동하기 위해서 다음의 3가지를 알아야 합니다.

  1. 한번의 시작으로 어떻게 커넥션풀을 생성할 수 있는가.
  2. Mutator 클래스를 이용하여 어떻게 데이터를 쓸 수 있는가.
  3. Selector 클래스를 이용하여 어떻게 데이터를 읽을 수 있는가.

매우 쉽죠?

커넥션풀 생성하기

Cassandra 클러스터를 이용해 어떤 작업을 처리하기 위해 당신은 커넥션 풀을 정의할 필요가 있습니다. 일반적으로 당신의 어플리케이션의 시작부분에 한번만 정의되기만 하면 됩니다. 때때로 당신은 하나 이상의 커넥션풀을 정의해야 할 경우도 있을것입니다. 예를 들어 우리의 프로젝트에서 2개의 Cassandra 클러스터를 사용하였는데 하나는 Random Partitioning 데이터 저장소로 사용되고 다른 하나는 인덱스 사용을 위해 Order Preserving Partitioning을 사용하였습니다. 당신은 원하는 만큼의 커넥션풀을 생성하는것이 가능합니다.

풀을 생성하기 위해 알려진 노드들의 리스트를 정의하는 이름을 특정 지어야 합니다. 네트워크 포트와 커넥션풀을 컨트롤하기 위한 정책을 포함할 수 있습니다. 다음은 기본 정책을 사용하여 풀을 생성하는 예시입니다.
[code]Pelops.addPool(
    “Main”,
    new String[] { “cass1.db.com”, “cass2.db.com”, “cass3.db.com”},
    9160,
    new Policy());[/code]

Mutator 사용하기

Mutator클래스는 Keyspace가 돌연변이(?)를 일으키도록 하여줍니다(데이터에 어떤식으로든지 변화를 준다는 의미에서 시작하는것 같습니다). 당신은 Pelops에 새로운 Mutator를 요청할 수 있고 당신이 원하는 어떤 변화를 일으키도록 할 수 있습니다. 이러한 일련의 작업들은 하나의 배치 메소드인 execute 가 호출될 때 Cassandra로 전송됩니다.

Mutator를 생성하기 위해 미리 지정해둔 커넥션풀의 이름과 변화를 주고 싶은 Keyspace의 이름을 명시해주어야 합니다. 다음과 같은 방식으로 SupportTickets Keyspace를 수정할 수 있는 Mutator를 가져옵니다.
[code]Mutator mutator = Pelops.createMutator(“Main”, “SupportTickets”);[/code]
한번 Mutator를 생성하게 되면 이제부터 다양한 변화를 줄 수 있게 됩니다.
[code]/**
 * 다수의 서브컬럼 값을 슈퍼 컬럼에 다중 쓰기를 합니다.
 * @param rowKey                    수정을 원하는 로우의 키
 * @param colFamily                 작업을 수행하기 원하는 슈퍼컬럼 패밀리 이름
 * @param colName                   슈퍼컬럼의 이름
 * @param subColumns                값을 쓰기 원하는 서브 컬럼들의 이름
 */
mutator. writeSubColumns(
    userId,
    “L1Tickets”,
    UuidHelper.newTimeUuidBytes(), // 시간순 정렬
    mutator.newColumnList(
        mutator.newColumn(“category”, “videoPhone”),
        mutator.newColumn(“reportType”, “POOR_PICTURE”),
        mutator.newColumn(“createdDate”,
            NumberHelper.toBytes(System.currentTimeMillis())),
        mutator.newColumn(“capture”, jpegBytes),
        mutator.newColumn(“comment”) ));

/**
 * 컬럼의 리스트나 슈퍼 컬럼을 삭제합니다.
 * @param rowKey                    수정을 원하는 로우의 키
 * @param colFamily                 작업을 수행하기 원하는 컬럼 패밀리 이름
 * @param colNames                  삭제를 원하는 서브/슈퍼 컬럼 이름
 */
mutator.deleteColumns(
    userId,
    “L1Tickets”,
    resolvedList);[/code]
모든 변화에 대한 작업을 명시한 다음 단일 배치 명령인 execute를 호출하여 Cassandra로 전송합니다. 이 작업에는 Cassandra의 Consistency 레벨값을 파라미터로 사용합니다.
[code]mutator.execute(ConsistencyLevel.ONE);[/code]
여기서 알아두어야 하는 점은 당신이 원하는 어떤 후속작업을 시작하기 전에 execute가 실행되었다면 추가적인 작업이 함께 적용될 수 없습니다. Mutator를 execute가 실행된 이후에 재사용될 수 없으며 이런 경우가 생긴다면 두개 또는 그 이상의 Mutator를 생성하여야 하며 그들을 execute할때는 최소한 QURUM Consistency 레벨을 사용하여야 합니다.

사용가능한 오버로드된 메소드들의 리스트를 확인하기 위해 Mutator 클래스 [소스]를 확인하세요.

Using a Selector

Selector 클래스는 Keyspace로부터 데이터를 읽어들이는데에 사용됩니다. Pelops에게 새로운 Selector를 요청하고 Selector의 메소드를 사용하여 데이터를 읽어옵니다.
[code]Selector selector = Pelops.createSelector(“Main”, “SupportTickets”);[/code]
한번 Selector 인스턴스 생성을 하게 되면 다양한 오버로드 메소드를 통해 데이터를 읽어올 수 있게 됩니다.
[code]/**
 * 로우로 부터 슈퍼컬럼을 읽어오기
 * @param rowKey                로우의 키
 * @param columnFamily          슈퍼컬럼을 포함하고 있는 컬럼패밀리
 * @param superColName          데이터를 가져오고 싶은 슈퍼컬럼의 이름
 * @param cLevel                Consistency Level
 * @return                      요청된 슈퍼컬럼
 */
SuperColumn ticket = selector.getSuperColumnFromRow(
    userId,
    “L1Tickets”,
    ticketId,
    ConsistencyLevel.ONE);

assert ticketId.equals(ticket.name)

// 서브컬럼들 순환하며 데이터 읽기
for (Column data : ticket.columns) {
    String name = data.name;
    byte[] value = data.value;
}

/**
 * 로우로 부터 슈퍼컬럼 리스트 읽어오기
 * @param rowKey                로우의 키
 * @param columnFamily          슈퍼컬럼을 포함하고 있는 컬럼패밀리
 * @param colPredicate          슈퍼컬럼의 Selector 정의
 * @param cLevel                Consistency Level
 * @return                      매칭된 컬럼의 리스트
 */
List<SuperColumn> allTickets = selector.getSuperColumnsFromRow(
    userId,
    “L1Tickets”,
    Selector.newColumnsPredicateAll(true, 10000),
    ConsistencyLevel.ONE);

/**
 * 로우로부터 슈퍼컬럼 셋 읽어오기
 * @param rowKeys                로우의 키
 * @param columnFamily           슈퍼컬럼을 포함하고 있는 컬럼패밀리
 * @param colPredicate           슈퍼컬럼의 Selector 정의
 * @param cLevel                 Consistency Level
 * @return                       매칭된 슈퍼컬럼으로 이루어진 Map 객체
 */
Map<String, List<SuperColumn>> allTicketsForFriends = selector.getSuperColumnsFromRows(
    Arrays.asList(new String[] { “matt”, “james”, “dom” }, // 친구들
    “L1Tickets”,
    Selector.newColumnsPredicateAll(true, 10000),
    ConsistencyLevel.ONE);

/**
 * 순서로 저장된 슈퍼컬럼의 페이지 네비게이션 구현
 * @param rowKey                로우의 키
 * @param columnFamily          슈퍼컬럼을 포함하고 있는 컬럼패밀리
 * @param startBeyondName       읽어오고자 하는 정렬된 데이터의 시작 키 값
 * @param orderType             어떤 타입의 정렬을 사용하는가
 * @param reversed              오름차순, 내림차순 정의
 * @param count                 데이터를 가져올 최대 갯수
 * @param cLevel                Consistency Level
 * @return                      슈퍼컬럼들의 페이지단위 리스트
 */
List<SuperColumn> pageTickets = getPageOfSuperColumnsFromRow(
    userId,
    “L1Tickets”,
    lastIdOfPrevPage, // null이면 처음부터
    Selector.OrderType.TimeUUIDType, // 슈퍼컬럼 패밀리가 어떻게 정렬되어있는가
    true, // 블로그글과 같은 역순으로 데이터 읽음
    10, // 페이지당 데이터 갯수
    ConsistencyLevel.ONE);[/code]
매우 많은 데이터를 읽어오는 Selector메소드 사용시 많은 Cassandra에 많은 부하를 줄 수 있을것입니다. 하지만 이러한 페이징 기법을 사용하게 되면 단순하게 이 문제를 해결 할 수 있습니다. Selector 클래스의 [소스]를 확인해 보세요.

다른 기능들

Pelops를 사용하기 위한 핵심적인 기능들에 대해 알아보았습니다. 마지막으로 Pelops에서 지원하는 다른 유용한 기능들에 대해 설명하겠습니다.

– 로우 키 레벨의 삭제를 원할 경우 KeyDeletor 클래스를 사용하면 됩니다. (Pelops.createKeyDeletor)
– Cassandra 클러스터의 정보를 가져오기를 원할 경우 Metric 클래스를 사용하면 됩니다. (Pelops.createMetrics)
– 시간순으로 정렬되는 유니크한 ID값인 Time UUID를 사용하기 위해서는 UuidHelper 클래스를 사용하면 됩니다.
– 숫자를 이진값으로 저장하기 위해서는 NumberHelper 클래스를 사용하면 됩니다.
– 문자열을 이진값으로 저장하기 위해서는 StringHelper 클래스를 사용하면 됩니다.
– Pelops 라이브러리의 메소드와 Cassandra의 통신중에 발생하는 예외는 Cassandra에서 정의한 예외들을 사용합니다.

참고 :
http://ria101.wordpress.com/2010/06/11/pelops-the-beautiful-cassandra-database-client-for-java/