[입 개발] Cassandra 의 Stage 와 SEDA

다들 알고 있듯이 Cassandra 는 SEDA 라는 Architecture를 사용하고 있습니다. SEDA는 간단하게 말하면 내부적으로 thread로 구분되는 stage 라는 것들이 존재하고 이 stage 간에는 Queue 를 통해서 데이터를 주고 받아서 각 stage 만의 작업을 처리하는 아키텍처입니다. SEDA 는 다음 https://www.google.ca/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&cad=rja&sqi=2&ved=0CDoQFjAB&url=http%3A%2F%2Fwww.eecs.harvard.edu%2F~mdw%2Fpapers%2Fseda-sosp01.pdf&ei=Ev0ZUfCuB8GriALLx4CABg&usg=AFQjCNHGulXCMJpiRQ7QfqK8eF2TLmEVRw 에서 보실 수 있습니다.

 

그래서 Cassandra의 소스는 위의 SEDA 아키텍처로 구현되어 있으므로, 소스 코드를 따라가기가 복잡합니다. 그래서 단순히 소스를 보는 것보다는 어떤 Stage 가 있는지를 이해하는 게 좀 더 도움이 될 것 같습니다.  org.apache.cassandra.concurrent/StageManager.java를 살펴보면 됩니다. multiThreadedConfigurableStage 나 multiThreadedStage 의 경우 두번째 파라매터는 몇개의 스레드를 생성할 것인지를 나타냅니다.

    static
    {
        stages.put(Stage.MUTATION, multiThreadedConfigurableStage(Stage.MUTATION, getConcurrentWriters()));
        stages.put(Stage.READ, multiThreadedConfigurableStage(Stage.READ, getConcurrentReaders()));
        stages.put(Stage.REQUEST_RESPONSE, multiThreadedStage(Stage.REQUEST_RESPONSE, FBUtilities.getAvailableProcessors()));
        stages.put(Stage.INTERNAL_RESPONSE, multiThreadedStage(Stage.INTERNAL_RESPONSE, FBUtilities.getAvailableProcessors()));
        stages.put(Stage.REPLICATE_ON_WRITE, multiThreadedConfigurableStage(Stage.REPLICATE_ON_WRITE, getConcurrentReplicators(), MAX_REPLICATE_ON_WRITE_TASKS));
        // the rest are all single-threaded
        stages.put(Stage.GOSSIP, new JMXEnabledThreadPoolExecutor(Stage.GOSSIP));
        stages.put(Stage.ANTI_ENTROPY, new JMXEnabledThreadPoolExecutor(Stage.ANTI_ENTROPY));
        stages.put(Stage.MIGRATION, new JMXEnabledThreadPoolExecutor(Stage.MIGRATION));
        stages.put(Stage.MISC, new JMXEnabledThreadPoolExecutor(Stage.MISC));
        stages.put(Stage.READ_REPAIR, multiThreadedStage(Stage.READ_REPAIR, FBUtilities.getAvailableProcessors()));
        stages.put(Stage.TRACING, tracingExecutor());
    }

각각의 Stage 에서 하는 역할은 좀 더 자세히 살펴본 후에 다시 공유하도록 하겠습니다.

[입 개발] Cassandra thrift Command map

Cassadra 의 경우 thrift를 이용해서 클라이언트와 통신을 하게됩니다.(Avro도 있지만 일단은 생략) 이 쪽 코드는 Command Pattern을 통해서 처리하게 되고, 각각의 Command는 processMap 이라는 곳에 저장됩니다. (interface/thrift/gen-java/Cassandra.java 에 존재)

    private static <I extends Iface> Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> getProcessMap(Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> processMap) {
      processMap.put("login", new login());
      processMap.put("set_keyspace", new set_keyspace());
      processMap.put("get", new get());
      processMap.put("get_slice", new get_slice());
      processMap.put("get_count", new get_count());
      processMap.put("multiget_slice", new multiget_slice());
      processMap.put("multiget_count", new multiget_count());
      processMap.put("get_range_slices", new get_range_slices());
      processMap.put("get_paged_slice", new get_paged_slice());
      processMap.put("get_indexed_slices", new get_indexed_slices());
      processMap.put("insert", new insert());
      processMap.put("add", new add());
      processMap.put("remove", new remove());
      processMap.put("remove_counter", new remove_counter());
      processMap.put("batch_mutate", new batch_mutate());
      processMap.put("atomic_batch_mutate", new atomic_batch_mutate());
      processMap.put("truncate", new truncate());
      processMap.put("describe_schema_versions", new describe_schema_versions());
      processMap.put("describe_keyspaces", new describe_keyspaces());
      processMap.put("describe_cluster_name", new describe_cluster_name());
      processMap.put("describe_version", new describe_version());
      processMap.put("describe_ring", new describe_ring());
      processMap.put("describe_token_map", new describe_token_map());
      processMap.put("describe_partitioner", new describe_partitioner());
      processMap.put("describe_snitch", new describe_snitch());
      processMap.put("describe_keyspace", new describe_keyspace());
      processMap.put("describe_splits", new describe_splits());
      processMap.put("trace_next_query", new trace_next_query());
      processMap.put("describe_splits_ex", new describe_splits_ex());
      processMap.put("system_add_column_family", new system_add_column_family());
      processMap.put("system_drop_column_family", new system_drop_column_family());
      processMap.put("system_add_keyspace", new system_add_keyspace());
      processMap.put("system_drop_keyspace", new system_drop_keyspace());
      processMap.put("system_update_keyspace", new system_update_keyspace());
      processMap.put("system_update_column_family", new system_update_column_family());
      processMap.put("execute_cql_query", new execute_cql_query());
      processMap.put("execute_cql3_query", new execute_cql3_query());
      processMap.put("prepare_cql_query", new prepare_cql_query());
      processMap.put("prepare_cql3_query", new prepare_cql3_query());
      processMap.put("execute_prepared_cql_query", new execute_prepared_cql_query());
      processMap.put("execute_prepared_cql3_query", new execute_prepared_cql3_query());
      processMap.put("set_cql_version", new set_cql_version());
      return processMap;
    }

혹시나 싶어서 저장해둡니다.

[입 개발] How to build cassandra with Eclipse

갑자기 Cassandra 소스코드가 보고 싶어져서, 빌드를 시도해봤습니다. Cassandra를 eclipse 에서 빌드하는 방법은 굉장히 쉽습니다. cassandra를 빌드하기 위해서는 ant 1.8.1 이상이 필요합니다. 저는 다음과 같이 git 을 통해서 cassandra를 받았습니다. 다음 명령을 통해서 Eclipse 에서 사용할 수 있습니다.

