Chandy-Lamport에서 Asynchronous Barrier Snapshotting까지
분산 스냅샷의 고전 Chandy-Lamport가 Flink의 Asynchronous Barrier Snapshotting과 RisingWave의 epoch barrier로 이어지는 흐름을 정리한 학습 노트입니다.
거래소 열한 곳의 호가를 Rust 수집기, Redpanda, RisingWave로 모아 USD 기준 프리미엄을 계산하고 웹 터미널로 서빙하는 구조와 설계 결정을 정리합니다.
BibTeX
@online{jerry2026stratops,
author = {Jerry},
title = {{거래소 시세 데이터 플레인 구축}},
year = {2026},
date = {2026-10-19},
url = {https://jerrylog.dev/ko/blog/stratops-dataplane},
language = {korean},
}APA
Jerry. (2026, October 19). 거래소 시세 데이터 플레인 구축. Jerrylog. https://jerrylog.dev/ko/blog/stratops-dataplane
분산 스냅샷의 고전 Chandy-Lamport가 Flink의 Asynchronous Barrier Snapshotting과 RisingWave의 epoch barrier로 이어지는 흐름을 정리한 학습 노트입니다.
S3 Parquet와 BigQuery로 이중 운영하던 데이터를 Delta Lake 한 벌로 합치고, Databricks 고객에게는 Delta Sharing, 그 밖에는 UniForm을 통한 Iceberg로 제공한 과정.
MAB 프레임워크로 Online Learning to Rank를 어떻게 정의하는지와 그 변천사를 간략히 정리한 글.
같은 비트코인도 거래소마다 가격이 다릅니다. Upbit의 원화 가격을 Binance의 달러 가격과 환율로 맞춰 보면 김치 프리미엄이 나오고, 해외 거래소끼리도 현물과 무기한 선물 사이에 작은 가격 차이가 생깁니다. 이 차이를 실시간으로 보려면 여러 거래소의 호가를 한곳에 모으고, 통화와 자산 이름을 같은 기준으로 맞추고, 수수료와 호가 깊이까지 반영해야 합니다.
그렇다면 초당 수백 건씩 바뀌는 호가를 어떤 구조로 모으고 계산해야 할까요? 이 글에서는 트레이딩 시스템 stratops의 데이터 플레인이 어떤 데이터를 어떻게 수집하고, 어떻게 가공해서, 누구에게 전달하는지 살펴보겠습니다. 처음 선택이 바뀐 부분도 그 이유와 함께 적었습니다.
데이터 플레인은 수집, 계산, 소비의 세 계층으로 나뉩니다. 수집기(collector)는 거래소와 외부 API에서 받은 값을 공통 레코드 형식으로 바꿔 Redpanda 토픽에 씁니다. RisingWave는 이 토픽을 읽어 최신 상태, 환산 가격, 프리미엄, 1분 bar를 스트리밍으로 계산합니다. 계산 결과는 다시 Kafka 토픽으로 나가고, 트레이딩 봇과 웹 터미널이 이 토픽을 받아 씁니다.
| 계층 | 하는 일 | 하지 않는 일 |
|---|---|---|
| 수집 | 원천 데이터를 공통 레코드로 바꿔 Redpanda에 발행 | SQL 계산, 봇 로직 |
| 전송·계산 | 레코드를 재생 가능한 로그로 보관하고, 스트리밍 MV로 지표 계산 | 외부 API 호출, 주문 |
| 소비 | 신호 토픽을 구독하고, 신선도 검사를 통과한 값만 사용 | 원천 API 직접 조회, 값 보정 |
계층을 나눈 이유는 장애가 번지는 범위를 줄이기 위해서입니다. 수집기가 재시작해도 RisingWave는 멈추지 않고, RisingWave를 다시 만들어도 보존 기간 안의 원본은 Redpanda에 남아 있어 다시 읽을 수 있습니다.
라이브 봇은 이 플레인의 토픽을 구독만 합니다. 데이터가 늦을 때 봇이 거래소 API를 직접 조회하기 시작하면, 봇이 본 값과 플레인이 계산한 값이 달라지기 때문입니다. 그래서, 값이 오래됐거나 상태가 정상이 아니면 봇은 마지막 값을 재사용하지 않고 그 신호를 버립니다. 이 동작을 DEGRADE라고 부릅니다. 봇 쪽 gate는 다음 세 가지를 검사합니다.
def gate(tick, now, max_age_s, min_conf):
if tick.get("book_state", "stale") != "ok":
return DEGRADE
age_s = now - float(tick.get("ts_collector", 0.0))
if not -max_age_s <= age_s <= max_age_s:
return DEGRADE
# confidence를 요구하는데 필드가 없으면 신뢰하지 않음
if min_conf > 0 and (tick.get("confidence") is None or tick["confidence"] < min_conf):
return DEGRADE
return tick응원의 댓글을 달아주신다면 힘내서 더 빨리 써보도록 하겠습니다...
댓글