관리 메뉴

HAMA 블로그

[하이퍼레저 패브릭] FastFabric - 20,000tps 본문

블록체인

[하이퍼레저 패브릭] FastFabric - 20,000tps

[하마] 이승현 (wowlsh93@gmail.com) 2019.08.30 18:17

FastFabric: Scaling Hyperledger Fabric to 20,000 Transactions per Second

패브릭 기술에 관심있는 사람이라면 한번은 보지 않았을 까 하는 논문인데..

과연 어떤식으로 커스터마이징을 하면 20,000 TPS를 달성 할 수 있을까? 이 논문에서는 Hyperledger Fabric 1.2 기반으로 아키텍쳐를 변경하여 성능을 3,000 TPS에서 20,000 TPS로 높였다고 하는데 과연 어떤 부분을 고쳐서 일까? 말이 되는 소릴 하는 걸까? 이것에 관해 각각의 토픽에 대해  배경지식/논문의 해결방식/개인적인소견(문제점)으로 정리하고자 한다.

1. Orderer - Seperate transaction header from payload

배경지식:  orderer 는 client로 부터  트랜잭션을 받는데, 트랜잭션 내부에는 Transaction ID 와 함께 endorser 들의 서명과 RWSet 등을 포함한 작지 않은 Payload가 있다. 이런 데이터들을 받아서 각 오더러는 Kafka에 전달하여 정렬을 하게 되는데, 정렬된 트랜잭션의 뭉치를 모아서 하나의 블록으로 생성하는 구조이다. 

 해결: Kafka에 정렬을 위해 보내는 데이터의 크기를 줄인다. 즉 모든 트랜잭션 데이터를 전송하는게 아니라, 트랜잭션을 대표하는 ID만 보내서 정렬 시킨 후에, orderer의 로컬 메모리에 저장되있던 트랜잭션 데이터를 정렬된 ID에 맞춰서 가지고 와서 블록을 생성한다. Kafka의 부담을 줄인다는 것이다. 

 문제점: Orderer의 경우 SPOF 와 부하분산을 위해서 여러개가 존재 한다. 클라이언트는 여러개의 Orderer 중 하나를 선택해서 트랜잭션을 보낼 수 있으며, Orderer는 받은 트랜잭션을 역시 분산된 zookeeper-Kafka 클러스터의 broker 로 보내게 된다. 여러 Orderer 는 모두 다른 데이터를 가지고 있으며, Kafka는 이 데이터들을 모아서 순서를 만드는데, Orderer 중 하나는 Kafka 로 부터 consuming 하여 committer에게 전달 할 블록을 만들게 된다. 여기서 만약 Kafka 로 부터 Transaction ID 만 받는다면, 원래 Payload 는 어디서 가져와야 할까? 원래 자신이 받은 Payload야 자신의 메모리에 관리 할 수 있겠지만, 다른 Orderer가 받은 것은?? 결국 Orderer 간에 통신 하는 부분이 추가 되야 하며, Kafka가 정렬하는 중간에 Orderer가 죽는다면 SPOF가 발생되고 만다. 모든 Orderer가 공통으로 관리하는 스토리지를 따로 둔다고 하면? 그 자체로 SPOF 이다. 

2. Orderer - Message pipelining

배경지식:  orderer 는 client로 부터 트랜잭션을 받는데, 트랜잭션에 대한 다양한 validation 검사(채널검증,클라이언트 서명검증등) 를 진행하고, Kafka로 넣어준다. 이 검증이 끝난 후에야 다른 트랜잭션에 대한 처리를 한다.

해결:  받은 트랜잭션을 검증 작업을 위한 쓰레드풀에 던져서 동시성으로 처리. 먼저 검증을 끝낸 트랜잭션을 consumer에서 받아서 Kafka에 넣어준다. 전형적인 동시성 처리 파이프라이닝. 

