Tag Archives: scrub

Cassandra 특정 노드가 부하가 심해질때 긴급 처리 방법

사용자 삽입 이미지
카산드라 서버를 운영하다 보면 매우 가끔씩 특정 노드가 혼자서 미친듯이 폭주하더니 이내 뻗어버리는 현상이 발생합니다. 이경우 잘 대처하지 않고 방치해 두면 마치 전염병처럼 다른 노드까지 영향을 미치곤 합니다. 매우 간단하게 시도해 볼 수 있는 방법을 정리해 보겠습니다.

문제 파악

1. 특정 노드가 폭주를 하게 될 경우 서버의 로드가 10을 넘어 30혹은 더 높은 수치까지 치솟습니다.
2. /var/log/cassandra/system.log 파일에 다음과 같은 StatusLogger가 지속적으로 출력됩니다.
[code]INFO [ScheduledTasks:1] 2011-08-09 17:52:56,601 StatusLogger.java (line 88) system.NodeIdInfo
INFO [ScheduledTasks:1] 2011-08-09 17:52:56,601 StatusLogger.java (line 88) system.IndexInfo
INFO [ScheduledTasks:1] 2011-08-09 17:52:56,601 StatusLogger.java (line 88) system.LocationInfo
INFO [ScheduledTasks:1] 2011-08-09 17:52:56,601 StatusLogger.java (line 88) system.Migrations
INFO [ScheduledTasks:1] 2011-08-09 17:52:56,601 StatusLogger.java (line 88) system.HintsColumnFamily
INFO [ScheduledTasks:1] 2011-08-09 17:52:56,601 StatusLogger.java (line 88) system.Schema[/code]
3. 구동중인 어플리케이션에서 다음과 같은 접속불가를 나타내는 Thrift 에러가 발생합니다.
[code]TSocket: Could not connect to 123.123.123.123:9160 (Connection timed out [110])[/code]

해결 방법 – 데이터가 꼬여있을(Conflict) 경우

[code]nodetool -h localhost disablethrift
nodetool -h localhost disablegossip[/code]
위와 같은 명령을 사용하여 현재 운영중인 서비스에서 제외시킵니다. thrift를 비활성화 할 경우 어플리케이션에 응답하지 않으면 gossip을 끌 경우 노드들간의 통신을 하지 않으며 결과적으로 다른 노드들에서 보기엔 이 노드가 Down된것처럼 인식이 됩니다.
[code]nodetool -h localhost flush[/code]
위의 명령을 사용하여 현재 메모리에 올라가 있는 데이터를 강제로 SSTable에 쓰기를 합니다. 비정상적인 종료가 일어날 경우 데이터 손실로 이어질수도 있으므로 일단 데이터를 파일형태의 디비로 옮기는 작업을 합니다.
[code]nodetool -h localhost scrub[/code]
위의 명령은 SSTable을 새로 생성을 하는 명령입니다. 모든 데이터를 점검하여 새로운 SSTable을 생성합니다.  /var/log/cassandra/system.log에 데이터 관련된 Fatal에러가 발생할 경우에도 문제가 수정이 됩니다.
[code]nodetool -h localhost enablegossip[/code]
위의 명령을 실행하여 gossip을 구동합니다. 다시 클러스터에 참여를 하게 되며 다른 노드들과의 통신을 시작합니다. 이상태에서 top명령등을 사용하여 노드의 상태를 확인합니다. 정상으로 돌아왔다면 잠시 폭주하는듯 하다가 이내 안정화 됩니다.
[code]nodetool -h localhost enablethrift[/code]
모든것이 정상이라면 다시 서비스에 참여시켜야 겠죠?

해결방법 – 메모리가 부족한 상황

