1. where 절의 in

in을 사용할 때는 많은 키의 정보를 한 번에 읽어야 할 때, 최대한 작게 사용하는 것이 좋다. 

read를 QUORUM으로 설정했다면 최대 2개 노드에 요청하기 때문에, 100개의 키를 사용한다면, 최대 200번을 호출해서 결과를 얻어올 것이다.



2. Paging (limit)

데이터가 어느 정도 많아지면 limit을 써서 pagination을 하는 것이 성능에 문제 없다.  

데이터가 많아지면 timeout이 발생할 수 있다. 개인적으로는 백 만개의 row도 20개 씩 읽으면 문제가 없다. 



3. secondary index

secondary index는 데이터가 클러스터내에서 동기화되지 않고 노드에만 저장되어 있다. 따라서 만약 secondary index만 가지고 select문을 사용한다면 해당 노드에 요청해 scan all을 하게 된다. 따라서 반드시 써야 한다면 값의 범위가 광범위하지 않는 것이 좋다(빨리 검색될 수 있는 컬럼이어야 한다)

데이터가 적을 때나 쓸만한 index이다. 대용량에서는 쓰면 안된다. 


secondary index가 없으면 allow filtering를 사용해야 하는데..(사실 이런 쿼리를 만들었다면 다시 만들어야 한다. 잘못 만든 모델링이다) 성능 이슈가 발생한다. 



이런 문제로 cassandra 3.4부터 SSTable Attached Secondary Index (SASI)가 추가되었다. 인덱스가 별도 테이블이 아니라 SSTable에 존재하도록 하여 조금 좋아지게 했다고 한다. 

https://docs.datastax.com/en/cql/3.3/cql/cql_using/useSASIIndex.html


CREATE INDEX d_index ON crizin(d) USING 'org.apache.cassandra.index.sasi.SASIIndex';



4. 잦은 삭제

작은 삭제는 tombstone과 gc를 유발시킬 수 있다. 

데이터가 삭제되면 gc_grace_seconds(기본 값: 864000)로 되어 있어서 10일간은 그대로 남아 있고, tombstone이라고 하지만 사실 데이터가 있다. 따라서, 검색(select)할 때 tombstone도 스캔한다. 따라서 작은 삭제가 일어나는 테이블은 gc_grace_seconds를 수정할 필요가 있을 수도 있다. 



5. strong consistency 욕심

http://docs.datastax.com/en/archived/cassandra/2.0/cassandra/dml/dml_config_consistency_c.html


strong consistency를 주기 위해 write할 때 consistency를 all로 주고 read할 때 consistency를 one으로 줄 수 있다.

단점은 노드가 하나라도 문제가 있어서 데몬이 내려가 있다(node failure)면, UnavailableException 예외가 발생할 것이다. QUORUM 또는 LOCAL_QUORUM이 나을 수 있다.


strong consistency를 높이려면 replication factor보다는 read consistency level과 write consistency level을 잘 잡는게 중요하다(말은 이렇게 하지만, 실제 상용 서비스에 적용할 때는 가장 어려운 것 같다..트레이드 오프와 node repair에 대한 고민은 늘 존재한다)



6. 데이터 복구 (data repairing)

데이터가 별로 사용하지 않는 곳, 서비스에 영향을 주지 않는 곳에서는 nodetool repair 를 사용해도 큰 이슈는 없지만, 대용량에서는 어마어마한 일이 벌어질 수 있다. 공부를 많이 하고 효과적인 전략을 세워야 한다. 

(cassandra summit 슬라이드 보면 관련 내용이 많다. )


(블로그에 내용을 따로 작성하고 있다. 즉, 운영을 어느 정도 해보고 자신있을 때 진행하는 게 좋다. 잘못하면 서비스 장애 가 발생할 수 있다.)



7. 여러 클러스터로 사용

하나의 클러스터에 이런 저런 용도의 테이블을 저장하기 보다는 여러 클러스터로 나누어 테이블을 저장하는 것이 좋다. compaction은 side effect가 존재할 수 있다. Hbase처럼 최대한 클러스터를 분리시키는 것이 훨씬 낫다.

Posted by '김용환'
,


카산드라에는 여러 key 개념이 있다.



CQL을 이용할 때 일반 Database를 사용하 듯 table을 생성할 때 다음처럼 키를 생성할 수 있다. 


primary key(col1, col2, col3)


* primary key 

DB의 pk와 비슷하다. row를 유일무이하게 해주는 key를 의미한다. 1개 이상의 키가 필요하다. 