git clone http://git-wip-us.apache.org/repos/asf/cassandra.git cassandra-trunk
cd cassandra-trunk
ant build
ant generate-eclipse-files

해당 작업이 끝나면 다음과 같은 순서를 따릅니다.

  • File -> New -> Java Project 선택
  • Project 명을 cassandra-trunk 로 정하고 Use Default Location 을 해제한 다음 아까 전에 소스 코드를 받은 디렉토리를 지정합니다.
  • Next -> Finish 하시면 기본적으로 Cassandra 프로젝트가 완성됩니다.

이 후에 실제 실행을 할 수 있도록 Run Configuration 을 설정합니다.

  • Run -> Run Configuration 을 선택합니다.
  • Java Application 을 선택해서 New Configuration을 만듭니다.
  • Main 탭에서 Main Class 를 org.apache.cassandra.service.CassandraDaemon 를 설정합니다. 이것은 소스 코드를 해당 클래스에서 부터 살펴나가면 된다라는 것을 알려줍니다.
  • Arguments 탭에서 다음 값을 VM arguments로 설정합니다. 디렉토리 명을 자신의 위치에 맞추면 됩니다.

-Dcassandra.config=file:C:\projects\cassandra-trunk\conf\cassandra.yaml
-Dcassandra-foreground
-ea -Xmx1G
-Dlog4j.configuration=file:C:\projects\cassandra-trunk\conf\log4j-server.properties

이 이후에 cassandra.yaml 에서 /var/lib 라고 되어 있는 경로들을 모두 자신의 프로젝트 경로로 설정해줘야 합니다. 이제 cassandra 는 정상적으로 실행이 되는데, 경우에 따라서 cassandra-cli 가 동작하지 않는 경우가 있습니다.


C:\projects\cassandra-trunk\bin>.\cassandra-cli.bat
Starting Cassandra Client
Connected to: "Test Cluster" on 127.0.0.1/9160
Welcome to Cassandra CLI version Unknown

Exception in thread "main" org.yaml.snakeyaml.error.YAMLException: java.io.IOExc
eption: Stream closed
 at org.yaml.snakeyaml.reader.StreamReader.update(StreamReader.java:149)
 at org.yaml.snakeyaml.reader.StreamReader.peek(StreamReader.java:116)
 at org.yaml.snakeyaml.reader.StreamReader.peek(StreamReader.java:105)
 at org.yaml.snakeyaml.scanner.ScannerImpl.scanToNextToken(ScannerImpl.ja
va:961)
 at org.yaml.snakeyaml.scanner.ScannerImpl.fetchMoreTokens(ScannerImpl.ja
va:234)
 at org.yaml.snakeyaml.scanner.ScannerImpl.checkToken(ScannerImpl.java:18
0)
 at org.yaml.snakeyaml.parser.ParserImpl$ParseImplicitDocumentStart.produ
ce(ParserImpl.java:199)
 at org.yaml.snakeyaml.parser.ParserImpl.peekEvent(ParserImpl.java:162)
 at org.yaml.snakeyaml.parser.ParserImpl.checkEvent(ParserImpl.java:147)
 at org.yaml.snakeyaml.composer.Composer.getSingleNode(Composer.java:103)

at org.yaml.snakeyaml.constructor.BaseConstructor.getSingleData(BaseCons
tructor.java:117)
 at org.yaml.snakeyaml.Loader.load(Loader.java:52)
 at org.yaml.snakeyaml.Yaml.load(Yaml.java:166)
 at org.apache.cassandra.cli.CliClient.loadHelp(CliClient.java:188)
 at org.apache.cassandra.cli.CliClient.getHelp(CliClient.java:173)
 at org.apache.cassandra.cli.CliClient.printBanner(CliClient.java:199)
 at org.apache.cassandra.cli.CliMain.main(CliMain.java:304)
Caused by: java.io.IOException: Stream closed
 at java.io.PushbackInputStream.ensureOpen(PushbackInputStream.java:57)
 at java.io.PushbackInputStream.read(PushbackInputStream.java:149)
 at org.yaml.snakeyaml.reader.UnicodeReader.init(UnicodeReader.java:83)
 at org.yaml.snakeyaml.reader.UnicodeReader.read(UnicodeReader.java:113)
 at java.io.Reader.read(Reader.java:123)
 at org.yaml.snakeyaml.reader.StreamReader.update(StreamReader.java:147)
 ... 16 more

CliMain.java 코드를 살펴보면 loadHelp() 라는 함수에서 org/apache/cassandra/cli/CliHelp.yaml 을 찾는데 실패하는 것을 알 수 있습니다.

    private CliUserHelp loadHelp()
    {
        final InputStream is = CliClient.class.getClassLoader().getResourceAsStream("org/apache/cassandra/cli/CliHelp.yaml");
        assert is != null;

        try
        {
            final Constructor constructor = new Constructor(CliUserHelp.class);
            TypeDescription desc = new TypeDescription(CliUserHelp.class);
            desc.putListPropertyType("commands", CliCommandHelp.class);
            final Yaml yaml = new Yaml(new Loader(constructor));
            return (CliUserHelp) yaml.load(is);
        }
        finally
        {
            FileUtils.closeQuietly(is);
        }
    }

이를 해결하는 방법은 간단합니다. 해당 파일을 복사해서 org/apache/cassandra/cli/ 에 복사해주고 eclipse refresh 후에 재빌드 해주면 그 다음부터는 정상적으로 실행되게 됩니다. 아니면 이 loadhelp 이전에 호출해주는 부분을 빼버려도 가능합니다.

 

Reference: http://wiki.apache.org/cassandra/RunningCassandraInEclipse

[발 번역]atlas의 미래에서 cassandra를 살펴보자.

해당 블로그는 KT UCloud 의 지원을 받고 있습니다.

해당 글은 http://metabroadcast.com/blog/looking-with-cassandra-into-the-future-of-atlas 글을 발 번역한 것입니다. 오역에 주의하세요. 왜 성공(?) 적인 MongoDB에서 다른 NoSQL 로 옮겨갈까요? 사실 어느 하나가 특별히 나쁜 경우는 없습니다. 자신의 needs 가 바뀌거나 새로운 needs 가 생겨서 그게 적응해야 하는게 아닐까 싶습니다. 그럼 왜 atlas 의 미래에서 카산드라가 어떤 역할을 할지 함께 살펴보도록 하겠습니다.

