March 16, 2026

What is Blockchain Indexer?

들어가며

인덱서란 블록체인 노드 데이터를 구성하는 핵심 요소인 블록, 트랜잭션, 로그를 추출하고 오프체인에 동기화하는 역할을 한다.

이 역할이 필요한 이유는 기존의 블록체인 시스템이 제공하는 RPC가 블록 번호, 트랜잭션, 리시트 등의 단순 데이터를 조회하는 것에 유효하지만 누가 언제 얼마나 토큰을 전송했고, 제일 많은 NFT를 보유했는지 등의 복잡한 쿼리에는 전혀 유용하지 않았기 때문이다.

그래서 많은 기업에서 DBMS의 SQL 같이 복잡한 쿼리를 실행하고 더 빨리 접근할 수 있는 오프체인 환경에 온체인 데이터를 동기화하기 시작했다.


인덱서의 핵심 과제

인덱서는 시스템에 추출한 데이터를 변환하고 저장하는 관점에서 우리가 익히 아는 ETL 프로그램을 의미한다.

따라서 데이터를 정확하게 주워담을 수 있으면서 뛰어난 처리 능력과 확장성을 갖춘 파이프라인을 구축할 필요가 있다.

이 과정에서 몇 가지 문제에 직면할 수 있는데, 대표적으로 아래와 같은 문제를 해결해야 한다.

  • 사용자가 정말 필요로 하는 데이터는 무엇일까? 그게 정해졌다면 인덱서는 어디까지의 데이터를 필요로 할까?
  • 데이터를 추출하기 위해 Infura, Alchemy 등 외부 API를 사용할까? 아니면 자체 노드를 구축할 필요가 있을까?
  • 블록의 과거 데이터와 미래 데이터를 각각 어떻게 처리할까?
  • 대용량 데이터를 추출하고 저장하는 전략은?
  • 아직 동기화 되지 않은 노드에서 데이터를 추출(Silent Failure 문제)하려 할 때 인덱서는 어떻게 반응해야 할까?
  • 블록체인의 재구성(Reorg)이 발생했을 때 어떻게 대응할까?
  • 얼마의 데이터를 얼마나 빠른 속도로 처리할 수 있을까?

물론 해당 문제들은 모든 블록체인 인덱서에서 공통적으로 발생하는 문제는 아니다.

비교적 최근에 연구된 Linera 같은 경우에는 마이크로체인 아키텍처를 사용하기 때문에 체인별로 인덱서를 구축하려면 창의적인 기법이 더 필요할거 같다.

또한 Solana와 Monad 같은 고성능 블록체인에선 재구성이 발생되는게 허용되는 구조거나 정상적인 경우엔 발생하지 않을 확률이 높고, 블록이 생성되는 속도가 Ethereum이나 Bitcoin에 비하면 워낙 빠른 속도기 때문에 병목이 쉽게 발생할 수 있다.

그래서 이에 대응할 수 있는 고성능 인덱서가 별도로 요구되기도 한다. 이 경우에는 내부적으로 이벤트 기반 처리와 버퍼링/비동기 처리를 조합해 동기화 처리할 수 있을거 같다.

본 글에서는 인덱서의 기원에 가까운 파이프라인 구축을 목표로 하기 때문에 전통적인 Ethereum 기반 블록체인 인덱서에 한해 설명하려 한다.

데이터 모델링

블록체인 데이터의 핵심 구성 요소는 블록(Block), 트랜잭션(Transaction), 그리고 로그(Log)이다. 인덱서를 설계할 때 이 세 가지 데이터를 모두 저장할 수도 있고, 비즈니스 목적에 따라 일부 데이터만 선택적으로 저장할 수도 있다.

Ethereum 메인넷의 모든 상태를 추적하거나 체인 분석을 수행하는 특수한 경우가 아니라면, 비즈니스 목적에 필요한 데이터만 추출해 저장하는 것이 중요하다. 모든 데이터를 그대로 저장하면 저장 공간과 처리 비용이 크게 증가할 뿐 아니라 시스템 복잡도도 높아질 수 있다.