* composite(compound) key

primary key가 2개 이상이면 composite key라 부른다.


* partition key

partition key는 primary key의 1번째 key(예시에서는 col1)를 의미한다. 저장소 row key로 직접 변환하고 해시 알고리즘에 따라 클러스터에 저장(분배)된다. 대부분의 질의는 partition key를 제공해서 카산드라는 요청된 데이터가 어느 노드에 있는지 알게 된다. 


* clustering key

primary key의 1번째 key외 나머지 key를 clustering key(또는 clustering column)라 한다. 해당 key는 디스크에 데이터 순서를 안다. 하지만 어느 노드에 저장될지는 결정하지 않는다. 

순서 관련해서 오름차순, 내림차순으로 변경할 수 있다. 


with clustering order by (col2 desc) 




실제로는 '값:값'이 아닌 'col1의 값:col2의컬럼 이름'의 조합으로 되어 있다. 따라서 2.x까지는 중복이 발생할 수 있지만, 3.x부터는 문제가 없도록 되어 있다고 한다. 


실제 예시를 소개한다.


$cqlsh 

cqlsh:google> 

CREATE TABLE story.crizin (

    a int,

    b int,

    c int,

    d int,

    type int,

    PRIMARY KEY (a, b, c)


cqlsh:google> insert into google.crizin(a,b,c,d,type) values(1,1,1,1,1);

cqlsh:google> insert into google.crizin(a,b,c,d,type) values(2,2,2,2,2);



$ cassandra-cli -h IP -port 9160

[default@google] list crizin

... ;

Using default limit of 100

Using default cell limit of 100

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

RowKey: 1

=> (name=1:1:, value=, timestamp=1484644014043867)

=> (name=1:1:d, value=00000001, timestamp=1484644014043867)

=> (name=1:1:type, value=00000001, timestamp=1484644014043867)

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

RowKey: 2

=> (name=2:2:, value=, timestamp=1484644020194477)

=> (name=2:2:d, value=00000002, timestamp=1484644020194477)

=> (name=2:2:type, value=00000002, timestamp=1484644020194477)


2 Rows Returned.


cassandra data layer에서는 콜론 단위로 저장됨을 확인할 수 있다. 




지금까지 하나의 partition key에 여러 clustering 컬럼 조합을 살펴봤다. partition key를 multi로 둘 수 있다. 


primary key((col1, col2), col3)



* composite partition key

composite partition key는 다수의 컬럼을 partition key로 둔다. 


data layer에서는 row key를 'col1의 값:col2의 값'으로 변경한다. 


$cqlsh 

cqlsh:google> 


CREATE TABLE story.crizin1 (

    a int,

    b int,

    c int,

    d int,

    type int,

    PRIMARY KEY ((a, b), c)


cqlsh> insert into story.crizin1(a,b,c,d,type) values(2,2,2,2,2);

cqlsh> insert into story.crizin1(a,b,c,d,type) values(1,1,1,1,1);




$ cassandra-cli -h IP -port 9160

[default@story] use story;

Authenticated to keyspace: story

[default@story] list story.crizin1;

 list crizin1;

Using default limit of 100

Using default cell limit of 100

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

RowKey: 2:2

=> (name=2:, value=, timestamp=1484645817202697)

=> (name=2:d, value=00000002, timestamp=1484645817202697)

=> (name=2:type, value=00000002, timestamp=1484645817202697)

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

RowKey: 1:1

=> (name=1:, value=, timestamp=1484645825186628)

=> (name=1:d, value=00000001, timestamp=1484645825186628)

=> (name=1:type, value=00000001, timestamp=1484645825186628)


2 Rows Returned.

Elapsed time: 58 msec(s).



key 말고 secondary index가 존재한다. index를 구축할 수 있지만, 


CREATE INDEX d_index ON crizin(d);


성능 이슈가 존재한다는 커다란 단점이 있다. (자세한 내용은 다음 블로그에서 설명한다)







Posted by '김용환'
,


카산드라 장비가 클러스터링이 되어 있다면, nodetool status로 서로 연동되어 있음을 확인할 수 있다. 


$ nodetool status

Datacenter: datacenter1

=======================

Status=Up/Down

|/ State=Normal/Leaving/Joining/Moving

--  Address       Load       Tokens  Owns    Host ID                               Rack

UN  1.1.1.1  77.1 GB    256     ?       e9fdd401-e83e-428e-b848-5f51602145e6  rack1

UN  1.1.1.2  75.07 GB   256     ?       ca478c2c-5fec-4ee8-9f3f-37c33cc761d0  rack1

UN  1.1.1.3  73.75 GB   256     ?       b3d60757-ef63-4847-b6c5-4ee4289e9a27  rack1




데이터는 어떻게 저장되어 있을까?


카산드라에서 nodetool ring 커맨드를 실행하면, 카산드라의 consistent hashing을 통해 여러 노드 address에 저장될 수 있도록 구성되어 있음을 확인할 수 있다. 



$ nodetool ring 

Datacenter: datacenter

==========

Address       Rack        Status State   Load            Owns                Token

                                                                                           9220917267562992196

1.1.1.1  rack1       Up     Normal  75.52 GB        ?                   -9221498249641296905

1.1.1.2  rack1       Up     Normal  77.02 GB        ?                   -9200410371552807557

1.1.1.3  rack1       Up     Normal  77.17 GB        ?                   -9177126049054932799

...

1.1.1.1  rack1       Up     Normal  77.17 GB        ?                   9220917267562992196


커맨드 결과를 살펴보면 토큰이 점점 작은 값(큰 음수)에서 큰 값(큰 양수)로 이어지는지 볼 수 있다. 


같은 클러스터의 다른 장비에서 nodetool ring 커맨드를 사용하면 동일한 결과가 보인다. 클러스터 내의 데이터가 저장되는 가상 단위라 말할 수 있다.



내용을 자세히 설명한다.


consistent hashing을 소개하면, 버켓(bucket)은 미리 정해진 범위(range)로 되어 있다. 카산드라 노드는 범위를 할당하고 다음처럼 계산된다. 

범위(range)의 시작을 토큰(token)이라 부른다. 카산드라 1.2까지는 토큰이라 불렀는데, 2.0부터는 vnode라고 한다. 


그리고 CQL을 통해 데이터를 저장할 때, row key는 token을 기반으로 각 노드에 분산 저장된다. 



<그림 참조 : https://academy.datastax.com >


범위는 토큰 값의 구분 값이 들어가 있다. 

range start : 토큰 값

range end : 다음 토큰 값 -1




카산드라에서 consistent hashing에 쓰이는 기본 알고리즘은 Murmur3Patitioner이다. 

https://docs.datastax.com/en/cassandra/3.0/cassandra/architecture/archPartitionerM3P.html

https://docs.datastax.com/en/cassandra/2.1/cassandra/configuration/configCassandra_yaml_r.html


Murmur3Partitioner(m3p)는 RandomPartitioner보다 빠른 해싱과 성능이 좋다. 

(Paritioner가 여러 존재하지만, ByteOrderedPartitioner를 사용할 때는 hot spot 이슈가 생길 수 있다. 많은 이들은 



또한 cql에서 paging을 사용할 수 있다. (setFetchSize와 setPagingState를 java driver에서 지원한다)

https://docs.datastax.com/en/cql/3.1/cql/cql_reference/paging.html

http://docs.datastax.com/en/developer/java-driver/3.1/manual/paging/ 




카산드라는 키를 읽고 저장할 때 어느 범위에 있는지 알기 위해 모든 클러스터의 카산드라는 동일한 해시 함수를 사용한다. 즉 모든 노드는 클러스내의 모든 범위를 알아야 하고 자기 노드의 범위 뿐 아니라 다른 노드의 범위를 알고 있다. 


따라서, 요청을 받은 노드를 coordinator라 한다. 카산드라 키가 요청받은 노드(coordinator)에 속하지 않으면 범위에 맞는 노드로 요청을 보낸다. 



vnode는 토큰을 상위 개념이지만, 토큰을 아예 안쓰는 것은 아니다. 최신 버전(2.0 이상)은 특별히 토큰 관리를 하지 않지만, cassandra.yml에 initial_token이라는 옵션을 사용해 각 카산드라에서 나름 토큰의 범위를 지정할 수 있었다. 반대로 생각해보면 cassandra 관리를 각 카산드라 서버별로 작업을 진행해야했기에 관리가 좀 불편했다.. 


cassandra문서에 따르면 현재 initial_token은 disable이다. initial_token이 true이고 하나의 token_num이 1개이면 완전 single node가 된다. 하나의 개발 장비로 구성할 때는 이 방법이 좋을 수 있을 것이다. 

https://docs.datastax.com/en/cassandra/2.1/cassandra/configuration/configCassandra_yaml_r.html





각 노드에 하나의 토큰만 할당하기 보다 num_tokens(기본 값 256)을 사용해 늘릴 수 있다. 바로 vnode이다. vnode는 데이터를 분산시키기 위해 존재한다. 





만약 10대의 장비가 있고 num_tokens가 256이면 전체 vnode 개수는 2560이 될 것이다. 실제 개수는 정확히 나온다. 


$ nodetool ring | grep Normal | wc -l

2560


num_tokens를 256으로 사용한다면, increment repair하는데 도움이 된다고 한다(직접 해본 적은 없다)


vnode는 카산드라의 rebuild작업을 수동으로 할때 편리하다. 부하가 더 많은 노드에 분산되기 때문에 재시작에 유리할 수 있다. 게다가 rebuild할 때 하나의 복제본을 사용할 수 있다. 따라서 안정성이 더 확보된다. 






Posted by '김용환'
,



zookeeper 서버에서 leader인지 follower인지를 다음처럼 간단히 확인할 수 있다. 


$ echo srvr | nc localhost 2181


그러면 Mode로 쉽게 파악할 수 있다. 



$ echo srvr | nc localhost 2181

Zookeeper version: 3.3.3-1203054, built on 11/17/2011 05:47 GMT

Latency min/avg/max: 0/0/82

Received: 70940852

Sent: 71197941

Outstanding: 1

Zxid: 0x2000016bf

Mode: follower

Node count: 4325



$ echo srvr | nc localhost 2181 | grep Mode

Mode: leader





실제 요청(request)이 들어오고 있는지 확인하려면 다음과 같이 진행하다. 


$ echo stat | nc localhost 2181

Zookeeper version: 3.3.3-1203054, built on 11/17/2011 05:47 GMT

Clients:

...


Latency min/avg/max: 0/0/16

Received: 4288

Sent: 4288

Outstanding: 0

Zxid: 0x1007f3e40

Mode: follower

Node count: 4317



stat 또는 srvr 정보는 다음과 같다. 



지표

내용

Latency min/avg/max

클라이언트 요청을 처리하는데 소요되는 전체 지연 시간(latency)

지연시간이 길어질수록 좋지 않다.

Outstanding

서버에서 큐잉되고 있는 요청 개수. 점점 요청 개수가 많아질 수록 개수는 늘어난다. 1,2까지는 괜찮은 것 같다. 하지만 그 이상이 되면 확인해볼 필요가 있다.

Received

지금까지 받은 클라이언트 요청 개수

Sent

지금까지 보낸 클라이언트 패킷 수(응답과 상태 알림)

Mode

노드의 상태
leader 또는 follow, standalone

Zxid

노드의 id

Node Count

주키퍼 네임스페이스의 znode 개수





참고로 나는 클러스터의 zookeeper 정보의 node count를 통해서 동기 상태를 파악한다.





정상적으로 동작하는지 확인하려면 ruok를 실행한다. imok이 들어오면 괜찮다.


$ echo ruok | nc 127.0.0.1 2181

imok




클러스터 정보를 보고 싶으면, jmx로 연결해서 확인한다.  org.apache.ZookeeperService에서 ReplicatedServer mbean을 확인한다. 





환경 정보(java, zookeeper classpath 등등)는 envi 커맨드를 사용한다. 


$ echo envi | nc 127.0.0.1 2181



dump 커맨드를 사용하면 zookeeper의 모든 노드 정보를 출력한다. 노드의 값은 나오지 않는다. 


$ echo dump | nc 127.0.0.1 2181


SessionTracker dump:

org.apache.zookeeper.server.quorum.LearnerSessionTracker@63f69aa9

ephemeral nodes dump:

Sessions with Ephemerals (225):


0x54f9dca900afb7f:

/google/plus/real/monitor/n_0000000040

.....




zookeeper의 어드민은 현재 장비를 기준으로 되어 있지, 전체적으로 모니터링이 조금 어렵다. 

zktop.py 소스를 다운받아 통합 모니터링이 가능하다. 


$ wget https://raw.githubusercontent.com/phunt/zktop/master/zktop.py

$ chmod 755 zktop.py

$ ./zktop.py --server "a.google.com:2181,b.google.com:2181,c.google.com:2181"






'nosql' 카테고리의 다른 글

zookeeper rmr 커맨드  (0) 2017.01.12
[hbase]-로컬 환경 - Failed to become active master 해결  (0) 2016.08.03
[redis] redis cluster 셋업하기  (0) 2016.06.07
[cassandra] history 보기  (0) 2016.05.06
[cassandra] Unsupported null value  (0) 2015.12.28
Posted by '김용환'
,