January 25, 2022
해당 글은 이더리움에 대한 기본적 이해를 요구합니다.
스냅샷 은 이더리움 State 위에 있는 가속 데이터 구조로, Account와 Storage를 훨씬 더 빠르게 읽을 수 있게 합니다. O(log N)로 계정에 액세스하는 비용을 O(1) 으로 줄입니다.
스냅샷은 한 블록내에 존재하는 “이더리움 글로벌 상태”에 대한 완전한 복사본입니다. 추상적 구현체는, 모든 account와 storage의 dump입니다. (flat key-value store)
따라서 7-8 단계 깊이의 tire를 조회하는게 아니라, 1단계 조회가 가능합니다. 또한 snapshot을 업데이트하는것에 큰 비용이 들지 않습니다. 즉 계정에 액세스하는 비용을 O(log N)에서 O(1) 으로 줄입니다.
단순히 매 블록 업데이트때마다 스냅샷을 업데이트 하면 안됩니다.
이런 문제점을 극복하기 위해, Geth의 snapshot은 두 엔티티로 구성됩니다.
새 블록이 생성될 때마다 디스크 계층에 직접 병합하지 않고, 오히려 변경사항을 메모리내에 diff 계층으로 만듭니다. 만약 인메모리 diff 레이어가 충분히 쌓였다면, 잴 아래 레이어가 디스크에 푸쉬(=병합)됩니다. 만약 특정 Account의 State를 조회한다면 최상위 diff 레이어에서 시작해서, 찾을때까지 내려갑니다 (즉 디스크 레이어까지 내려갑니다)
인메모리의 diff 계층이 Tree 구조로 조합되기 때문에(Since the in-memory diff layers are assembled into a tree) 128 블록보다 더 낮은 레벨의 reorg는 단순히 diff layer 하나를 선택 후, 거기서부터 다시 시작하면 됩니다.
128블록이 언급되는 이유는 무엇일까? ‘추측’하기로는 128개 블록에 대한 State를 In-memory diff layer로 구성하고 있기 때문이 아닐까요? Geth 코드를 통해 더 명확히 이해 가능할 듯 합니다
(최대) 128개의 Diff 레이어를 조회하는 Map lookup 비용은 증가하지만, 8번의 Disk Read가 필요한 LevelDB에 비해 4~5배 빠릅니다.
이러한 스냅샷 가속 구조는 읽기 복잡성을 약 10배정도 줄입니다.
그렇다면 gasLimit을 10배 늘릴 수 있을까요? 그건 아닙니다. 읽기(=스냅샷)은 10배의 성능을 제공하지만, EVM 실행단의 write 작업은 아닙니다.
다만 저렴한 Read opcode 위주의 DoS 공격에 장기적으로 효과적입니다. 또한 storage call에 효과적입니다. 특히 개인 노드가 아니라, 여러 사용자를 위해 운영되는 엔터프라이즈 레벨 노드에서 10배는 엄청난 성능 향상입니다.
Snap Sync을 알아보기전에, Fast sync의 문제점에 대해 알아보겠습니다.
지금까지 동기화 과정은 크게 두가지 방법이 있었습니다.
fast sync의 “예상치 못한 병목 현상”은 이더리움 데이터 모델로 인해 발생하는 “대기 시간”입니다. 트리의 루트(블록 헤더에 포함된 해시)에서 동기화를 시작해서 모든 노드를 다운로드하는 유일한 방법은 트리의 각 노드를 하나씩 요청하는 것 입니다. 다운로드할 노드가 6억 7500만 개이므로 384개 요청을 일괄 처리하더라도 175만 번 엑세스해야 합니다.
10개의 서빙 피어에 매우 관대대하게 50ms RTT(Round Trip Time, 왕복시간)를 가정하면 Fast Sync는 기본적으로 데이터가 도착할 때까지 150분 이상을 기다립니다.
서빙 피어는 동기화 노드에 요청을 받으면 디스크에서 해당 정보를 검색해야 합니다. 트리 의 노드는 해시로 키가 지정되므로 일괄적으로 저장/검색하는 효과적인 방법이 없으며 각각 고유한 데이터베이스 읽기가 필요합니다. 설상가상으로 Geth에서 사용하는 LevelDB는 데이터를 7단계로 저장하므로 Random 읽기는 많은 파일을 건드릴 것입니다.
각 트리 노드를 개별적으로 요청한다는 것은 실제로 많은 해시를 원격 피어에 업로드하여 서비스를 제공하는 것을 의미합니다. 다운로드할 노드가 6억 7,500만 개이므로 업로드할 해시는 6억 7,500만 개 또는 675 * 32바이트 = 21GB입니다. (전 세계 평균 97Mbps)
요약하면 Fast Sync는 데이터를 기다리며 아무것도 하지 않고 대략 6시간을 소비합니다. (문서에 따르면…)
Snap Sync는 위 3가지 문제점을 모두 해결하게 디자인 되었습니다. 핵심 아이디어는 매우 간단합니다.
Trie의 노드를 각각 다운로드 하는게 아니라, State flat data(= snapshot)의 연속 chunk를 다운로드하고 그 데이터로 부터 Trie를 로컬에서 다시 구성합니다.
스냅 동기화는 패리티의 warp sync
와 상당히 유사하며, 실제로 많은 설계 아이디어를 가져왔습니다. (개선하며)
Snap Sync는 Geth v1.10.0 에서 비활성화 상태입니다.
--syncmode snap
을 통해 스냅동기화를 활성화해도, 당분간 적절한 서빙 노드를 찾지 못할것입니다. 만약 네트워크에 적절한 서빙 노드가 많아졌다고 판단되면 default 기능으로 변경할 것입니다.
그리고 v1.10.14 --syncmode fast
가 삭제됬습니다. fast syncmode로 실행할 경우 오류가 발생합니다. (왜 릴리즈노트에 언급이 없었는지… 🙀)
실행 환경: syncmode=full, gcmode=full
syncmode=snap
을 이용하여 Geth를 동기화geth db export ...
명령어로 동기화된 노드에서 스냅샷 데이터 export 후, geth db import ...
명령어로 import 합니다. 그 후 스냅샷이 rebuilt 됩니다.> eth.syncing
{
currentBlock: 13188285,
highestBlock: 13188389,
knownStates: 0,
pulledStates: 0,
startingBlock: 13186299
}
몇시간 후 동기화 완료
> eth.syncing
false
eth.syncing
은 스냅 동기화에 필요한 정보를 채우지 못합니다 (개선해야합니다)eth.syncing
명령어는, 동기화 완료(synced) 상태라면 eth.syncing
은 false
를 리턴한다.type SyncProgress struct {
StartingBlock uint64 // Block number where sync began
CurrentBlock uint64 // Current block number where sync is at
HighestBlock uint64 // Highest alleged block number in the chain
// "fast sync" fields. These used to be sent by geth, but are no longer used
// since version v1.10.
PulledStates uint64 // Number of state trie entries already downloaded
KnownStates uint64 // Total number of state trie entries known about
// "snap sync" fields.
SyncedAccounts uint64 // Number of accounts downloaded
SyncedAccountBytes uint64 // Number of account trie bytes persisted to disk
SyncedBytecodes uint64 // Number of bytecodes downloaded
SyncedBytecodeBytes uint64 // Number of bytecode bytes downloaded
SyncedStorage uint64 // Number of storage slots downloaded
SyncedStorageBytes uint64 // Number of storage trie bytes persisted to disk
HealedTrienodes uint64 // Number of state trie nodes downloaded
HealedTrienodeBytes uint64 // Number of state trie bytes persisted to disk
HealedBytecodes uint64 // Number of bytecodes downloaded
HealedBytecodeBytes uint64 // Number of bytecodes persisted to disk
HealingTrienodes uint64 // Number of state trie nodes pending
HealingBytecode uint64 // Number of bytecodes pending
}
모든 내용은 요약되었기 때문에, 아래 링크를 통해 자세한 내용을 읽어보는 것을 추천합니다.