모든 해결 방법이 위의 경우와 동일합니다만 scrub을 사용하지 않고 compact를 사용합니다. 기본적으로 카산드라는 Compaction이라는 과정을 주기적으로 실행하며 SSTable을 병합 및 규모를 축소하는 작업을 지속적으로 하게 됩니다. 하지만 어쩐일인지 정상적으로 정리 정돈이 이루어지지 않아 서버의 부하로 이어지는 경우가 있습니다. 이 경우 튜닝이 필요하겠지만 급한대로 compact명령을 통해 강제로 Compaction을 할 수 있습니다.
[code]nodetool -h localhost disablethrift
nodetool -h localhost disablegossip
nodetool -h localhost flush
nodetool -h localhost compact
nodetool -h localhost enablegossip
nodetool -h localhost enablethrift[/code]

주의할점

별것은 아니지만 서비스 운영중에 왠만하면 피해야 하는 명령이 하나 있습니다.
[code]nodetool -h localhost repair[/code]
노드가 이상해졌다면 위와 같은 명령이 호감이 가게 됩니다. 무언가 지금의 상황을 고쳐줄것만 같은 명령입니다. 하지만 repair는 데이터를 교정해 주는 명령으로 주변 노드들과의 데이터 비교를 통하여 신뢰할 수 있는 값으로 교정을 해주는 명령입니다. 하지만 이 과정에서 메모리를 더 잡아먹게 되며 결과적으로 SSTable이 기하급수적으로 확장되는 과정을 거칩니다. 그냥 결론만 말하자면 서비스중에 이 명령을 사용하지 마십시오. 기본적으로 Cassandra는 기본 운영중에 끊임없이 데이터의 무결성을 검증하고 교정하게 됩니다. 경험상 repair는 정기점검때와 같이 서비스가 중단된 상태가 아닌 데이터가 계속해서 변화하는 상황에서 실행했을때 좋은 상황을 맞이한적이 없군요. 조심하시길 바랍니다.

[Cassandra] 0.8.1로 업그레이드 후 AbstractCassandraDaemon Fatal exception in thread Thread 에러 수정

사용자 삽입 이미지

아직 1.0도 못된 Cassandra를 운영함에 있어 여러가지 이해할수 없는 버그스러운 일이 발생하곤 합니다. 하지만 다행이도 하늘이 무너져도 솟아날 구멍은 항상 있더군요. 이번에도 아주 활당한 경험담을 적어볼까 합니다. 하지만 생각보다 검색해 보면 많은 글이 나오는걸 보면 저와같은 경험담을 가진 사람들이 많을듯 하군요.

잘 운영하던 카산드라 서버를 이번에 새로나온 0.8.1로 업그레이드를 할 경우 다음과 같은 무지막지한 에러를 보게 됩니다.

[code]ERROR [ReadStage:594] 2011-07-22 06:17:20,877 AbstractCassandraDaemon.java (line 113) Fatal exception in thread Thread[ReadStage:594,5,main]
java.io.IOError: java.io.EOFException: EOF after 12559390 bytes out of 842137600
        at org.apache.cassandra.db.columniterator.SimpleSliceReader.<init>(SimpleSliceReader.java:66)
        at org.apache.cassandra.db.columniterator.SSTableSliceIterator.createReader(SSTableSliceIterator.java:91)
        at org.apache.cassandra.db.columniterator.SSTableSliceIterator.<init>(SSTableSliceIterator.java:67)
        at org.apache.cassandra.db.filter.SliceQueryFilter.getSSTableColumnIterator(SliceQueryFilter.java:66)
        at org.apache.cassandra.db.filter.QueryFilter.getSSTableColumnIterator(QueryFilter.java:80)
        at org.apache.cassandra.db.ColumnFamilyStore.getTopLevelColumns(ColumnFamilyStore.java:1292)
        at org.apache.cassandra.db.ColumnFamilyStore.getColumnFamily(ColumnFamilyStore.java:1189)
        at org.apache.cassandra.db.ColumnFamilyStore.getColumnFamily(ColumnFamilyStore.java:1146)
        at org.apache.cassandra.db.Table.getRow(Table.java:385)
        at org.apache.cassandra.db.SliceFromReadCommand.getRow(SliceFromReadCommand.java:61)
        at org.apache.cassandra.db.ReadVerbHandler.doVerb(ReadVerbHandler.java:69)
        at org.apache.cassandra.net.MessageDeliveryTask.run(MessageDeliveryTask.java:72)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
        at java.lang.Thread.run(Thread.java:662)