looking with cassandra into the future of atlas

Written by Sergio Bossa on 13 Jul 2012

우리가 MongoDB 를 거의 모든 부분 에서 사용하고 있는 것은 놀라운 일은 아닙니다.

그리고 우리가 세계 최고를 지향하고, Atlas platform 에 다양한 미디어 소스 데이터를 더더욱 많이 집어 넣을려고 하는 것도 놀랍지 않습니다. Atlas platform은 MongoD의 확장성 제한을 두드려 부수고, 잘 알려지지 않은 버그를 발견하고, 디자인 결정에 대해 여러가지 궁금중을 가지도록 이끌어주고 있습니다.

이것이 곧 다가올 Atlas 4.0 버전에서 기어를 비 관계 데이터베잇 진영에서 가장 유명한  Cassandra 바꾸고 옮길려고 하는 이유입니다.

is it because you’re all crazy hipsters?

MongoDB에서 Cassandra로 옮기는 이유는 최신 기술이나 훌륭한 기술에 심취되어 있기 때문이 아니라, Atlas 플랫폼에서발생하는 데이터와 사용자 측면에서 큰 성장을 하고 있기 때문입니다.

  • 장애에 대한 높은 회복성이 필요합니다. MongoDB는 replica sets을 제공하지만, replica 끼리 동기화하거나 replication이 뒤쳐졌을 때 많은 문제를 경험했습니다.
  • 높은 확장성이 필요합니다. MognoDB의 global Lock과 많은 메모리에 대한 요구는 크게 증가하는 데이터 셋을 다루기에 이미 적합하지 않습니다.

그래서 다음과 같은 이유로 Cassandra로 옮기기로 결정했습니다.

  • Cassandra는 JVM 위에서 동작합니다. 우리는 JVM에 대한 많은 경험이 내부적으로 있습니다.
  • 스토리지 용량과 처리 성능 측면에서 확장이 가능합니다.
  • Cassandra의 Column-Based 데이터 모델은 몇 분 뒤에 얘기할 몇 가지 고급 기능을 제공합니다.
  • Cassandra의 Consistency Level 에 대한 설정은 높은 가용성과 일관성 요구사항에 대해서 훌륭한 제어권을 줍니다.

그러나 단순히 이론적인 고려사항에서 멈추지 않고, Cassandra를 사용하기 위해서, 프로토타이핑과 테스트을 진행했습니다.

하나의 Cassandra 클러스터를 AWS에 배포하고, 데이터 스키마를 프로토타이핑하고 CassJMeter 로 테스트를 시작했었습니다.( 현재까지 계속 CassJMeter에 공헌하고 있습니다. )

테스트는 훌륭하게 성공했습니다. Atlas 4.0으로 Cassandra 가 들어오게 되고, 몇몇 중요한 작업을 담당하게 되었습니다. 이제 우리가 직면한 여러가지 도전에 대해서 살펴볻록 하겠습니다.

first things first: organizing data in a schemaless world

Cassandra는 관계 데이터베이스를 선택하는 것과 매우 다릅니다. 또한, Cassandra 는 schemaless 데이터베이스의 일종입니다. 그러나 Cassandra는 자기 자신의 데이터 모델을 가지고 있습니다. Cassandra의 데이터 모델은 Keyspace 와 Column Family 들을 정의할 수 있고, Column Family 내부에 원하는 만큼의 많은 Column들을 만들 수 있습니다. 이것은 큰 유연함을 제공해주지만, 어떻게 데이터를 구조화 할것인지에 대해서는 확신을 주지 않습니다.

우리의 데이터는 중첩된 아이템과 여러개의 필드를 가지고 있는 아이템으로 구성되어 있습니다. 해당 데이터 모델을 Cassandra에서 사용하기 위해서 다음과 같은 방법을 할 수 있었습니다.

  • 엔티티들을 불확실한 데이터로 모델링했습니다. 실제로 Cassandra는 Key/Value 저장소로 이용합니다.
  • 실용적으로, Cassandra를 관계 데이터베이스 처럼 사용하기 위해서, id 로 연관된 엔티티들에 대해서 각 엔티티의 필드들을 다른 컬럼들과 연관지어서 사용했습니다.

액세스 패턴에 기반해서 데이터를 모델링 하는 것을 선택했습니다. 엔티티들이 종종 거대하고, 다른 클라이언트들이 각기 다른 시간에 다른 부분에 접근할 필요가 생겨서,  각기 다른 부분들을 쪼개어서, 하나하나를 컬럼으로 저장하는 형태로 엔티티들을 저장했습니다. 지금도 계속 전적으로 개발하고 있지만, 이것은 실제로 클라이언트가 필요한 엔티티들만 탐색하도록 하게 만들어줄 것입니다. 그로 인해서, 서버나 클라어인트 측면에서 네트웍 밴드위스나 메모리 사용량이 줄어들 것입니다.

Cassandra1

현명하게 구현하기 위해서, 엔티티들은 Cassandra 안에 JSON 형태로 저장되어 있습니다. Jackson  을 JSON 과 오브젝트 간에 자동적으로 매핑하기 위해서 사용합니다. 방금 얘기한 쪼개는 작업을 구현하기 위해서 filters  를 사용합니다. 그리고 데이터 모델에 Jackson에 의존적인 어노테이션을 적용하는 것을 피하고 데이터 모델을 깨끗하고 직렬화나 저장시 자유게 유지하기 위해서 mixins 을 사용합니다.

 

다음 도전은 …

being available, or being consistent?

CAP theorem 에 대해서 들어보셨을 껍니다.그리고 eventual consistency에 대해서도 역시 들어봤을 껍니다.

데이터 모델 측면에서 Cassandra는 유연함을 주었지만, 장애 측면에서도 Consistency Level을 설정 할 수 있게 함으로써 높은 가용성과 일관성 사이에도 유연함을 제공합니다.

실용적으로, 몇개의 Cassandra Replica 가 쓰기나 읽기 요청에 대답할지에 대해서 결정할 수 있습니다. 높은 가용성과 강한 일관성 사이의 트레이드 오프에 대해서 결정할 수 있습니다.

결국 필요한 요구사항에 의해서 우리의 선택이 결정됩니다.

