메인 콘텐츠로 건너뛰기

목적 및 범위

이 문서는 StableNet에서 상태 액세스를 최적화하는 다층 캐싱 메커니즘과 스냅샷 시스템을 설명합니다.
캐싱 아키텍처는 자주 액세스되는 데이터에 대한 메모리 내 LRU 캐시부터, trie 순회 없이 계정 및 저장소 데이터에 대한 빠른 액세스를 제공하는 상태 스냅샷 시스템까지 확장됩니다.
기본 Merkle Patricia Trie 데이터 구조 및 데이터베이스 인터페이스에 대한 정보는 데이터베이스 레이어 및 Merkle Patricia Trie을 참조하세요.
과거 데이터 관리 및 아카이브 전략은 고대 저장소 및 데이터 라이프사이클을 참조하세요.

StateDB 캐싱 아키텍처

StateDB는 모든 블록체인 상태에 대한 최상위 캐싱 레이어로 동작하며, 트랜잭션 실행 중 반복적이고 비용이 큰 trie 및 데이터베이스 접근을 최소화하기 위해 여러 메모리 내 데이터 구조를 유지합니다.

계정 및 저장소 캐시 맵

StateDB는 블록 실행 중 상태 변화를 추적하기 위해 다음과 같은 캐시 맵을 유지합니다:
Cache MapTypePurpose
stateObjectsmap[common.Address]*stateObject실행 중 수정되는 라이브 계정 객체
accountsmap[common.Hash][]byte커밋을 위한 slim RLP 인코딩 계정 데이터
storagesmap[common.Hash]map[common.Hash][]byte커밋 대상 저장소 슬롯 변경사항
accountsOriginmap[common.Address][]byte변경 전 계정 상태(diff 계산용)
storagesOriginmap[common.Address]map[common.Hash][]byte변경 전 저장소 상태(diff 계산용)
stateObjectsPendingmap[common.Address]struct{}finalise 완료, trie 반영 전 객체
stateObjectsDirtymap[common.Address]struct{}현재 실행에서 수정된 객체
stateObjectsDestructmap[common.Address]*types.StateAccount블록 내에서 selfdestruct된 계정
stateObjects는 현재 블록 실행 동안 유지되는 라이브 상태 객체 캐시입니다.
각 계정은 최초 접근 시 한 번만 로드되며, 이후에는 동일 객체가 재사용됩니다.
계정 로딩 흐름 요약:
  1. stateObjects 캐시 확인
  2. 미존재 시 스냅샷에서 로드 시도
  3. 스냅샷 미사용 또는 미존재 시 trie 조회
  4. stateObject 래퍼 생성
  5. stateObjects에 캐시 후 반환

상태 객체 저장소 캐싱

stateObject는 저장소 슬롯 접근을 위해 3단계 저장소 캐시를 유지합니다. 저장소 단계별 역할:
  • dirtyStorage
    현재 트랜잭션에서 변경된 슬롯. 트랜잭션 종료 시 초기화됩니다.
  • pendingStorage
    트랜잭션이 종료되어 확정되었지만 아직 trie에 커밋되지 않은 변경사항.
  • originStorage
    스냅샷 또는 trie에서 로드된 기준 상태.
이 구조는 다음을 보장합니다:
  • 트랜잭션 단위 롤백 가능
  • 동일 슬롯에 대한 중복 trie 쓰기 방지
  • 변경사항의 정확한 diff 계산

상태 스냅샷 시스템

스냅샷 시스템은 Merkle Patricia Trie를 직접 순회하지 않고도 계정 및 저장소 데이터를 조회할 수 있는 평면(flat) 상태 표현을 제공합니다.
이는 snap 동기화와 일반 블록 실행 성능 모두에 핵심적인 역할을 합니다.

스냅샷 아키텍처

스냅샷 시스템은 다음 세 구성 요소로 이루어집니다:
  1. 디스크 레이어
    특정 블록 높이의 영구 스냅샷으로,
    계정 해시 → 계정 데이터, 저장소 해시 → 저장소 값의 평면 매핑을 보유합니다.
  2. Diff 레이어
    각 블록 단위의 상태 변경사항을 메모리에 체인 형태로 보관합니다.
    변경된 계정과 저장소 슬롯만 저장합니다.
  3. 스냅샷 트리
    상태 루트별 스냅샷을 관리하며 Snapshot(root) 인터페이스를 제공합니다.

스냅샷 기반 상태 액세스

스냅샷이 활성화되면 StateDB는 다음 순서로 상태를 조회합니다:
  • 계정 조회: snap.Account(hash)
  • 저장소 조회: snap.Storage(addrHash, slotHash)
  • 미스 시에만 trie로 폴백
