클레이튼맛 Geth의 데이터베이스 얕게 훑어보기

시리즈

기본

  1. 이더리움 상태(State)란 무엇일까?

Geth 동기화

  1. Geth는 어떻게 동기화(Sync)할까?
  2. Geth의 Snapshot과 Snap Sync

Geth DB

해당 시리즈는 Geth DB 시리즈는 Klaytn Dev Ambassador Core Development Project 과정 학습 내용을 공유하기 위한 목적으로 작성하였습니다.

  1. 클레이튼맛 Geth의 데이터베이스 얕게 훑어보기
  2. 클레이튼맛 Geth의 데이터 살펴보기

들어가며

이더리움(Geth)은 Key-Value DB를 사용한다. 그리고 이더리움을 포크하여 개발한 Klaytn, BSC등의 클라이언트는 동일한 데이터 구조를 가진다.

newDatabase

Klaytn에서 지원하는 주요 Key-Value DB는 위 이미지와 같다. 그 중 LevelDB를 사용한다는 말을 가장 많이 들었을 것 같다.

leveldb

leveldb는 위와 같은 매우 간단한 인터페이스를 가지고 있다.

필자는 지금까지 어떻게 아래와 같은 복잡한 데이터 구조를 Key-Value 를 통해 구현했는지 감이 잡히지 않았다.

explorer

klaytnscope

코드 속으로

해당 글은 Klaytn Core의 소스코드를 통해 어떻게 실제로 데이터를 저장하고 관리하는지를 얕게 훑어본다.

참고로 Klaytn은 Geth Fork로 부터 많은 시간이 흘렀기 때문에 현재는 일부 다른 구조를 가지고 있지만 해당 글에서는 다루지 않는다.

모든 소스코드를 분석할 수는 없기에 db_manager.go 를 중심으로 살펴본다.


db_manager_structure

기본적으로 알고 있으면 좋은 구조를 시각화했다. 최종적으로 Key Value DB를 통해 Disk Read/Write를 한다

Read 구조

ReadCanonicalHash

주석을 통해 쉽게 이해 가능하다. 위와 같은 패턴이 대부분의 함수에서 반복된다.

Key는 어떻게 만들어질까?

headerHashKey

생각했던것보다 단순한 것을 확인 가능하다

dbEntry 선택이란?

Geth는 데이터의 종류에 따라 실제 물리적으로 저장하는 디렉토리의 위치가 다르다.

dbentrydir

CanonicalHash는 /header에 저장된다.

chaindata

참고로 각 dbEntry는 아래와 같은 방법으로 초기화된다.

singleDatabaseDBManager

levelDB 하나만 사용하기로 설정한다면, 각 dbEntry가 모두 levelDB로 초기화된다.

Write 구조

WriteCanonicalHash

결국 아래와 같은 데이터를 확인 가능하다. (Key-Value)

[0d9348243d7357c491e6a61f4b1305e77dc6acacdb8cc708e662f6a9bab6ca02, f8518080808080a018e3b46e84b35270116303fb2a33c853861d45d99da2d87117c2136f7edbd0b980a0717aef38e7ba4a0ae477856a6e7f6ba8d4ee764c57908e6f22643a558db737ff808080808080808080]
[18e3b46e84b35270116303fb2a33c853861d45d99da2d87117c2136f7edbd0b9, f871a036c093a349d905ad74b68851304d5dc5f111fbab2c24c4b4d02e96d2fc0727fdb84ef84c80880de0b6b3a7640000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470]
[337e249c268401079fc728c58142710845805285dbc90e7c71bb1b79b9d7a745, f872a120761d5c42184a02cc64585ed2ff339fc39a907e82731d70313c83d2212b2da36bb84ef84c80888ac7230489e80000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470]
[446174616261736556657273696f6e, ]
...

출처: sigmoid 님의 Level DB를 까보자

ReadBlock, WriteBlock

ReadBlock

위와 같은 패턴이 반복되고 있다

WriteBlock

Write도 동일한 패턴이다

RLPEncoding

WriteBody

Body와 같은 데이터는 그 유명한 ‘RLP’ 인코딩을 사용해 저장

ReadBody

읽어올때도 RLP 디코딩을 사용한다

batch

batch

연속적인 Write, Update 작업이 발생할때는 Batch를 사용한다. (levelDB, RocksDB 등 모두에 구현되어 있다)

Flag 값 저장

WritePruningEnabled

단순한 Flag도 위와 같이 저장하기도 한다

InMigration

InMigration

읽을때는 역으로 Old, New 각각 DB에서 조회한다.

Migration은 Klaytn에만 존재하는 기능이다.

State Migration은 Klaytn 블록체인 노드에서 특정 시점을 기준으로 과거 State Trie를 제거하고 새로운 State Trie만을 노드 스토리지에 저장함으로써 노드 스토리지 사용량을 최대 75% 절약하는 기능 - Klaytn v1.5.0 State Migration: 노드 스토리지 절약하기

결론

결국 Geth의 데이터는 목적에 따라 나눠진 디렉토리 구조를 가지며, 가장 하위 레이어는 아래와 같은 구조의 연속이다.

[key, value]

마치며

chat1

팀 내부 채팅 中

Geth와 같은 방대한 양의 코드를 읽는것은 익숙치 않은 작업이라 인지부하를 많이 느꼈습니다. 학습 중 프로그래머의 뇌라는 책에서 많은 도움을 얻었고 아래와 같은 방법이 큰 도움된다는것을 느꼈습니다.

  • 텍스트를 하향식으로 내려가면서 읽지 말고, 함수의 콜스택을 따라 읽자.
  • 시각화, 인쇄, 하이라이팅, 요약, 메모 등의 전략을 사용하자.

IMG_6521

다만 그럼에도 아직까지 ‘표면적 지식’에서 ‘계획 지식’까지 효과적으로 넘어가는 방법은 잘 모르겠습니다. 이런 경험을 반복하다 보면 더 발전할 것이라고 믿습니다.

참고


Written by@Juna
I love Node.js