Atlas 플랫폼은 몇개의 백그라운드 프로세스들이 다양한 소스로 부터 데이터를 수집합니다.  이때는 데이터의 일관성이 무엇보다 중요합니다. 그래서 “Quorum” 일관성을 선택했습니다. 일관성이 깨지는 것을 피하기 위해서 과반수 이상의 노드가 성공했음을 인지해야 함을 의미합니다.

반면에, 다수의 클라이언트가 플랫폼으로 부터 데이터를 읽어갈 때는, 성능과 가용성이 가장 중요합니다. 그래서 이 때는 “one” consistency level을 선택하고, 이것은 한대의 서버만 읽기 요청에 응답하면 됩니다. 비록 이전 데이터를 넘겨줄 수도 있지만요.(이것은 나중에 자동적으로 수정될것입니다.)

Cassandra2

그래서 데이터 모델과 액세스 패턴에 대해서 명확하게 알고 있었고, 코드를 구현했었습니다. 이제 운영을 위한 시간이었습니다.

the devil is in the operations

초반에 언급한 것 처럼, 장애 대응은 확장성 만큼 중요합니다. 그래서 실제 서비스에 추가하기 전에 여러 차례 장애 복구 테스트를 진행했습니다. Cassandra에 대해서 많은 것들을 배웠고, 지금도 다른 블로그의 주제가 될 만한 것들을 계속 많이 배우고 있습니다. 그런데, 여기에 몇 가지 주의할 부분들이 있습니다.

  • 장애난 노드를 교체할 때, 현명하게 토큰을 선택해야 합니다. 안전하게 정식 공식인 “오래된 토큰 – 1 ” 을 선택합니다.
  • DNS를 엉망으로 만들지 말아야 합니다. 수동으로 “auto_bootstrap”을 설정해서 새로운 노드가 bootstrap 할 때, seeds 목록안에 새로운 노드의 DNS 주소가 들어가 있도록 설정해서는 안됩니다.(역자 주: Cassandra는 bootstrap 시에 seed 목록에 있는 노드로 부터 정보를 가져옵니다. 아무 데이터나 정보가 없는 노드로 부터 정보를 가져오게 되면 음… -_-)
  • 복구 과정을 문서화 하고 가능한 자동화 해야 합니다.(우리는 Puppet 을 사용합니다.)

the end?

우리는 일찍 그리고 자주 출시합니다. 그래서 Cassandra 를 Atlas 3.0에 단지 추가한 것만을 홍보하는 것만으로도 자랑스럽습니다. Cassandra는 새로운 음악 데이터를 서비스하기 위해서 글로벌 비디오&오디오의 인덱스와 음악의 메타데이터를 서비스하고 있습니다.

물론 계속 작업 중입니다. 여전히 많은 데이터는 MongoDB 안에 있고,  몇달안에 해당 데이터를 이전할 것이고, Atlas 4.0의 쿨한 기능들을 완료할 것입니다.

Cassandra 가 MongoDB 만큼 쿼리 능력을 제공하지 못하기 때문에(오직 모자라는 부분입니다.) 우리는 ElasticSearch 를 추가하기로 결정했습니다. 이것은 다음 포스트에서 다루기로 하겠습니다. 그 동안에, 이 글을 좋아하기를 바랍니다.

[발 번역] Cassandra의 Read에 대하여

해당 블로그는 KT UCloud의 지원을 받고 있습니다.

해당 포스트는 http://www.datastax.com/docs/1.1/dml/about_reads 을 발 번역한 것입니다. 얼마 전에 카산드라의 Write 에 대해서 올렸는데, Read는 양이 적어서 금방 할 수 있을 것 같아서, 같이 진행하였습니다. Oracle, MySQL등의 RDBMS는 사용자도 많고, 이에 대한 트러블 슈팅을 가진 사람들도 많습니다. NoSQL등의 경우는 그에 비해 사용자도 적고 문제를 공유할 사람들도 적은게 사실입니다. 다만, Oracle, MySQL에서도 내부 구조를 이해하면 개발 시에 편리하지만, NoSQL은 필수로 알아야만 잘 이용할 수 있습니다.

About Reads in Cassandra

하나의 노드에 속한 row 에 읽기 요청이 오면, 응답을 하기 위해서 해당 row는 해당 row에 속하는 컬럼을 포함한 노드에 속하는 모든 SSTable 로 부터 결과를 조합해야 하고, 모든 디스크로 flush 되지 않은 memtable 들로 부터, 정보를 조합해야 합니다. 이런 문제를 효율적으로 처리하기 위해서, 카산드라는 메모리 내에 Bloom filter 라는 것을 이용합니다. 각각의 SSTable은 다른 I/O가 일어나기 전에 요청된 row가 해당 SSTable 에 존재하는지 확인하기 위해서 Bloom Filter를 가지고 있습니다. 그 결과, 카산드라는 다른 스토리지 시스템에 비해서 매우 읽기 부하가 심할때를 포함해서 읽기 성능이 좋습니다.

 

다른 데이터베이스와 마찬가지로, 대부분의 요청 데이터가 메모리에 들어가 있으면 매우 빠릅니다. 자주 요청되는 데이터에 대한 빠른 접근을 위해서 모든 현대의 스토리지 시스템이 몇가지 형태의 캐시를 제공함에도 불구하고,  캐시 용량을 초과하면, 모든 시스템이 성능 저하를 겪게 되고, 디스크 I/O가 필요해집니다.  카산드라의 읽기 성능은 캐시 안에 있을 때 이점이 크지만, 랜덤 디스크 액서스가 필요한 상황에도 극단적으로 느려지지는 않습니다. 읽기 부하가 늘어나 카산드라 내부에 I/O 가 많아지기 시작하면, 클러스터에 새로운 노드를 추가하므로써 쉽게 해결할 수 있습니다.(역자 주: 카산드라 클러스터가 부하가 많은 상황에 서버를 한 번에 많이 추가하면, 해당 서버로 리플리케이션 해야되는 데이터를 가진 서버들의 부하가 함께 늘어나므로, 카산드라는 서버를 한번에 적게 추가하는 것이 좋습니다. )

 

row가 빈번하게 요청되는 것을 위해, 카산드라는 내부적으로 key cache 를 만들어 두었습니다.( 추가적인 row 캐시 ).  캐싱 기능을 이용한 읽기 성능 최적화에 대한 좀 더 설명이 필요하면 Tuning Data Caches 를 보시기 바랍니다.(역자 주: 전체 SSTable을 뒤져야 하는 부담 때문에, 카산드라에서는 Row 캐시를 제공합니다. 다만 해당 기능은 기본적으로는 꺼져있습니다. )

 