Caused by: java.io.EOFException: EOF after 12559390 bytes out of 842137600
        at org.apache.cassandra.io.util.FileUtils.skipBytesFully(FileUtils.java:229)
        at org.apache.cassandra.io.sstable.IndexHelper.skipBloomFilter(IndexHelper.java:50)
        at org.apache.cassandra.db.columniterator.SimpleSliceReader.<init>(SimpleSliceReader.java:57)
        … 14 more
 INFO [CompactionExecutor:9] 2011-07-22 06:18:11,575 CompactionManager.java (line 743) Retrying from row index; data is -8 bytes starting at 14898883
 WARN [CompactionExecutor:9] 2011-07-22 06:18:11,575 CompactionManager.java (line 767) Retry failed too.  Skipping to next row (retry’s stacktrace follows)
java.io.IOError: java.io.EOFException: bloom filter claims to be 905969664 bytes, longer than entire row size -8
        at org.apache.cassandra.io.sstable.SSTableIdentityIterator.<init>(SSTableIdentityIterator.java:149)
        at org.apache.cassandra.io.sstable.SSTableIdentityIterator.<init>(SSTableIdentityIterator.java:90)
        at org.apache.cassandra.db.compaction.CompactionManager.scrubOne(CompactionManager.java:748)
        at org.apache.cassandra.db.compaction.CompactionManager.doScrub(CompactionManager.java:633)
        at org.apache.cassandra.db.compaction.CompactionManager.access$600(CompactionManager.java:65)
        at org.apache.cassandra.db.compaction.CompactionManager$3.call(CompactionManager.java:250)
        at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
        at java.util.concurrent.FutureTask.run(FutureTask.java:138)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
        at java.lang.Thread.run(Thread.java:662)
Caused by: java.io.EOFException: bloom filter claims to be 905969664 bytes, longer than entire row size -8
        at org.apache.cassandra.io.sstable.IndexHelper.defreezeBloomFilter(IndexHelper.java:111)
        at org.apache.cassandra.io.sstable.SSTableIdentityIterator.<init>(SSTableIdentityIterator.java:119)
        … 10 more[/code]
음…좀 이쁘게 보여드리고 싶었는데 달리 방법이 없네요. 위와 같은 에러가 무한정 발생을 하는데요. 위의 에러를 고칠려면 다음의 명령 한방이면 해결이 됩니다. 각각의 노드마다 한번씩 실행해 주셔야 합니다. 보유 데이터량에 따라 소요되는 시간이 다릅니다.
[code]nodetool -h localhost scrub[/code]
scrub 명령은 원하는 키스페이스 또는 컬럼패밀리만을 정하여 sstable을 새로 생성합니다. 기왕이면 서버의 사용량이 적을때를 이용해서 실행하시는것이 정신건강에 좋습니다.

노드만 충분하다면 scrub을 실행하실때에 다음의 명령을 이용하여 서비스 노드에서 제거하시는것이 좋습니다. 이유는 클러스터간에 상태가 꼬여있는 상태(?)일수가 있는데 이경우 scrub을 실행하게 되면 scrub을 실행하는 노드뿐만 아니라 다음의 노드의 부하가 올라가더니 뻗는 경우가 있더군요.
[code]nodetool -h localhost disablegossip[/code]
위의 명령을 실행하게 되면 기존의 노드들간의 클러스터링에서 제외..라는 표현보다는 사라진척 한다는 말이 맞겠네요. 서버들간의 상태를 주고 받는데에 사용되는 gossip을 끄게됩니다. 켤때는 반대로 enablegossip으로 켜주시면 됩니다.

만약에 불가피하게 서비스중에 위의 작업들을 수행해야만 하는 경우라면 클라이언트 소스를 수정할 필요 없이 다음의 명령으로 클라이언트의 요청에 응답해야 할 의무를 버릴 수 있습니다. (thrift를 시용할시)
[code]nodetool -h localhost disablethrift[/code]
마찬가지로 다시 켤때는 반대로 enablethrift를 실행하시면 됩니다.