따라서 인덱서를 구축하기 전에 우리가 실제로 저장해야 하는 데이터가 무엇인지 명확히 정의해야 한다.

이 글에서는 모든 과거 블록부터 앞으로 생성될 블록까지를 대상으로, 특정 ERC-20 인터페이스를 사용하는 커스텀 토큰 컨트랙트의 트랜잭션을 추적하고 Transfer 이벤트를 감시하는 인덱서를 구축한다고 가정한다.

먼저 블록 데이터 모델을 정의해보자.

블록은 트랜잭션들의 집합이며, 이전 블록과 연결되어 체인을 구성한다. 블록은 체인의 무결성을 유지하는 핵심 구조이며 트랜잭션 실행 결과를 시간 순서대로 기록하는 역할을 한다.

ERC-20 이벤트 인덱싱을 위해 필요한 최소한의 블록 정보는 다음과 같다.

  • chain id
  • block number
  • block hash
  • parent block hash
  • timestamp
  • gas used
  • gas limit
  • miner

블록은 여러 개의 트랜잭션을 포함한다. 따라서 다음 단계로 트랜잭션 모델을 정의해야 한다.

트랜잭션은 사용자가 블록체인에 제출한 실행 요청이며, 특정 블록에 포함된다. 트랜잭션에는 누가 트랜잭션을 보냈는지, 어떤 컨트랙트를 호출했는지, 실행을 위해 어떤 데이터를 전달했는지 등의 정보가 포함된다.

트랜잭션 모델에는 다음과 같은 정보가 필요하다.

  • chain id
  • block hash
  • block number
  • transaction hash
  • transaction index
  • sender
  • receiver
  • value (native ETH value)
  • input data
  • gas price
  • gas used

여기서 주의할 점은 ERC-20 토큰 전송량은 트랜잭션 객체가 아니라 이벤트 로그에서 얻어진다는 것이다. 대부분의 ERC-20 토큰 이동은 컨트랙트 실행 과정에서 Transfer 이벤트를 발생시키며, 실제 토큰 이동 정보는 로그 데이터에 포함된다.

마지막으로 로그 모델을 정의한다.

로그는 트랜잭션 실행 과정에서 발생하는 이벤트 데이터를 의미한다. 스마트 컨트랙트는 특정 로직 실행 시 이벤트를 발생시키며, 이 이벤트는 검색 효율을 위해 인덱싱된 형태로 블록체인에 기록된다.

ERC-20 표준에서는 토큰 전송이 발생할 때 다음과 같은 이벤트가 발생한다.

plaintext
Transfer(address indexed from, address indexed to, uint256 value)

이 이벤트는 다음과 같은 구조를 가진다.

  • topic0 : 이벤트 시그니처 해시
  • topic1 : from address
  • topic2 : to address
  • data : 전송된 토큰 수량

또한 이벤트가 발생한 컨트랙트 주소는 로그의 address 필드에 포함된다. 이를 기반으로 로그 모델을 정의하면 다음과 같다.

  • chain id
  • block hash
  • block number
  • transaction hash
  • log index
  • contract address (token contract)
  • topic0 (event signature hash)
  • topic1 (from)
  • topic2 (to)
  • data (token value)

로그 데이터는 ABI 디코딩을 통해 사람이 이해할 수 있는 형태로 변환할 수 있다. 예를 들어 ERC-20 Transfer 이벤트의 경우 다음과 같은 구조로 변환된다.

  • token address
  • from
  • to
  • value
  • timestamp (block timestamp)

이러한 데이터를 기반으로 서비스 조회를 위한 Transfer 이벤트 projection을 별도로 저장할 수도 있다. 예를 들어 토큰 전송 내역 조회 API는 raw 로그 대신 다음과 같은 형태의 데이터 테이블을 사용하는 것이 일반적이다.

  • token address
  • from address
  • to address
  • value
  • block number
  • transaction hash
  • timestamp