카산드라에서 클라이언트의 읽기, 쓰기 요청이 어떻게 처리되는지 자세한 정보가 필요하면, 다음 About Client Requests in Cassandra 페이지를 보시기 바라니다.

[발 번역] Cassandra의 Write 에 관하여

해당 블로그는 KT UCloud의 지원을 받습니다.

최근 몇일 동안 갑자기 Cassandra의 Read/Write Path 에 대해서 궁금해져서 소스를 파게 되었는데, 그걸 정리하기 전에 공식 문서에 정리된 내용을 한번 더 정독하는게 좋을듯 해서 해당 글을 번역하게 되었습니다. 해당 글은 http://www.datastax.com/docs/1.1/dml/about_writes 발 번역한 것입니다. 오역에 주의하세요.

카산드라는 매우 빠르고, 고가용성을 가지는 쓰기에 최적화되어 있습니다. 관계형 데이테베이스는 일반적으로 최소한의 중복만 유지하기 위해서 테이블을 구조화합니다. 쿼리에 적합한 데이터를 제공하기 위해서, 정보는 여러 조각으로 나뉘어서 미리 정의된 여러 테이블에 저장되게 됩니다. 관계형 데이터베이스의 데이터 구조 때문에, 데이터 쓰기는 여러 연관된 테이블들의 데이터 정합성을 보장하기 위해서 추가적인 작업을 하는 만큼 느립니다. 그 결과, 관계형 데이터베이스는 일반적으로 쓰기가 빠르지 않습니다.

반대로, 카산드라는 쓰기 성능에 최적화 되어 있습니다. 카산드라의 쓰기는 처음에 Commit Log 에 쓰여집니다.(데이터 안전성을 위해서), 그리고, memtable이라고 부르는 메모리 내 테이블 구조에 저장합니다. 쓰기는 Commit Log와 메모리에 쓰여지면 성공하게 됩니다. 그래서, 최소한의 Disk I/O만 쓰기시에 발생합니다.( 역자 주: Commit Log를 쓰는 것도 설정할 수 있습니다. )  쓰기는 메모리내에서 배치작업형태로 , 주기적으로 SSTable(Sorted String Table) 로 만들어서 디스크에 저장합니다. Column family 마다  Memtable 과 SSTable 을 유지합니다.( 역자 주: ColumnFamilyStore 안에 Memtable 이 존재하고 Memtable은 ConcurrentSkipListMap 형태로 Key를 관리합니다. 실제로 flush 시에 SSTable은 Memtable의 데이터를 단순히 순회하면서, 출력하는 작업을 하게 됩니다. )  Memtable은 row key에 의해서 정렬되어서 저장되어 있고, SSTable 에 순차적으로 저장하게 됩니다.( 관계형 데이터베이스에서의 no random seeking )

SSTable 은 변경 이 불가능합니다.( flush가 되고 나면 다시 쓸 수 없습니다. ) 이것은 일반적으로 하나의 Row 가 여러개의 SSTable 파일들에 걸쳐서 저장된다는 것을 의미합니다. 읽을 때는, 요청이 들어왔을 때 하나의 Row는 반드시  디스크의 전체 SSTable 과, 아직 flush되지 않은 memtable 들에서 읽은 데이터를 결합해서 만들어지게 됩니다.(역자 주: HBase 도 그렇지만, 카산드라는 그 정도가 더더욱 심합니다. 최악의 경우에는 정말 극악의 읽기 속도가 예상되는 부분입니다.) 이런 문제를 보완하기 위해서, 카산드라는 Bloom filter 라고 부르는 메모리내 데이터 구조를 사용합니다. 각각의 SSTable은 Bloom Filter 와 연결되어 있고, row 키가 요청되었을 때, SSTable에 존재하는지를 디스크를 찾기 전에 Bloom Filter를 통해서 체크합니다.

카산드라에서 클라이언트의 요청을 어떻게 처리하는지에 대해서 자세히 알고 싶은 분은 About Client Requests in Cassandra 를 보시기 바랍니다.

Managing Stored Data

카산드라는 현재 column family들을 디스크에 저장할 때, 디렉토리와 파일명의 포맷을 다음과 같은 형태로 합니다.

/var/lib/cassandra/data/ks1/cf1/ks1-cf1-hc-1-Data.db

(역자 주: ks는 key space, cf 는 column family 입니다. ) 카산드라는 각각의 Column Family를 위한 디렉토리를 생성하고, 개발자나 관리자에게 다른 데이터 볼륨이나 물리 드라이브에 링크를 거는 것을 허용합니다. 이것은 매우 접근이 많은 Column Family들을 SSD 등의 더 좋은 퍼포먼스를 내는 장비에 옮기는 것이 가능하게 해주고, column family 에 따라서 스토리지 레이어에서 더 좋은 I/O 밸런스를 가지도록 스토리지 디바이스를 나눠주는게 가능해줍니다. ( 역자 주: 카산드라는 그냥 로컬 장비의 디스크의 파일을 이용하므로 이런 것이 가능합니다. )

bulk loading을 더 쉽게 하기 위해서 keyspace 이름이 SST 이름의 한 부분을 차지합니다. 더 이상 sstable 을 keyspace 이름으로 만들어진 디렉토리에 넣거나,  클러스터를 분리하기 위해서 새로운 sstable을 생성할 때 keyspace 에 대해서 고민할 없습니다

About Compaction

백그라운드에서, 카산드라는 정기적으로 SSTabe 들을 더 큰 SSTable로 합치는 작업을 진행하고 있고, 이를 Compaction 이라고 부릅니다. Compaction 은 조각난 row 들을 합쳐주고, tombstone 들을 제거하고,  주 인덱스와 보조 인덱스 들을 재구성합니다.(역자 주: SSTable이 immutable 하므로, 삭제는 tombstone이라고 해서, 해당 컬럼이 삭제되었다는 추가 정보를 넣어서 lazy delete 형태로 처리합니다. ) SSTable이 row key로 정렬되어있기 때문에, 머지 작업이 매우 효과적입니다.( Random I/O가 없으므로 ) 새로운 SSTable이 머지가 끝나서 만들어졌을 때, 원본 SSTable 들은 더 이상 쓰지 않는다고 표시되고, 나중에 JVM garbage collection(GC) 프로세스에 의해서 제거됩니다. 그러나, Compaction 을 하는 동안에는  disk 공간과 disk I/O를 일시적으로 많이 사용합니다.