이 방식의 성능적 이점:
  • trie 노드 디코딩 없음
  • 해시 검증 없음
  • 평면 구조 직접 접근
  • 일반적으로 계정 조회 기준 10~100배 속도 향상

Trie 프리페처

Trie 프리페처는 트랜잭션 실행 중 접근 가능성이 높은 trie 노드를 백그라운드에서 선제적으로 로드하여 커밋 지연을 최소화합니다. 작동 흐름:
  1. 블록 실행 시작 시 StartPrefetcher() 호출
  2. finalise() 단계에서 수정된 저장소 슬롯 정보 수집
  3. 워커 고루틴이 해당 경로의 trie 노드를 비동기 로딩
  4. IntermediateRoot() 또는 커밋 시 캐시된 trie 재사용
  5. 블록 종료 시 StopPrefetcher()로 정리
프리페처는 실행과 병렬로 동작하며, 트랜잭션 처리 지연을 증가시키지 않습니다.

저널 및 스냅샷 롤백

StateDB는 저널 기반 변경 추적을 통해 트랜잭션 실행 중 언제든 이전 상태로 롤백할 수 있습니다. 각 상태 변경은 journalEntry로 기록되며, 대표적인 항목은 다음과 같습니다:
  • 잔액 변경
  • nonce 변경
  • 코드 변경
  • 저장소 변경
  • selfdestruct
  • 계정 생성
Snapshot() 호출 시 현재 저널 길이가 저장되고, RevertToSnapshot() 호출 시 저널을 역순으로 재생하여 상태를 복원합니다. 이 메커니즘은 다음에 필수적입니다:
  • EVM REVERT
  • Out-of-gas 처리
  • 예외 발생 시 상태 복구
  • 추측 실행

상태 커밋 및 캐시 관리

상태 커밋은 다음 3단계로 진행됩니다.

단계 1: Finalise

  • dirty 객체 정리
  • selfdestruct 처리
  • dirtyStorage → pendingStorage 이동
  • 저널 초기화

단계 2: IntermediateRoot

  • 모든 pending 객체의 저장소 trie 해싱
  • 계정 trie 업데이트
  • 중간 상태 루트 계산

단계 3: Commit

  • 모든 저장소 trie 커밋
  • 계정 trie 커밋
  • 변경된 노드에 대한 NodeSet 생성
  • triedb.Update()로 일괄 기록

빠른 저장소 삭제

대형 저장소 trie 삭제 시 전체 trie 순회는 비효율적입니다.
StateDB는 스냅샷을 활용한 빠른 삭제 경로를 제공합니다.
작동 방식:
  1. 스냅샷 기반 저장소 슬롯 평면 반복
  2. 메모리 내 StackTrie 구성
  3. 각 노드에 대한 삭제 마커 생성
  4. 크기 제한 초과 시 기존 느린 경로로 폴백
  5. 삭제 마커를 포함한 NodeSet 반환
이 방식은 디스크 접근 없이 순수 메모리 연산으로 동작합니다.

StateDB 복사 의미론

StateDB.Copy()는 병렬 실행 및 추측 상태 생성을 위해 사용됩니다. 공유(읽기 전용):
  • 데이터베이스 인터페이스
  • 스냅샷 트리
  • 기본 trie
딥 복사(쓰기 가능):
  • 상태 객체 맵
  • 변경 추적 맵
  • journal
  • logs, preimages
  • accessList, transientStorage
이를 통해:
  • 병렬 트랜잭션 실행 가능
  • 메모리 중복 최소화
  • 상태 간 완전한 격리 보장

성능 메트릭 및 계측

StateDB는 상태 처리 전반에 대해 상세한 메트릭을 수집합니다. 측정 항목 예:
  • 계정/저장소 읽기 시간
  • trie 해싱 시간
  • 커밋 시간
  • 스냅샷 접근 시간
  • 업데이트/삭제 개수
이 메트릭은 다음에 사용됩니다:
  • 병목 지점 식별
  • 캐시 적중률 분석
  • 스냅샷 효과 검증
  • 성능 회귀 탐지

정리 및 캐시 제거

스냅샷 기반 정리

  • 오래된 diff 레이어 병합
  • 디스크 레이어 최신화
  • 불필요한 trie 노드 제거 가능

캐시 제거 전략

  • trie 노드 LRU 캐시
  • clean / dirty 캐시 분리
  • dirty 노드는 커밋 후 제거

메모리 관리

  • 트랜잭션 종료 시 저널 정리
  • pending 객체 초기화
  • 프리페처 종료
  • StateDB 복사 시 구조 공유
이러한 메커니즘은 장기 실행 환경에서도 메모리 사용량이 제한되도록 보장합니다.