이처럼 raw 로그 데이터와 서비스 조회용 데이터 모델을 분리하면, 이벤트 디코딩 로직이 변경되더라도 raw 로그를 기반으로 데이터를 다시 생성할 수 있다는 장점이 있다.

대용량 데이터 추출 전략

인덱서가 노드에서 추출해야할 데이터는 두 가지가 있다. 하나는 과거에 생성된 모든 블록이며, 다른 하나는 미래에 생성될 블록이다. 편의상 각각 History와 Future라 부르겠다.

History의 경우에는 이미 생성된 데이터기 때문에 하나씩 불러와 오프체인에 동기화시키는 전략은 굉장히 비효율적인 선택이다. 최악의 경우에 해당 프로세스가 실행 중일 때 다른 프로세스가 중단되거나 빠른 동기화를 목적으로 외부 API를 짧은 주기로 호출한다면 최신 데이터를 동기화할 틈도 없이 Rate Limit에 도달해 전체 인덱서 프로그램이 동작하지 않을 수 있다.

따라서 History를 추출하기 위해 일정 간격으로 배치 데이터를 추출하는 전략을 사용할 필요가 있다. 가능하다면 해당 프로세스를 수행하는 서버를 몇 대씩 두어 병렬 처리를 시도해 추출 속도를 더 높일 수도 있다.

Future는 아직 Canonical 체인에 포함되지 않았거나 포함된 블록이 될 수 있다. Ethereum의 블록 생성 주기에 맞추거나 더 적은 주기로 새로 생성된 블록을 실시간으로 탐지하고 이를 바로 저장하면 되는 단순한 구조기 때문에 복잡한 처리 방식이 존재하지는 않는다.

다만, 체인이 재구성되면 앞선 포크가 발생한 블록이 어딘지 탐색하고 재구성된 블록으로 업데이트해야 하기 때문에 이를 위한 Reorg-Aware 로직이 별도로 필요할 수 있다.

재구성을 탐지하는 가장 간단한 방법은 인덱서가 현재 동기화된(DB에 위치한) 블록 번호와 노드가 동기화한 블록의 부모 블록 번호를 대조해 보는 것다. 최근에 Reth 같은 클라이언트 노드는 인덱서를 대신해서 재구성을 탐지하고 알림을 주기도 한다.

저장소 최적화

Ethereum의 데이터를 동기화 한다는 것은 필연적으로 많은 저장 공간을 필요로 한다. 알려진 바에 의하면 전체 데이터를 모두 동기화 하려할 때 필요한 저장 공간은 아카이브 노드를 기준으로 적어도 10TB 이상을 필요로 한다. 더군다나 이 데이터는 실시간으로 생성된다.

10TB 되는 데이터를 PostgreSQL 같은 RDBMS에 저장할 경우에는 인덱스 테이블이 함께 증가할 가능성을 고려해 그 이상의 저장 공간이 필요할 수도 있고, 데이터가 많아질 수록 탐색과 동시 처리 비용이 증가할 수 있다.

저장소문제를 해결하는 경험이 부족한 관계로 업계에서 알려진 Best Practice와 주요 Conference 발표에서 사용된 다양한 기법들을 리서치 해봤다.

공통적으로 인덱서 파이프라인 중단 없이도 새 요구사항을 반영할 수 있어야 하기 때문에 마이크로서비스 도입으로 인덱싱 로직의 여러 부분(추출, 변환, 그외 별도 로직 등)을 분리하는 양상을 보였다.

또한 전체 트랜잭션을 파싱하는 것보단 Log에서 필요한 부분만 선택해 저장 공간을 낭비하지 않고도 서비스에 필요한 자원을 사용자에게 제공하려 했다. (물론 Envio처럼 범용적으로 설계된 사례도 있었지만 이 역시도 필요한 이벤트만 처리함)