Compaction 은 읽기 성능에 두 가지 형태로 영향을 미칩니다.(역자 주: 한 row 자체가 여러개로 나눠져서 들어갈 수 있으므로, 대충 어떤 얘기를 할지 생각해보시면 될듯 합니다.) compaction이 진행되는 동안에 일시적으로 disk I/O와 디스크 사용율이 증가합니다. 이로 인해서 cache에 있지 않은 데이터에 대한 read의 경우 read 성능이 영향을 받습니다. 그러나, compaction 가 완료된 후에는, cache에 없는 데이터라도 read 성능이 증가됩니다. read 요청을 처리하기 위해서 몇 개의 SSTable 파일만 체크하면 되기 때문입니다.

카산드라 1.0의 경우, Column family-size-tiered compaction 과 Leveed Compaction의 두 가지 다른  설정할 수 있는 compaction 전략이 있습니다. 해당 Compaction 전략에 대한설명은  Tuning Compaction 을 보시기 바랍니다

카산드라 1.1 부터 실제 서비스 환경에 부담을 주지 않고 다양한 compaction 전략을 테스트 할 수 있도록 옵션이 추가되었습니다. Testing Compaction and Compression 를 보시기 바랍니다. 게다가, Compaction을 멈출 수도 있습니다.

About Transactions and Concurrency Control

카산드라는 완전한 ACID-compliant 트랜잭션을 제공하지 않습니다. 관계형 DB에서 트랜잭션은 다음과 같습니다.

  • Atomic. 트랜잭션내의 모든 작업이 성공하거나 아니면 완전하게 롤백되어야 한다.
  • Consistent. 하나의 트랜잭션은 데이터베이스를 불일치하는 형태로 남겨두면 안된다.
  • Isolated. 트랜잭션이 다른 트랜잭션에 영향을 주면 안된다.
  • Durable. 서버 장애나 다른 종류의 문제에도 완료된 트랙잭션은 문제가 없어야 한다.

비관계형 데이터베이스처럼, 카산드라는 join이나 foreign key 를 제공하지 않고, 당연히 ACID 의 consistency도 제공하지 않습니다. 예를 들어서, 계정 A에서 계정 B로 돈을 옮긴다면, 전체 어카운트의 돈은 바뀌지 않습니다. 카산드라는 atomicity 와 isolation은 row 수준에서만 제공합니다. 트랜잭션 isolation 과  atomicity 를 높은 가용성과 빠른 쓰기를 위해서 포기했습니다. 카산드라의 Write는 durable 합니다.

Atomicity in Cassandra

카산드라에서 row 단위에서는 쓰기가 atomic 합니다. 즉 하나의 주어진 row에 대한 insert나 update는 하나의 쓰기 작업으로 다루어집니다. 카산드라는 여러개의 row 사이에 발생하는 update에 대해서는 트랜잭션을 제공하지 않습니다.( 역자 주: all or nothing, 다 되거나, 아예 안되거나 하는 transaction의 특성을 제공하지 못합니다. ) 하나의 리플리카에 성공적으로 썼을 때는 롤백을 하지만, 다른 리플리카에 대해서는 실패하게 됩니다. 카산드라에서는 클라이언트에 쓰기 실패에 대해서 보고하지만, 실제로 하나의 리플리카에 데이터가 남아있을 수 있습니다.

예를 들어서, 복제 개수가 3으로 설정된 상황에서 QUORUM 을 일관성 레벨로 설정해서 쓰기를 하게 되면, 카산드라는 2개의 리플리카에 쓰기를 요청할 것입니다. 만약에 하나의 리플리카에 쓰기가 실패하고 다른 한곳에는 성공하면, 카산드라는 쓰기 실패를 클라이언트에게 알려줄 것입니다. 하지만, 다른 리플리카에서 자동적으로 롤백되지는 않습니다.

카산드라는 timestamp를 컬럼에 최신 데이터를 판단하기 위해서 사용하는데, 해당 timestamp는 클라이언트 어플리케이션에서 제공한 것입니다. 하나의 row에 대해서 동시에 여러 개의 클라이언트들이 업데이트를 요청하면 가장 최신의 timestamp를 가진 요청이 항상 적용될 것입니다. 가장 최근의 업데이트가 결국 디스크에 저장되게 됩니다.( 역자 주: 결국 이런 상황이면 만약에 데이터 읽기를 ONE으로 이용해서 사용하면 실패한 결과를 읽을 수 도 있다라는 뜻이됩니다.  만약에 읽기도 QUORUM이라면 읽는 시점에, 값이 다른 하나는 보정되게 될 것입니다. )

Tunable Consistency in Cassandra

여러개의 row나 column family들에 대해서 동시에 업데이트가 발생할 때 적용할 수 있는 Lock이나 트랜잭션 전략이 카산드라에는 없습니다. 카산드라에서는 가용성과 일관성 사이를 설정할 수 있는 기능을 제공합니다.(tuning between availability and consistency) 그리고 항상 Partition tolerance 기능을 제공합니다. 카산드라는 분산 데이터베이스 클러스터내에서 모든 노드들이 CAP 이론에서 말하는 Strong Consistency를 주도록 설정할 수 있습니다. 유저는 하나의 오퍼레이션에 얼마나 많은 노드들이 DML 명령이나 SELECT query에 응답하도록 설정할 수 있습니다

Isolation in Cassandra

Cassandra 1.1 이전에는, 다른 유저가 같은 row를 읽는 동안에 다른 유저가 해당 row를 부분적으로 업데이트 하는 것이 가능했습니다. 만약 한 유저가 2000개의 컬럼과 하나의 row를 쓰고 있는 중에, 다른 유저가 해당 row 와 컬러중에 일부를 읽을 수 있습니다. 하지만, 전부는 아니지만 쓰기 작업은 계속 진행 중 일 수도 있습니다.

한 유저가 쓰기를 수행중에 다른 유저가 완료되기 전까지는 변경 중 내용을 볼 수 없게 하는 완벽한 row 단위의 isolation은 현재 적용중입니다.

전통적인 ACID((atomic, consistent, isolated, durable)) 관점에서, 해당 개선으로 카산드라는 트랙잭션의 AID를 지원하게 되었습니다. 쓰기 작업은 스토리지 엔진에서 row 단위로 분리되어 있습니다.

