
Solana Geyser Plugins: 빛의 속도로 데이터를 스트리밍하세요
이 글에서 다루는 내용
Geyser Plugins는 계정, 슬롯, 블록, 트랜잭션 데이터를 외부 데이터 저장소로 전송하도록 설계된 모듈형 구성 요소입니다. 이를 통해 개발자는 검증인의 RPC(Remote Procedural Call) 부하를 줄일 수 있습니다. Geyser Plugins는 데이터 스트리밍 및 처리 요구 사항을 맞춤화하려는 개발자에게 유연한 솔루션을 제공합니다.
이 글에서는 Solana Geyser Plugins의 세부 구조를 살펴봅니다. 먼저 데이터 복제와 부하 관리를 위해 제안되었지만 결국 Geyser Plugins로 대체된 AccountsDB 복제본을 알아봅니다.
그런 다음 Geyser Plugins의 정의와 작동 방식, Plugin Interface를 통한 구조를 자세히 살펴봅니다.
이어서 일반적으로 사용되는 Geyser Plugins를 알아보고 직접 만드는 복잡한 과정을 안내합니다. 마지막으로 Helius가 Solana 데이터 스트리밍을 간소화하는 방법을 설명합니다.
AccountsDB 복제본: 폐기된 데이터 복제 및 RPC 부하 처리 방식
Solana는 높은 RPC 부하와 데이터 복제 문제를 해결하기 위해 여러 방식을 검토했습니다. 그중 유망했던 방식은 AccountsDB 복제본이었습니다. 이 복제본은 계정 스캔 요청을 주 검증인에서 AccountsDB 복제본으로 분산하도록 설계되었습니다. 가능성은 있었지만 시스템이 본질적으로 복잡했고, 주 검증인과 복제본을 동기화하려면 새로운 서비스 집합이 필요했습니다. 결국 이 제안은 Geyser Plugin System을 채택하면서 폐기되었습니다. Geyser Plugin System은 검증인 클라이언트가 더 간단하게 지원할 수 있고, 개발자가 애플리케이션을 구현할 때 더 높은 유연성을 제공하는 솔루션입니다.
그렇다면 Solana Geyser Plugins는 정확히 무엇일까요?
Solana Geyser Plugins란 무엇인가요?
Solana Geyser Plugins는 Solana 데이터에 짧은 지연 시간으로 접근할 수 있게 하며, 검증인에 RPC 호출을 보내야 하는 애플리케이션을 대체할 수 있습니다. 예를 들어 검증인이 짧은 시간에 수많은 getProgramAccounts 호출을 처리해야 한다면, 과도한 트래픽으로 인해 네트워크를 따라가지 못할 수 있습니다.
Geyser Plugins는 계정, 블록, 슬롯, 트랜잭션 정보를 관계형 데이터베이스, NoSQL 데이터베이스, Kafka 같은 외부 데이터 저장소로 전달해 이 문제를 해결합니다.
이렇게 데이터를 전달하면 외부 저장소에서 데이터를 가져오는 RPC 서비스가 캐싱과 인덱싱처럼 더 유연하고 목적에 맞는 최적화를 제공할 수 있습니다.
Geyser Plugins는 Solana와 외부 데이터 저장소 솔루션을 연결합니다. 개발자는 데이터 관리 작업의 상당 부분을 검증인에서 분리할 수 있어 성능을 높이고 잠재적인 병목 현상의 위험을 줄일 수 있습니다.
Geyser Plugins는 RPC 트래픽 규모와 관계없이 검증인이 네트워크와 동기화된 상태를 유지하도록 합니다.
Geyser Plugin Interface
개발자는 Solana Geyser Plugin Interface를 사용해 Geyser Plugins를 구축할 수 있습니다. 이 인터페이스는 계정, 트랜잭션, 슬롯, 블록 메타데이터, 엔트리에 대한 접근을 제공합니다. solana-geyser-plugin-interface crate에 선언되어 있으며 GeyserPlugin trait으로 정의됩니다.
이 trait는 새 데이터가 생성되거나 기존 데이터가 업데이트될 때마다 호출되는 메서드를 정의하며, 각 메서드에는 update_ 접두사가 붙습니다. Geyser Plugins는 로드 및 언로드 과정에서의 동작도 지정해야 합니다. 이 trait는 원하는 플러그인 동작에 따라 효율적으로 데이터를 스트리밍하기 위해 Geyser Plugin이 구현해야 할 핵심 메서드를 설명합니다.
소스 코드
pub trait GeyserPlugin:Any +Send +Sync +Debug {
// Required method
fn name(&self) -> &'static str;
// Provided methods
fn on_load(&mut self, _config_file: &str) ->Result<()> { ... }
fn on_unload(&mut self) { ... }
fn update_account(
&self,
account:ReplicaAccountInfoVersions<'_>,
slot: Slot,
is_startup:bool
) ->Result<()> { ... }
fn notify_end_of_startup(&self) ->Result<()> { ... }
fn update_slot_status(
&self,
slot: Slot,
parent:Option,
status:SlotStatus
) ->Result<()> { ... }
fn notify_transaction(
&self,
transaction:ReplicaTransactionInfoVersions<'_>,
slot: Slot
) ->Result<()> { ... }
fn notify_entry(&self, entry:ReplicaEntryInfoVersions<'_>) ->Result<()> { ... }
fn notify_block_metadata(
&self,
blockinfo:ReplicaBlockInfoVersions<'_>
) ->Result<()> { ... }
fn account_data_notifications_enabled(&self) ->bool { ... }
fn transaction_notifications_enabled(&self) ->bool { ... }
fn entry_notifications_enabled(&self) ->bool { ... }
}Trait 선언
GeyserPlugin trait는 Solana Geyser Plugin 생태계의 모든 플러그인을 위한 기본 인터페이스입니다. Rust 표준 라이브러리의 Any, Send, Sync, Debug trait bound를 가진 public trait로 선언됩니다. 각 trait bound는 다음과 같습니다.
Any는 타입 리플렉션을 지원해 구체적인 타입으로 다운캐스팅할 수 있게 합니다Send는 이 trait를 구현하는 타입의 소유권을 스레드 간에 전달할 수 있음을 나타냅니다Sync는 이 trait를 구현하는 타입의 참조를 스레드 간에 공유할 수 있음을 의미합니다Debug는 출력용으로 타입을 포맷할 수 있게 하며, 특히 디버깅에 사용됩니다
Any와 Debug는 여기서 크게 중요하지 않습니다.
정말 중요한 점은 프로그램을 스레드 안전하게 만들기 위해 GeyserPlugin에 Send와 Sync가 필요하다는 것입니다.
필수 메서드
fn name(&self) -> &'static str;name 메서드는 GeyserPlugin를 구현하는 모든 타입에 필요합니다. 이 메서드는 Geyser Plugin의 식별자 역할을 합니다. Geyser Plugin의 이름을 나타내는 정적 문자열 슬라이스를 반환합니다.
on_load와 on_unload를 제외한 이 메서드와 모든 다른 메서드가 &mut self 대신 &self를 사용하는 것은 Solana 1.16 업데이트에서 새롭게 도입되었습니다. Geyser Plugin을 Read-Write Lock으로 감싸고 함수를 호출할 때마다 쓰기 잠금을 획득할 필요가 없어져 성능이 크게 향상됩니다.
제공되는 메서드
이 trait에는 기본 구현이 포함된 여러 메서드가 있으며, GeyserPlugin 구현에서 이를 재정의할 수 있습니다.
fn on_load(&mut self, _config_file: &str) ->Result<()> { ... }on_load 메서드는 시스템이 플러그인을 로드할 때 호출되는 콜백으로, 플러그인에 필요한 초기화 작업에 사용됩니다. 구성 파일 경로를 나타내는 string의 참조를 받습니다. 구성은 JSON5 형식이어야 하며, 이 인터페이스를 구현하는 공유 라이브러리의 전체 경로를 나타내는 libpath 필드를 포함해야 합니다.
fn on_unload(&mut self) { ... }on_unload 메서드는 시스템이 플러그인을 언로드하기 전에 정리 작업을 수행하도록 호출되는 콜백입니다.
fn update_account(
&self,
account:ReplicaAccountInfoVersions<'_>,
slot: Slot,
is_startup:bool
) ->Result<()> { ... }update_account 메서드는 처리됨 확인 수준에서 계정이 업데이트될 때 호출되며, 한 슬롯 내에서 여러 번 발생할 수 있습니다. 표준 체인에 커밋된 계정 업데이트를 가져오려면 확인된 슬롯을 추적하는 것이 중요합니다.
ReplicaAccountInfoVersions struct에는 스트리밍된 계정의 메타데이터와 데이터가 포함됩니다.
slot 매개변수는 계정이 업데이트되는 슬롯을 가리킵니다.
is_startup가 true이면 검증인이 시작될 때 스냅샷에서 계정을 로드했다는 의미입니다. is_startup가 false이면 트랜잭션 처리 중에 계정이 업데이트됩니다.
fn notify_end_of_startup(&self) ->Result<()> { ... }notify_end_of_startup 메서드는 시작 단계가 끝났음을 알리기 위해 호출됩니다. 검증인이 스냅샷에서 계정 데이터베이스를 복원하고 모든 계정이 그에 맞게 업데이트되면 호출됩니다.
fn update_slot_status(
&self,
slot: Slot,
parent:Option,
status:SlotStatus
) ->Result<()> { ... }update_slot_status 메서드는 슬롯 상태가 업데이트될 때 호출됩니다. Slot, 부모 슬롯을 위한 Option<u64>, SlotStatus enum 인스턴스를 받습니다.
SlotStatus는 Solana 슬롯의 세 가지 상태를 정의합니다.
Processed- 노드가 처리한 가장 높은 슬롯입니다. 아직 확인되거나 확정되지 않았지만, 검증인이 표준 체인이 될 가능성이 가장 높다고 판단하는 체인의 일부입니다Confirmed- 안전하며 체인의 일부로 간주될 만큼 충분한 투표를 받은 슬롯입니다. Solana 검증인의 압도적 다수가 이 슬롯을 지지합니다Rooted- 이제 블록체인의 영구적인 일부가 된 슬롯입니다. 체인의 다른 모든 버전이나 포크는 이 슬롯을 기반으로 구축되어야 합니다. 즉, 네트워크의 모든 분기는 이 블록에서 파생됩니다
fn notify_transaction(
&self,
transaction:ReplicaTransactionInfoVersions<'_>,
slot: Slot
) ->Result<()> { ... }notify_transaction 메서드는 슬롯에서 트랜잭션이 처리될 때 호출되어 플러그인에 트랜잭션 세부 정보를 전달합니다.
ReplicaTransactionInfoVersions는 ReplicaTransactionInfo를 처리하는 enum 래퍼입니다. RepicaTransactionInfo의 구조가 변경되면 새 버전을 위한 enum 엔트리가 추가됩니다. 그러면 플러그인 구현은 새 enum 엔트리를 수용해 변경 사항을 처리해야 합니다. 현재 enum는 두 가지 변형을 래핑합니다.
V0_0_1(&'a ReplicaTransactionInfo<'a>)V0_0_2(&'a ReplicaTransactionInfoV2<'a>)
pub struct ReplicaTransactionInfo<'a> {
pub signature: &'a Signature,
pub is_vote: bool,
pub transaction: &'a SanitizedTransaction,
pub transaction_status_meta: &'a TransactionStatusMeta,
}
pub struct ReplicaTransactionInfoV2<'a> {
pub signature: &'a Signature,
pub is_vote: bool,
pub transaction: &'a SanitizedTransaction,
pub transaction_status_meta: &'a TransactionStatusMeta,
pub index: usize,
}두 변형의 주요 차이는 두 번째 변형이 블록 내 트랜잭션 인덱스를 저장한다는 점입니다.
fn notify_entry(&self, entry:ReplicaEntryInfoVersions<'_>) ->Result<()> { ... }notify_entry는 새 엔트리를 플러그인에 알립니다. 향후 ReplicaEntryInfo 처리 변경에 대비한 래퍼인 ReplicaEntryInfoVersions 인스턴스를 받습니다. 현재 V0_0_1(&'a ReplicaEntryInfo<'a>) 변형을 포함합니다.
이 변형은 엔트리의 슬롯, 블록 내 인덱스, 이전 엔트리 이후의 해시 수, 엔트리의 SHA-256 해시, 엔트리에서 실행된 트랜잭션 수에 관한 정보를 포함하는 struct입니다.
fn notify_block_metadata(
&self,
blockinfo:ReplicaBlockInfoVersions<'_>
) ->Result<()> { ... }notify_block_metadata 메서드는 블록의 메타데이터가 업데이트될 때 호출됩니다. 블록 정보를 담은 ReplicaBlockInfoVersions enum 인스턴스를 받습니다. 이 enum은 다양한 ReplicaBlockInfo 버전을 위한 래퍼입니다. 각 버전에는 슬롯, 해시, 보상, 블록 시간, 블록 높이 같은 블록 정보가 포함됩니다.
fn account_data_notifications_enabled(&self) ->bool { ... }
fn transaction_notifications_enabled(&self) ->bool { ... }
fn entry_notifications_enabled(&self) ->bool { ... }이 메서드들은 플러그인이 계정 데이터, 트랜잭션, 엔트리에 대한 알림을 각각 활성화할지 나타내는 불리언 값을 반환합니다.
확정 수준 참고 사항
Geyser는 계정 데이터와 트랜잭션이 처리되는 즉시 업데이트를 전송합니다. 이는 엔드투엔드 인덱싱 속도에 유리하지만 처리된 슬롯이 건너뛰어질 위험이 있습니다.
건너뛴 슬롯은 리더가 오프라인이었거나 해당 슬롯을 포함한 포크가 더 나은 대안을 위해 폐기되어 블록을 생성하지 못한 과거 슬롯을 의미합니다. 스트리밍 대상 데이터 저장 시스템은 이러한 가능성을 인식하고 그에 맞게 업데이트를 관리해야 합니다.
일반적인 Solana Geyser Plugins
개발자는 다양한 Solana Geyser Plugins를 사용하거나 특정 요구 사항에 맞게 포크할 수 있습니다. 대표적인 플러그인은 다음과 같습니다.
PostgreSQL Plugin: PostgreSQL을 사용한 데이터 관리 및 쿼리gRPC Service Streaming Plugin: Solana 계정 업데이트를 gRPC 서비스로 스트리밍RabbitMQ Producer Plugin: RabbitMQ를 사용한 메시지 큐 처리Kafka Producer Plugin: Kafka를 사용한 데이터 스트리밍Amazon SQS Plugin: Amazon Simple Queue Service를 활용한 메시지 큐 처리Google BigTable Plugin: Google BigTable을 사용한 데이터 관리 및 쿼리
이 플러그인들은 수많은 사용 사례에 맞게 조정할 수 있습니다.
예를 들어 Clockwork는 Geyser Plugin을 활용해 트랜잭션을 예약하고 자동화된 이벤트 기반 Solana 프로그램을 구축했습니다. 프로젝트는 종료되었지만 오픈 소스 코드는 여전히 유용한 자료이며 GitHub에서 확인할 수 있습니다.
그 밖에도 Geyser Plugins를 사용해 DeFi 플랫폼의 계정 잔액을 모니터링하거나, 네트워크 상태 지표를 제공하거나, 공급망 이벤트를 실시간으로 모니터링할 수 있습니다.
나만의 Solana Geyser Plugin 만들기
직접 플러그인을 구축하는 데 필요한 몇 가지 리소스와 구성 요소를 소개합니다.
Solana Geyser Plugin Scaffold
Solana Geyser Plugin Scaffold는 Solana Geyser Plugin 개발을 시작할 때 가장 쉽게 사용할 수 있는 리소스입니다. 이 스캐폴드는 Plugin Manager와 플러그인 간 상호작용을 기록하는 최소한의 템플릿입니다. 플러그인 워크플로와 디버깅 기법을 익히기에 훌륭한 출발점입니다.
Plugin Manager
Plugin Manager는 모든 Geyser Plugins의 생명 주기와 상호작용을 관리하는 핵심 구성 요소입니다. 런타임에 플러그인을 동적으로 로드하고 언로드할 수 있어 유연성과 모듈성이 향상됩니다.
런타임에 Plugin Manager는 구성 파일 경로를 플러그인에 전달합니다. 따라서 플러그인 코드를 변경하지 않고도 Geyser Plugins 설정을 맞춤화하고 수정할 수 있습니다.
플러그인을 검증인에 통합하려면 --geyser-plugin-config 매개변수를 사용해 동적 라이브러리 경로를 지정해야 합니다. 이를 통해 검증인은 플러그인과 관련 구성의 위치를 찾을 수 있습니다.
구성 파일은 최소한 JSON 형식이어야 하며, Linux에서는 .so인 Geyser Plugin 동적 라이브러리의 경로를 포함해야 합니다. 최소 구성 파일은 다음과 같습니다.
{
"libpath": "/.so"
}Geyser Plugin을 처음부터 만들기
스캐폴드를 사용하거나 기존 플러그인을 수정하지 않고 새로운 방식으로 직접 Geyser Plugin을 만들려면 Geyser Plugin Interface를 사용해 플러그인을 작성해야 합니다.
플러그인이 런타임에서 작동하려면 GeyserPlugin trait를 반드시 구현해야 합니다. 또한 동적 라이브러리는 플러그인 구현을 생성하는 “C” 함수 _create_plugin를 내보내야 합니다.
예를 들어 GeyserPlugin trait를 구현하는 Webhook 플러그인을 만들 수 있습니다.
#[no_mangle]
#[allow(improper_ctypes_definitions)]
/// # Safety
///
/// This function returns the WebhookPlugin pointer as trait GeyserPlugin.
pub unsafe extern "C" fn _create_plugin() -> *mut dyn GeyserPlugin {
let plugin = WebhookPlugin::new();
let plugin: Box = Box::new(plugin);
Box::into_raw(plugin)
}여기서는 C 및 다른 언어와 호환되도록 C 호출 규약인 extern "C"를 사용하는 unsafe public 함수를 만듭니다. fn _create*_*plugin() -> *mut dyn GeyserPlugin 함수 자체는 GeyserPlugin trait인 dynGeyserPlugin에 대한 가변 원시 포인터를 반환합니다. 함수 본문은 WebhookPlugin의 새 인스턴스를 생성하고, 이를 trait 객체로 박싱한 다음, 함수에서 반환할 수 있도록 박싱된 trait 객체를 원시 포인터로 변환합니다.
따라서 직접 Geyser Plugin을 만드는 단계는 다음과 같습니다.
- Solana Geyser Plugin 인터페이스를 구현하는 플러그인을 구축합니다
target/release또는target/debug폴더에서 동적 라이브러리(.so파일)를 가져옵니다- “libpath” 필드에 Geyser Plugin 동적 라이브러리의 경로를 포함하는
geyser-config.json파일을 생성합니다 --geyser-plugin-config geyser-config.json플래그를 사용해 검증인을 시작합니다
단계 자체는 비교적 간단해 보이지만, 실제로 Solana Geyser Plugin을 실행하고 유지 관리하는 과정은 상당히 까다로울 수 있습니다.
Helius Geyser 스트리밍
Helius는 Solana에서 탁월한 개발자 경험을 제공하는 것으로 잘 알려져 있습니다. Solana에만 집중해 온 Helius는 다양한 문제를 해결하고 수많은 대규모 통합을 지원하며 풍부한 경험을 쌓았습니다. 개발자가 마주할 수 있는 어떤 문제든 해결할 수 있는 독보적인 위치에 있습니다.
Helius는 Solana 생태계의 여러 고성능 팀을 위해 Geyser Plugins를 관리합니다. 추가적인 이중화와 내결함성을 갖춘 전용 Geyser 클러스터를 운영하므로 데이터 누락이나 다운타임을 걱정할 필요가 없습니다. 프로그래밍 방식의 API 접근을 통해 안정성을 걱정하지 않고 Geyser plugins를 동적으로 수정할 수 있습니다. Geyser plugins를 관리하려면 데이터 일관성, 안정성, 가용성을 직접 보장해야 하므로 부담이 큽니다. Helius에 맡겨보세요.
Geyser 스트리밍에 관심이 있다면 Helius 대시보드에서 전용 노드를 주문하거나 Discord로 문의해 지금 시작하세요.
마치며
축하합니다!
이 글에서는 Solana Geyser Plugins를 살펴보며 데이터 복제와 RPC 부하 관리의 복잡한 구조를 알아봤습니다. 이 시스템을 이해하는 일은 쉽지 않습니다. 문서가 거의 없는 정교한 아키텍처이지만, Solana 개발자에게 풍부한 맞춤화와 성능 최적화 기회를 제공합니다.
이 글에서 얻은 지식은 Solana에서 고성능 애플리케이션을 구축하거나 관리하려는 개발자와 팀에 특히 유용합니다. Geyser Plugins는 Solana 생태계에 확장 가능하고 안정적인 솔루션을 제공하므로 반드시 이해해야 합니다.
익명의 독자님, 여기까지 읽어주셔서 감사합니다!
추가 자료
관련 아티클
Helius 구독하기
최신 Solana 개발 소식을 확인하고 새 게시물 알림을 받아보세요