아래는 리서치를 통해 발견한 저장소 성능 최적화를 위해 활용할 수 있는 기술들을 나열했다.

  • 우선 인덱스 저장 공간을 확보하기 위해 필요한 부분에만 인덱스를 건다.
  • 데이터 압축 기술과 테이블 파티셔닝 기술이 필요하다. 파티셔닝을 통해 큰 테이블을 논리적인 하위 테이블로 분할해 디스크 I/O 병목 현상을 줄인다.
  • 대규모 데이터를 빠르게 처리하기 위해선 입력 데이터가 압축되어 있고 구조화되어 있어야 유리한다. 추출된 데이터는 Parquet 형태로 변환해 별도 저장소에 압축 백업을 해둔다. 이러면 나중에 History를 재인덱싱 하려할 때 노드를 거치지 않아도 되므로 더 빠른 속도와 낮은 비용으로 과거 블록을 동기화할 수 있다. 이를 위해 일부 프로젝트에선 Cryo를 사용하기도 한다.
  • Ethereum 데이터처럼 블록 번호에 따라 순차적으로 쌓이는 데이터는 BRIN 인덱스가 더 효과적이다. BRIN을 사용하면 B-tree 대비 인덱스 크기를 수백 배 이상 줄일 수 있고, BRIN이 데이터가 블록 번호나 시간순으로 물리적으로 정렬되어 저장되는 특성을 이용하기 때문에 수십억 건의 트랜잭션 데이터가 쌓여도 인덱스 오버헤드를 최소화하면서 특정 블록 구간을 빠르게 스캔할 수 있게 해준다.
  • TimescaleDB 도입을 고려한다. 블록 데이터는 시간 순서로 쌓이는 Historical 성격을 갖기 때문에 시간 기반으로 파티셔닝을 하면 자연스럽게 오래된 데이터를 압축해 저장 공간을 확보하면서 조회도 가능하게 한다.
  • 데이터를 삽입하기 위해 INSERT를 사용하는 것은 컴퓨팅 자원 낭비를 초래할 수 있으니, Binary Copy를 사용해 삽입 성능을 비약적으로 상승시킨다.
  • CREVERSE의 CTO "PanJ"는 Apache Beam을 도입해 수십억 개의 레코드를 동시 분산 처리하는 파이프라인을 구축했다.
  • Advisory Lock을 활용하면 인메모리 방식의 가벼운 자금을 통해 고가용성 구성시 여러 인덱서 중 하나만 활성화되도록 제어할 수 있다.
  • AWS의 시니어 블록체인 아키텍트인 "Cristoph Niemann"은 Reth의 ExEx 플러그인을 활용해 데이터 추출부를 인덱서에서 분리해 Push 모델을 만들었고 기존 RPC 방식의 Pull 모델 한계를 극복했다. 이후에 핵심 데이터별로 독립 변환을 수행하기 위해 Kafka를 도입했으며, 8~9TB 정도의 막대한 데이터를 Kafka에 두는 것은 장기적으로 비용 부담이 컸기 때문에 Data Firehorse를 도입해 Kafka 데이터를 S3로 전송하는 보조 저장체계를 두었다.

마치며…

이를 통해 인덱싱 성능을 크게 향상하기 위해선 데이터베이스 내부 작동 원리를 이해하고 적절한 기능을 선택해 적용하는 것이 중요하다고 느꼈다.

또한 여러 언어와 프레임워크를 동시에 숙달해야 하는 것은 고되지만 데이터 처리 효율성과 시스템 유지보수성 면에서 좋다고 생각했다.

물론 리서치한 방법들이 지금부터 구현할 나만의 인덱서에 모두 필요한 내용은 아니다. 각 방법의 장/단점, 다른 대안들과 트레이드오프에 대한 내용은 직접 시스템을 구축하며 추후 서술하도록 하겠다.