Durability in Cassandra

카산드라의 쓰기는 안전합니다. 복제 노드의 모든 쓰기는 성공을 리턴하기 전에 메모리와 Commit Log 양쪽에 저장됩니다. 메모리 테이블을 디스크로 저장하기 전에 서버 장애나 뭔가 충돌이 생기면, 재 시작시에 Commit Log를 이용해서 잃어버린 테이터를 모두 복구합니다.

About Inserts and Updates

동시에 여러 개의 Column들이 insert 될 수 있습니다. Column이 하나의 Column Family에 insert나 update 될 때, 클라이언트 애플리케이션은 어떤 Column의 내용을 업데이트 할 것인지 row key를 통해서 확인하게 됩니다. Row Key는 하나의 Column Family 내에서 각각의 row를 유일하게 구분해주는 primary key와 유사합니다. 그러나 primary key와는 다르게, 중복된 row key를 통한 insert는 primary key 중복 제한 위반이 발생하지 않습니다. 그 때는 UPSERT( 데이터가 없으면 insert, 데이터가 있으면 update로 동작하는 ) 로 다뤄집니다.

 

컬럼은 오직 현재 존재하는 버전보다 최신의 새 버전의 timestamp를 가질 때만 update 됩니다. 그래서 만약 update가 자주 발생한다면, 정확한 timestamp 가 필요합니다.  timestamp는 클라이언트에게서 전달받기 때문에, 모든클라이언트 장비는 NTP(network time protocol)를 통해서 동기화 시켜야 합니다.

 

About Deletes

카산드라에서 Row 나 Column 을 지울 때, 관계형 데이터베이스 일어나는 동작과 비교해서 다른 부분이 몇가지 있습니다.

When deleting a row or a column in Cassandra, there are a few things to be aware of that may differ from what one would expect in a relational database.

  1. 삭제된 데이터는 즉시 디스크에서 지워지지 않는다. 카산드라에 들어가 있는 데이터는 디스크의 SSTable에 저장되어 있습니다. SSTable이 쓰여질 때, SSTable은 immutable(파일이 더 이상 DML 오페레이션으로 업데이트가 되지 않는) 상태입니다. 이것은 삭제된 Column이 바로 사라지는 것이 아니라, tombstone 이라고 불리는 삭제 마크가 새로운 Column 정보를 가리키게 됩니다. tombstone에 설정된 삭제된 컬럼은 설정에 기록된 시간동안 존재하게 됩니다.( Column Family 에 gc_grace_seconds 라고 설정되어 있습니다. ), 해당 시간이 지나면 compaction 과정에서 실제 디스크에서 삭제되게 됩니다.
  2. 정기적인 node 복원 작업이 동작하지 않으면삭제된 컬럼이 다시 나타날 수 있다. tombstone 에서 삭제된 컬럼을 마킹하는 것은 replica가 다운되었더라도 나중에 해당 시간동안의 삭제 정보를 노드가 복구되었을 때 삭제 정보를 다시 받을 수 있는 것을 보장합니다. 하지만, 해당 노드가 tombstone 정보를 저장하는 시간보다 오래 다운되어 있다면( ColumnFamily의 gc_grace_seconds  에 정의되어 있습니다. ) 삭제 했다는정보를 전부 잃어버릴 수도 있습니다. 그리고 삭제된 데이터가 노드가 복구되면서 삭제되었던 정보가 다시 복제되어서 데이터가 다시 나타날 수 있습니다. 지워진 데이터가 다시 살아나는 현상을 방지하기 위해서, 관리자는 반드시 , 크러스터의 모든 클러스터의 노드에 정기적으로 복원작업을 해주어야 합니다.(기본적으로 10일마다 한번씩)
  3. 삭제된 row key는 range query 에서 여전히 나타날 수 있다. 카산드라에서 row를 하나 삭제했을 때, 해당 row key를 위한 모든 컬럼이 compaction에 의해서 해당 tombstone들이 삭제될 때 까지 tombstone 에 표시됩니다. 만약 row key에 어떤 컬럼도 없는 빈 row key를 가지고 있다면, 이 삭제된 key들은 get_range_slices()의 결과로 보여질 수 있습니다. 클라이언트 어플리케이션이 해당 row들에 range queries를 수행한다면, 반환되는 빈 컬럼 리스트에 대해서 필터링을 수행해야 할 수 있습니다.

About Hinted Handoff Writes

Hinted handoff 는 카산드라의 장애난 서버가 클러스터로 돌아옸을 때, consistency를 맞추기 위한 시간을 줄이기 위한 추가적인 기능입니다. (역자 주: Hinted handoff 는 카산드라의 모든 노드가 coordinator 가 되고, 이 때, 요청이 간 서버가 다운되서 정보를 저장하지 못하면, 해당 서버가 대신 정보를 저장하고 있다가 실제 서버가 복구되었을 때, 이를 알려주는 기능입니다. ) 또, 클라이언트가 쓰기 실패는 허용하지 않지만, 읽기의 불일치는 허용할 수 있을 때, 쓰기 가용성을 높이기 위해서도 사용됩니다.( 역자 주: 꼭 그렇다는 건 아니지만, SNS 류의 데이터의 경우, 잠시 쓰인 것이 읽히지 않더라도 문제 되지 않는 상황이 있을 수 있습니다. 내 트위터나 페이스북 내용이 보는 사람들마다 조금 다르다고 해서 무슨 문제가 있겠습니까? 쿨럭.. )

 

쓰기가 실행될 때, 카산드라는 영향 받는 row key를 가진 모든 리플리카에서 쓰기를 시도합니다. 쓰기가 일어난 시점에 하나의 리플리카가 다운된걸로 확인되면, 응답을 할 살아있는 리플리카가 Hint를 저장합니다. 해당 hint 는 해당 데이터의 위치 정보 뿐만 아니라( 복제해야할 노드와 복제해야할 row key ), 실제로 쓰여질 데이터도 포함됩니다. 리플리카에 hint를 저장하는데는 이미 자신에게 써야할 데이터를 쓰는 동안  쓰기 과정을 통해서 hint에 써야할 데이터들이 분석되어서 최소한의 오버헤드만 발생합니다.(역자 주: 해당 케이스는 리플리카가 3인데 하나가 죽어서 같은 키를 저장하는 리플리카에 hint가 저장되는 케이스입니다. )

 