의견:  OK  ( 근데 동시성,멀티쓰레딩이 항상 옳은것은 아니다. [블록체인] TPS 그리고 Disruptor 패턴

3. Peer - Replacing the world state database with a hash table

배경지식Orderer로 부터 받은 블록을 committer는 여러 단계의 검증 행위(1. 패킷정합성 검사 2. client 와 endorser 의 서명 검사 3. rwset 검사)를 수행한 후 최종적으로 Ledger (블록체인, StateDB)에 커밋하게 된다. 이때 rwset 검증을 위한 levelDB 의 i/o 에 많은 부하가 걸리는데...

해결:  
Fabric의 key-value 저장소를 가벼운 in-memory 데이터 구조로 변경. Fabric의 데이터 관리 계층을 가벼운 hash table로 재설계. stateDB 에 저장되는 양은 블록데이터에 비하면 아주 작다..라고 주장... 하지만..

문제점
양에 대해서는 절대 작지 않다. value에 무엇이 저장 될 지 알 수 없으며, 서버가 갑자기 다운이라도 된다면 모두 소실되는데, 그때마다 다른 곳에서 다시 block sync를 받아야 하며 그 부하가 대단하다. 참고로 leveldb 자체도  write에 최적화된 매우 빠른 db이며 가능한 in-memory에 저장을 많이 해두고 있다가, 디스크로 쓰는 구조이다. 

4. Peer- Store blocks using a peer cluster

배경지식블록은 최종적으로 Ledger로 남는데, Ledger 는 최종 상태만 보관하는 StateDB 와 모든 데이터를 append only 로 붙히는 블록체인으로 이루어 진다..(패브릭에서 statedb는 leveldb or couchdb를 이용하고 블록체인은 그냥 file에 쓴다)

해결
Peer가 단독적으로 관리하지 말고 Hadoop MapReduce 나 Spark 를 사용하자. 

문제점
kafka-zookeeper도 관리문제가 있는데, 또 외부시스템을 사용하자고? 게다가 현재 블록데이터를 그냥 bulk file write 하는것에 비해 훨씬 느려지는 것은 당연하다. 문제는 statedb의 디스크 i/o지 저런게 아니다. 

5. Peer - Separate commitment and endorsement 

배경지식Fabric에서 Peer 는 commtment역할을 무조건 하고, 옵션에 따라서 endorsement역할도 한다. 이 두가지 모두 많은 부하를 갖게 되는데, 하나의 서버에서 돌리면 많은 수의 CPU를 동시에 사용하게 되어, 컨텍스트 스위칭 비효율을 낳게 되고 마는데..

해결:  
committer와 endorser를 다른 하드웨어로 분리함.

의견
성능을 위해서라면 당연히 해야 하는 것이다. 네트워킹파워가 엄청난 이 시대에 둘이 공통으로 접근해야 하는 stateDB는 공통 접근 할 수 있는 곳에 따로 놓아도 된다. 컨텍스트 스위칭 문제가 생각보다 훨씬 병목이 된다. 참고로 블록체인 데이터는 committer만 사용한다. (그냥 append only ) 유일한 문제점은 관리자 입장에서 관리 대상이 몇개 더 늘어난 것일 뿐이니..(msp등) 성능이 핵심이라면 트레이드 오프가 당연. 

6. Peer - Parallelize validation 

배경지식Orderer로 부터 받은 블록을 committer는 여러 단계의 검증 행위 (1. 패킷정합성 검사 2. client 와 endorser 의 서명 검사 3. rwset 검사)를 수행 한다고 위에 언급했다. 하나의 블록 안에는 수많은 트랜잭션이 있고, 그 트랜잭션 안에는 역시 수 많은 서명,RWSet이 있을 것이다. 이것을 CPU 하나로 처리 한다면?? 으악...

해결:  
병렬적으로 처리 할 수 있는 것은 병렬로 처리하자.

문제점
조심 해야 하는 요소들이 있다. leveldb의 동작특성을 이해해야 하며 (batch write 같은) Reply attact, Double spending 의 위험성에 노출 될 수 도 있다. 너무 많은 동시 처리는 컨텍스트 스위칭을 일으켜서 오히려 병목이 될 수도 있다. CPU 말고 다른 장치를 사용 해서 서명검증등을 처리 하는 방안을 추가로 강구 해야 할 것이다.    

7. Peer - Cache unmarshaled blocks

배경지식대부분의 분산 시스템에서 객체 이동은 빠질 수 없는 부분이다. 패브릭도 proposal request, proposal response, transaction, block progagation 등 수 많은 객체 이동(복사)가 네트워크를 통해 발생하는데, 이럴때 직렬화,역직렬화가 필수적이다. 근데 직렬화,역직렬화라는 것은 추상성이 높을 수록 복잡해 지게 마련인데, 추상성이 높으면 사용성은 높아진다. 즉 자바를 예로 들면 추상성이 높아서 사용하기 편한 반면, 직렬화,역직렬화에 들어가는 비용은 높아지게 된다. 따라서 자바표준직렬화를 속도가 필요한 부분에서는 사용하지 않으며, 커스터마이징 하게 마련이다. 반대로 C++를 사용하면 "손쉽게" 직렬화,역직렬화에 비용소모를 엄청 줄일 수 있다. 네트워크간의 직렬화 말고, 메모리 내부에서도 작업이 분리되 있으며, 데이터 이동(복사)가 반드시 필요하게 되는데, 이것도 가능한 deep copy를 줄이고, move로 처리해야 하는 부분이다. 논문에서 주목하는 부분은 후자(내부 프로세스에서의 객체 분해/조립 병목) 이다. 

해결:  
패브릭에서의 블록내부구조는 하나의 구조체로 이루어져 있는 것이 아니라, 구조체의 구조체의 구조체의... 이렇게 여러 레이어들의 합인데, 하위호환성 및 유연성을 반영한 설계이다. 역시 모든 것에는 트레이드 오프가 있는 법, 유연성이 증가한 만큼 사용시 부담이 생긴다. committer에서 validation 과정이 여러개라고 위에서 언급했는데 각각의 행위에는 블록구조를 분해/조립 (byte array <-> object)해서 필요한 것만 가져가서 사용하는데, 이럴 경우 분해,재조립에 부하가 간다. 따라서 새로 만들어진 객체(unmarchaled data)를 cyclic buffer 형태의 블록캐시를 만들어두고 재사용하여 복사(분해,조립) 과정을 줄이는 방식이다. 

의견
성능이 제1덕목이라면 유연성/추상화를 줄여야한다. 다만 해당 부분이 전체 시스템에서 주요 병목인가를 잘 살펴봐야 겠다.  raw data로 부터 생성된 객체가 다른 worker들로 이동 될 때 어떻게  부하를 줄이냐는 각 언어별로 달라질 수 있을 것이고, 해당 객체에 대한 재사용 기법 또한 선택의 가짓수가 많은 C++의 경우에서는 특히 깊이있게 고민해야하는 부분일 것이다. 사실 이런 기법은 문장(string building)에 대한 편집을 다룰때 개발 단어를 최대한 캐시해두고, new를 통한 메모리 할당의 감소를 줄여서 대폭적인 성능 향상을 이루는 기법과 일맥 상통하다.   

P.S
이런 커스터마이징 정도로는 하이퍼레저 패브릭이 애시당초 의도한 엔터프라이즈 환경의 일부 인원들만 접근해서 사용하는 환경에서는 의미를 가질 수 있으나, 대규모의 다양한 사용자가 접근 해서 수 많은 비지니스 로직(체인코드)가 만들어지는 환경으로의 범위를 넓히고자 하는 사람들에게는 논문에서 언급되지 않은 다른 병목들도 있기에 사용성에 문제가 발생 할 여지가 있다. 

0 Comments
댓글쓰기 폼