paust-team / paust-db

GNU General Public License v3.0
6 stars 5 forks source link

Test master application error with limited space #141

Closed dragon0170 closed 5 years ago

dragon0170 commented 5 years ago

Reference https://github.com/paust-team/paust-db/issues/131#issuecomment-471495645

rocksdb를 사용해서 데이터를 저장하는 master application에서 현재 rocksdb batch write를 하다가 error가 발생한 경우에 대한 에러 처리는 단순히 에러를 stdout으로 출력하는 것만 구현되어 있습니다. 이는 이미 블록이 정상적으로 쌓였는데 tx으로 보낸 데이터가 저장되지 않았음을 의미하므로 바람직한 에러 처리가 아닙니다. rocksdb에서 write를 할 때 error가 발생한 경우 복구가 불가능한 에러일 수도 있고 다음번 write는 성공할 수도 있는 것처럼 여러가지 error 시나리오가 가능합니다. 이런 error에 대한 처리와 master application의 durability를 향상시키기 위해서는 error를 발생시키는 실험을 해보는 것이 좋아보입니다.

우선 여러가지 error가 발생할 수 있는 시나리오를 생각해보고 이에 대한 error 발생 실험을 진행하려고 합니다. 가장 먼저 생각나는 error 시나리오는 아래와 같습니다.

추후 다른 시나리오 추가와 시나리오 구체화를 본 이슈에서 진행하고 error 발생 실험을 해볼 예정입니다.

dragon0170 commented 5 years ago

rocksdb가 사용하는 디렉토리의 저장공간이 가득차서 더이상 데이터를 저장할 수 없는 경우

결과

데이터가 쌓이다가 paust-db에서 아래와 같은 에러가 발생. 에러는 commit이 될 때마다 계속 출력되지만 MasterApplication 프로세스는 죽지않고 살아있으며 tendermint 프로세스도 살아있습니다.

ERROR[2019-03-18|08:39:48.648] Error writing batch                          state=Commit err="IO error: No space left on deviceWhile appending to file: /tendermint/paustdb.db/000003.log: No space left on device"

해당 에러는 MasterApplication.go의 rocksdb에서 batch를 write할 때 발생한 것임(아래 참고)

count, err := app.mwb.Write()
if err != nil {
    app.logger.Error("Error writing batch", "state", "Commit", "err", err)
    return
} else if count > 0 {
    app.logger.Info("Flush metadata", "state", "Commit", "size", count)
}

에러를 보면 알 수 있듯이 device의 공간이 부족하다고 더이상 file에 쓸 수 없다고 합니다. 하지만 이렇게 에러가 발생하는 중에 checkTx, deliverTx는 정상적으로 수행이 되면서 tendermint 블록은 정상적으로 쌓여갑니다. 이에 따라 client도 put이 성공했다는 메세지를 받지만 rocksdb의 batch에 담기기만 했지 write가 된 것이 아니므로 실제로 데이터가 저장된 것은 아닙니다. 당연히 error가 발생한 시점부터 put된 데이터에 대한 query 결과는 출력되지 않습니다.

다시 docker를 실행해서 paust-db를 실행하려는 경우 아래와 같은 error가 발생하며 paust-db MasterApplication이 실행되지 않고 tendermint도 ABCI server를 찾지 못해 에러만 발생합니다.

DB open error IO error: No space left on deviceWhile open a file for appending: /tendermint/paustdb.db/MANIFEST-000008: No space left on device
NewMasterApplication err: NewCRocksDB err: IO error: No space left on deviceWhile open a file for appending: /tendermint/paustdb.db/MANIFEST-000008: No space left on device
E[2019-03-18|12:16:16.990] abci.socketClient failed to connect to tcp://127.0.0.1:26658.  Retrying... module=abci-client connection=query err="dial tcp 127.0.0.1:26658: connect: connection refused"
1dennispark commented 5 years ago

rocksdb의 결과값인 status는 에러 뿐만이 아니고 여러의미를 담고있습니다. 어떤 것들이 있는지 학습도 필요합니다.

저 status에 따라서 retry 를 할지 결정하는 등의 각각 다른 행동을 해주어야할것입니다.

그리고 device의 저장공간이 다차는것도 굉장히 중요합니다. 예전에 engine이 학습하고자하는 대상으로 gas 의 정책에 예로 이런실험도 해보라고 제안드렸었습니다.

그런 상황이 현재 발생했네요 ㅎㅎ

2019년 3월 18일 (월) 오후 9:21, Kevin Choi notifications@github.com님이 작성:

rocksdb가 사용하는 디렉토리의 저장공간이 가득차서 더이상 데이터를 저장할 수 없는 경우

  • Mac OS local에서 docker를 사용해 single node로 test를 진행
  • 9MB 크기로 Mac의 새로운 볼륨을 생성해 도커의 볼륨으로 mount하여 사용
    • 해당 볼륨이 가득찰 때까지 twitter streaming api를 통해 데이터를 paust-db에 계속해서 put

결과

데이터가 쌓이다가 paust-db에서 아래와 같은 에러가 발생. 에러는 commit이 될 때마다 계속 출력되지만 MasterApplication 프로세스는 죽지않고 살아있으며 tendermint 프로세스도 살아있습니다.