만약 해당 row key를 저장해야하는 모든 리플리카가 다운도면, write consistency level 를 ANY로 선택함으로써, 쓰기에 성공하는 것이 가능합니다. 해당 시나리오에서는, 해당 hint 와 data 들이 coordinator 노드에 저장됩니다. 그러나 실제 리플리카에 해당 정보가 쓰여지기 전까지는 읽기는 이용할 수 없습니다. ANY consistency level 은 데이터를 쓴 후에 언제 읽을 수 있다는 것을 보장하지 않기 때문에 완벽한 쓰기 가용성을 제공합니다. ( 물론 얼마나 오랜 시간동안 리플리카가 다운되어 있는지에 달려있습니다.) ANY consistency level  을 이용하는 것은 coordinator 노드가 쓰기를 받아들일 리플리카가 없는 경우, 추가적인 row 정보를 저장해야 해서, 잠재적으로 클러스터의 부하를 높입니다.

 

Note

기본적으로 hint는 한 시간 동안만 저장됩니다. 만약 쓰기를 할 때, 모든 리플리카가 다운되어 있고, 모든 리플리카가 max_hint_window_in_ms 에 설정된 시간보다 길게 장애가 유지되면, ANY Consistency Level을 이용하더라도 잠재적으로 데이터를 잃어버릴 수 있습니다.

Hinted handoff는 ANY Consistent Level 이외에는 동작하지 않습니다. 예를 들어서 ONE Consitency Level 을 이용하면 쓰기시에 모든 리플리카가 다운되었을 때, 해당 쓰기는 hint를 저장했든 아니든, 실패로 처리됩니다.

하나의 hint를 저장하고 있는 리플리카가 gossip 프로토콜로 장애난 녿가 복구 된것을 알게 되면,  해당 리플리카가 따라잡아야 하는 놓친 쓰기들을 보내기 시작할 것입니다.

 

Note

Hinted handoff 는 정기적인 node 복구 작업을 수행하는 것을 대체하기에는 완전하지 않습니다.

간단한 thrift를 이용한 cassandra 예제

간단한 thrift를 이용한 카산드라 예제 흐음…

 

create keyspace BLOG with strategy_options={replication_factor:1} and placement_strategy = ‘org.apache.cassandra.locator.SimpleStrategy’;
create column family category with comparator=UTF8Type
and default_validation_class=UTF8Type and key_validation_class=UTF8Type
and column_metadata=[
{column_name: category_id, validation_class: UTF8Type},
{column_name: category_name, validation_class: UTF8Type}];

create column family posts with comparator=UTF8Type
and default_validation_class=LongType and key_validation_class=LongType
and column_metadata=[
{column_name: id, validation_class: UTF8Type},
{column_name: title, validation_class: UTF8Type},
{column_name: post, validation_class: UTF8Type},
{column_name: category_id, validation_class: UTF8Type, index_name: category_index, index_type: 0},
{column_name: post_date, validation_class: LongType}];
create column family comments with comparator=UTF8Type
and default_validation_class=LongType and key_validation_class=LongType
and column_metadata=[
{column_name: post_id, validation_class: UTF8Type, index_name: post_index, index_type: 0},
{column_name: comment, validation_class: UTF8Type},
{column_name: comment_date, validation_class: LongType}];

 

<pre>import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Collections;

import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.ColumnPath;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.InvalidRequestException;
import org.apache.cassandra.thrift.NotFoundException;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.thrift.SliceRange;
import org.apache.cassandra.thrift.KeyRange;
import org.apache.cassandra.thrift.KeySlice;
import org.apache.cassandra.thrift.TimedOutException;
import org.apache.cassandra.thrift.UnavailableException;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

public class CClient
{
 public static void main(String[] args)
 throws TException, InvalidRequestException, UnavailableException, UnsupportedEncodingException, NotFoundException, TimedOu
tException
 {
 TTransport tr = new TFramedTransport(new TSocket("10.64.81.181", 9160));
 TProtocol proto = new TBinaryProtocol(tr);
 Cassandra.Client client = new Cassandra.Client(proto);
 tr.open();

String key_user_id = "1";

// insert data
 long timestamp = System.currentTimeMillis();
 client.set_keyspace("BLOG");
 ColumnParent parent = new ColumnParent("category");

Column idColumn = new Column(toByteBuffer("category_id"));
 idColumn.setValue(toByteBuffer("3"));
 idColumn.setTimestamp(timestamp);
 client.insert(toByteBuffer("3"), parent, idColumn, ConsistencyLevel.ONE);

Column nameColumn = new Column(toByteBuffer("category_name"));
 nameColumn.setValue(toByteBuffer("NoSQL"));
 nameColumn.setTimestamp(timestamp);
 client.insert(toByteBuffer("3"), parent, nameColumn, ConsistencyLevel.ONE);

&nbsp;

ColumnPath path = new ColumnPath("category");

// read single column
 path.setColumn(toByteBuffer("category_id"));
 System.out.println(client.get(toByteBuffer(key_user_id), path, ConsistencyLevel.ONE));

// read entire row
 SlicePredicate predicate = new SlicePredicate();
 SliceRange sliceRange = new SliceRange(toByteBuffer(""), toByteBuffer(""), false, 10);
 predicate.setSlice_range(sliceRange);
// predicate.addToColumn_names( ByteBuffer.wrap("category_id".getBytes("UTF8")));
// predicate.addToColumn_names( ByteBuffer.wrap("category_name".getBytes("UTF8")));

KeyRange keyRange = new KeyRange();
 keyRange.start_key = ByteBuffer.allocate(0);
 keyRange.end_key = ByteBuffer.allocate(0);
 keyRange.count = 2;

List<KeySlice> results = client.get_range_slices( parent, predicate, keyRange, ConsistencyLevel.ONE);

for( KeySlice keySlice : results ){
 List<ColumnOrSuperColumn> columns = keySlice.getColumns();
 for (ColumnOrSuperColumn result : columns)
 {
 Column column = result.column;
 System.out.println(toString(column.name) + " -> " + toString(column.value));
 }

}

tr.close();
 }

public static ByteBuffer toByteBuffer(String value)
 throws UnsupportedEncodingException
 {
 return ByteBuffer.wrap(value.getBytes("UTF-8"));
 }

public static String toString(ByteBuffer buffer)
 throws UnsupportedEncodingException
 {
 byte[] bytes = new byte[buffer.remaining()];
 buffer.get(bytes);
 return new String(bytes, "UTF-8");
 }
}

&nbsp;