ERROR[2019-03-18|08:39:48.648] Error writing batch state=Commit err="IO error: No space left on deviceWhile appending to file: /tendermint/paustdb.db/000003.log: No space left on device"

해당 에러는 MasterApplication.go의 rocksdb에서 batch를 write할 때 발생한 것임(아래 참고)

count, err := app.mwb.Write()if err != nil { app.logger.Error("Error writing batch", "state", "Commit", "err", err) return } else if count > 0 { app.logger.Info("Flush metadata", "state", "Commit", "size", count) }

에러를 보면 알 수 있듯이 device의 공간이 부족하다고 더이상 file에 쓸 수 없다고 합니다. 하지만 이렇게 에러가 발생하는 중에 checkTx, deliverTx는 정상적으로 수행이 되면서 tendermint 블록은 정상적으로 쌓여갑니다. 이에 따라 client도 put이 성공했다는 메세지를 받지만 rocksdb의 batch에 담기기만 했지 write가 된 것이 아니므로 실제로 데이터가 저장된 것은 아닙니다.

다시 docker를 실행해서 paust-db를 실행하려는 경우 아래와 같은 error가 발생하며 paust-db MasterApplication이 실행되지 않고 tendermint도 ABCI server를 찾지 못해 에러만 발생합니다.

DB open error IO error: No space left on deviceWhile open a file for appending: /tendermint/paustdb.db/MANIFEST-000008: No space left on device NewMasterApplication err: NewCRocksDB err: IO error: No space left on deviceWhile open a file for appending: /tendermint/paustdb.db/MANIFEST-000008: No space left on device E[2019-03-18|12:16:16.990] abci.socketClient failed to connect to tcp://127.0.0.1:26658. Retrying... module=abci-client connection=query err="dial tcp 127.0.0.1:26658: connect: connection refused"

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/paust-team/paust-db/issues/141#issuecomment-473887686, or mute the thread https://github.com/notifications/unsubscribe-auth/AqM4SokdoV2CLx4oIkB_205YhlhfJeLBks5vX4TegaJpZM4b2US6 .

dragon0170 commented 5 years ago

rocksdb의 status가 가지고 있는 code, subcode, severity는 아래에 있습니다. 각각의 경우에 대해 어떤 행동을 할 지 Master application에서 처리하면 될 거 같네요.

  enum Code : unsigned char {
    kOk = 0,
    kNotFound = 1,
    kCorruption = 2,
    kNotSupported = 3,
    kInvalidArgument = 4,
    kIOError = 5,
    kMergeInProgress = 6,
    kIncomplete = 7,
    kShutdownInProgress = 8,
    kTimedOut = 9,
    kAborted = 10,
    kBusy = 11,
    kExpired = 12,
    kTryAgain = 13,
    kCompactionTooLarge = 14
  };

  enum SubCode : unsigned char {
    kNone = 0,
    kMutexTimeout = 1,
    kLockTimeout = 2,
    kLockLimit = 3,
    kNoSpace = 4,
    kDeadlock = 5,
    kStaleFile = 6,
    kMemoryLimit = 7,
    kSpaceLimit = 8,
    kMaxSubCode
  };

  enum Severity : unsigned char {
    kNoError = 0,
    kSoftError = 1,
    kHardError = 2,
    kFatalError = 3,
    kUnrecoverableError = 4,
    kMaxSeverity
  };
dragon0170 commented 5 years ago

위의 에러를 분류하고 각각의 에러에 대한 Master Application의 대처방법을 정하고 의도한대로 동작하는지 확인할 필요가 있습니다. 현재 gorocksdb wrapper의 경우 c++에 대한 모든 것이 wrapping된 것이 아니라 status가 아닌 하나의 error string만 return하고 있어서 해당 에러를 parsing하거나 gorocksdb를 수정해서 status에 대한 것도 우리가 wrapping해서 사용하는 방식이 있을 거 같네요. rust의 경우 rocksdb에 대한 wrapping이 어떤지 확인이 필요합니다. paust-db에서 c++을 바로 사용한다면 wrapping이 필요없어 가장 간단할 거 같긴 하네요.

-> 우선 에러 핸들링에 대한 로직만 정리하는 걸로 결정했습니다.

dragon0170 commented 5 years ago

tendermint의 경우 block 등의 데이터를 저장할 때 내부적으로 leveldb를 사용합니다. tendermint의 leveldb 저장공간 부족에 대한 에러를 어떻게 처리하는지 알아보기 위해 https://github.com/paust-team/paust-db/issues/141#issuecomment-473887686 와 비슷한 실험을 진행해보았습니다.

tendermint의 leveldb가 사용하는 디렉토리의 저장공간이 가득차서 더이상 데이터를 저장할 수 없는 경우

결과

tendermint의 블록이 leveldb에 저장되다가 node0의 볼륨이 가득찼을 때

node0 process를 종료하고 볼륨의 크기를 충분히 증가시킨 후 다시 cluster를 구성하였을 때

tendermint의 leveldb 에러에 따른 처리 로직을 참고해서 paust-db의 rocksdb 에러 핸들링 로직을 정리하면 될 것 같습니다.

elon0823 commented 5 years ago

test 완료 / 추후 tendermint 에서 처리하는 구조 처럼 master application 의 rocksdb error 에 대한 task 정의 필요