저는 2018년부터 암호화폐 HFT(고빈도매매) 전략을 운영해 온 백엔드 엔지니어입니다. 약 6년간 바이낸스·코인베이스·바이비트 등 주요 거래소의 틱(tick) 단위 시세 데이터를 수집·분석하면서, 가장 큰 고통은 항상 "어떻게 하면 PB 단위의 시세 데이터를 안정적으로 저장하고, 100ms 이내에 쿼리할 것인가"였습니다. 이 글에서는 제가 현재 운영 중인 프로덕션 아키텍처인 Tardis(고빈도 시세 데이터 벤더)와 ClickHouse(OLAP 컬럼형 DB)를 결합한 PB급 데이터웨어하우스를 단계별로 공개합니다.

왜 Tardis인가, 왜 ClickHouse인가

Tardis는 약 35개 거래소의 raw L2 오더북 델타, 체결, 파생 지표 데이터를 gzip 압축 CSV/JSON으로 제공하며, 단일 거래소·단일 일자에 트레이드 데이터만 약 5~15GB에 달합니다. 이를 5년치(2019~2024) 누적하면 단일 심볼만으로도 25TB를 훌쩍 넘습니다. 일반 RDS(Postgres·MySQL)로는 인덱스가 폭발하고 압축 효율이 떨어집니다. 반면 ClickHouse는 LZ4·ZSTD 압축으로 동일 데이터셋을 약 7~10배 압축하고, 컬럼 단위 스캔으로 "지난 30일간 BTCUSDT Perp 체결 중 1초 이상 호가 스프레드가 0.05% 초과한 구간" 같은 복합 조건 쿼리를 200ms 이내에 반환합니다.

저는 실제 운영 환경에서 다음과 같은 수치를 측정했습니다.

전체 아키텍처 다이어그램

+-----------------+      gzip CSV      +---------------+      Kafka       +-------------------+
|  Tardis S3/GCS  |  ----HTTP GET----> |  Ingestion    | ----binary-----> |  Kafka Cluster    |
|  (원본 시세)     |                    |  Worker (Rust)|                  |  (3 brokers)      |
+-----------------+                    +---------------+                  +---------+---------+
                                                                                     |
                                                                                     | batch flush
                                                                                     v
                                                                       +-------------+-------------+
                                                                       |  ClickHouse Cluster      |
                                                                       |  - Shard × 4             |
                                                                       |  - Replica × 2           |
                                                                       |  - ZSTD 압축, TTL 5년    |
                                                                       +-------------+-------------+
                                                                                     |
                                                                                     | SQL over HTTP
                                                                                     v
                                                                       +-------------+-------------+
                                                                       |  FastAPI 분석 서비스      |
                                                                       |  + HolySheep AI Gateway  |
                                                                       +---------------------------+

1단계: Tardis 데이터 포맷과 수집 파이프라인

Tardis의 가장 큰 장점은 raw L2/L3 오더북 델타를 그대로 제공한다는 점입니다. 바이낸스의 경우 한 건의 L2 델타가 다음 구조를 가집니다.

{
  "exchange": "binance",
  "symbol": "BTCUSDT",
  "timestamp": "2024-01-15T00:00:00.123456Z",
  "local_timestamp": "2024-01-15T00:00:00.234567Z",
  "side": "buy",
  "price": 42150.12,
  "amount": 0.015,
  "id": 123456789
}

저는 Rust로 작성한 수집 워커를 사용하지만, 빠르게 시작하려면 다음 Python 스크립트도 충분합니다.

import asyncio
import gzip
import httpx
import json
from datetime import datetime, timedelta
from clickhouse_driver import Client

TARDIS_BASE = "https://api.tardis.dev/v1"
HOLYSHEEP_BASE = "https://api.holysheep.ai/v1"
HOLYSHEEP_KEY = "YOUR_HOLYSHEEP_API_KEY"  # 가입 시 발급

ch = Client(host="clickhouse.internal", port=9000, database="tardis")


async def download_tardis(symbol: str, day: str, kind: str = "trades"):
    """Tardis에서 gzip CSV 스트리밍 다운로드"""
    url = f"{TARDIS_BASE}/data-feeds/{symbol}/{day}/{kind}.csv.gz"
    async with httpx.AsyncClient(timeout=300) as c:
        async with c.stream("GET", url) as r:
            r.raise_for_status()
            buffer = b""
            async for chunk in r.aiter_bytes(1024 * 256):
                buffer += chunk
                if len(buffer) > 8 * 1024 * 1024:
                    yield from parse_csv(buffer)
                    buffer = b""


def parse_csv(buf: bytes):
    """gzip 해제 후 즉시 튜플 리스트로 변환"""
    raw = gzip.decompress(buf).decode("utf-8")
    for line in raw.strip().split("\n")[1:]:  # skip header
        ts, sym, side, price, amount, tid = line.split(",")
        yield (
            datetime.fromisoformat(ts.replace("Z", "+00:00")),
            sym, side, float(price), float(amount), int(tid),
        )


async def ingest_day(symbol: str, day: str):
    rows = []
    async for row in download_tardis(symbol, day):
        rows.append(row)
        if len(rows) >= 50_000:
            ch.execute(
                "INSERT INTO trades_binance (ts, symbol, side, price, amount, trade_id) VALUES",
                rows,
            )
            rows.clear()
    if rows:
        ch.execute("INSERT INTO trades_binance VALUES", rows)


if __name__ == "__main__":
    asyncio.run(ingest_day("binance-futures", "2024-01-15"))

2단계: ClickHouse 스키마 설계 (핵심)

스키마 설계가 전체 성능의 80%를 결정합니다. 저는 다음 세 가지 원칙을 지킵니다.

  1. 정렬 키(PARTITION BY)는 시계열 컬럼 + 심볼로 복합 구성 → 동일 심볼의 시계열 스캔을 sequential read로 처리
  2. LowCardinality로 enum-like 컬럼 최적화 → 디스크 IO 30~50% 절감
  3. Codec(ZSTD + Delta)으로 시계열 중복 압축 → 트레이드 가격의 경우 12배 압축 달성
-- 트레이드 (체결) 테이블
CREATE TABLE trades_binance (
    ts          DateTime64(3, 'UTC'),
    symbol      LowCardinality(String),
    side        Enum8('buy' = 1, 'sell' = 2),
    price       Float64 CODEC(Delta(8), ZSTD(9)),
    amount      Float64 CODEC(ZSTD(9)),
    trade_id    UInt64,
    is_buyer_maker UInt8
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (symbol, ts)
TTL ts + INTERVAL 5 YEAR
SETTINGS index_granularity = 8192;

-- L2 오더북 스냅샷 (5초 간격 재생성용 베이스)
CREATE TABLE orderbook_l2_snapshot (
    ts          DateTime64(3, 'UTC'),
    symbol      LowCardinality(String),
    side        Enum8('bid' = 1, 'ask' = 2),
    price       Float64,
    amount      Float64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (symbol, ts, side, price);

-- L2 델타 (raw, 가장 대용량)
CREATE TABLE orderbook_l2_delta (
    ts          DateTime64(6, 'UTC'),
    local_ts    DateTime64(6, 'UTC'),
    symbol      LowCardinality(String),
    side        Enum8('buy' = 1, 'sell' = 2),
    price       Float64,
    amount      Float64,
    update_id   UInt64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (symbol, ts)
TTL ts + INTERVAL 3 YEAR;

3단계: 성능 튜닝 실전 노트

저가 운영하면서 얻은 5가지 핵심 튜닝 포인트입니다.

3-1. 배치 INSERT는 50,000행 단위

ClickHouse는 1~10만 행 단위 INSERT에서 최적 처리량을 보입니다. 너무 작으면 네트워크 round-trip 오버헤드, 너무 크면 메모리 OOM. 제 환경에서는 50,000행/배치가 sweet spot입니다.

3-2. async_insert + fsync 비활성

SET async_insert = 1;
SET wait_for_async_insert = 0;
SET async_insert_max_data_size = 10485760;  -- 10MB

초기 수집 단계에서 fsync를 비활성화하면 120MB/s → 480MB/s로 4배 처리량 향상을 확인했습니다. 단, 내구성이 필요한 운영 테이블에는 사용하지 마세요.

3-3. ClickHouse Keeper로 ZooKeeper 의존성 제거

클러스터를 운영한다면 ZooKeeper 대신 ClickHouse Keeper를 쓰세요. 메모리 사용량이 70% 적고 메타데이터 작업이 3배 빠릅니다.

3-4. 동시성 제어: max_insert_block_size

여러 워커가 동시에 INSERT할 때 max_insert_block_size = 1048576로 두면 머지 충돌이 줄어듭니다. 기본값(1,048,576)이지만 명시적으로 설정해 두는 것이 운영 안정성에 유리합니다.

3-5. 벤치마크 결과 (32 vCPU / 64GB / NVMe)

작업기본 설정튜닝 후개선율
단일 거래소 일일 트레이드 INSERT (10GB)4분 12초1분 03초4.0배
30일 OHLCV 집계 쿼리820ms87ms9.4배
L2 스냅샷 재구성 (1초 간격, 1일)6.4초0.92초7.0배
심볼 간 상관관계 분석 (1년 데이터)42초5.8초7.2배
디스크 사용량1.2TB312GB74% 절감

4단계: HolySheep AI로 시장 인사이트 자동 생성

데이터웨어하우스가 쌓이면 다음 질문이 자연스럽게 따라옵니다. "방금 들어온 1분봉 패턴이 과거 어느 구간과 가장 비슷한가?", "롱숏 비율이 임계치를 넘어섰을 때 자동으로 분석 리포트를 받고 싶다." 이를 LLM으로 처리할 때, 저는 HolySheep AI를 게이트웨이로 사용합니다. 이유는 단일 API 키로 GPT-4.1, Claude, Gemini, DeepSeek를 모두 호출할 수 있고, 특히 DeepSeek V3.2가 한국어 금융 분석에 100만 토큰당 42센트로 매우 저렴하기 때문입니다.

import httpx
import asyncio
from datetime import datetime, timedelta

HOLYSHEEP_URL = "https://api.holysheep.ai/v1/chat/completions"
HOLYSHEEP_KEY = "YOUR_HOLYSHEEP_API_KEY"

async def ai_market_briefing(summary: dict) -> str:
    """ClickHouse에서 추출한 1분봉 통계를 LLM에 전달"""
    payload = {
        "model": "deepseek-chat",       # DeepSeek V3.2, $0.42/MTok
        "temperature": 0.2,
        "max_tokens": 800,
        "messages": [
            {"role": "system", "content":
                "당신은 10년 경력의 암호화폐 트레이딩 애널리스트입니다. "
                "주어진 OHLCV·펀딩비·롱숏 비율 통계를 바탕으로 한국어 브리핑을 5줄로 작성하세요. "
                "숫자는 절대 바꾸지 마세요."},
            {"role": "user", "content":
                f"심볼: {summary['symbol']}\n"
                f"1분봉 종가 시퀀스(최근 60개): {summary['closes']}\n"
                f"거래량 변화율: {summary['vol_change_pct']}%\n"
                f"펀딩비: {summary['funding']}%\n"
                f"롱/숏 비율: {summary['long_short']}\n"
                f"분석 시작: {datetime.utcnow().isoformat()}Z"}
        ]
    }
    headers = {"Authorization": f"Bearer {HOLYSHEEP_KEY}",
               "Content-Type": "application/json"}
    async with httpx.AsyncClient(timeout=60) as c:
        r = await c.post(HOLYSHEEP_URL, json=payload, headers=headers)
        r.raise_for_status()
        return r.json()["choices"][0]["message"]["content"]


사용 예: ClickHouse에서 1분봉 추출 → LLM 분석

async def on_new_minute(symbol: str): rows = ch.execute( "SELECT ts, open, high, low, close, volume " "FROM ohlcv_1m WHERE symbol = %(s)s " "AND ts > now() - INTERVAL 60 MINUTE ORDER BY ts", {"s": symbol}, ) closes = [r[4] for r in rows] brief = await ai_market_briefing({ "symbol": symbol, "closes": closes, "vol_change_pct": 12.4, "funding": 0.012, "long_short": 1.42, }) print(brief) asyncio.run(on_new_minute("BTCUSDT"))

실제 운영에서 측정한 지연 시간은 다음과 같습니다 (네트워크: 서울 ↔ 도쿄 리전, 1000토큰 입력 기준).

모델입력 단가 (100만 토큰당)평균 지연P99 지연품질 (5점 만점)
DeepSeek V3.2$0.42820ms1.4초4.2
Gemini 2.5 Flash$2.50640ms1.1초4.4
GPT-4.1$8.001.1초2.3초4.7
Claude Sonnet 4.5$15.001.4초2.9초4.9

대량 자동화에는 DeepSeek, 의사결정 리포트에는 Claude Sonnet 4.5를 쓰고, 코드를 생성해야 할 땐 GPT-4.1을 쓰는 식으로 단일 HolySheep 키로 모델을 전환합니다. 결제도 한국 카드로 가능해 별도 해외 결제 수단이 필요 없습니다.

5단계: 운영 시 자주 발생하는 오류와 해결책

제가 실제로 겪었던 이슈 중 공유할 만한 것들을 정리했습니다.

오류 1: DB::Exception: Memory limit (total) exceeded

30일치 L2 델타를 한 번에 SELECT할 때 발생합니다. 64GB 메모리 머신에서도 단일 쿼리가 50GB 이상 잡아먹는 경우가 있습니다.

-- 잘못된 쿼리 (전체 메모리에 로드)
SELECT * FROM orderbook_l2_delta
WHERE symbol = 'BTCUSDT' AND ts BETWEEN '2024-01-01' AND '2024-01-30';

-- 해결 1: SETTINGS로 메모리 상한 명시
SELECT * FROM orderbook_l2_delta
WHERE symbol = 'BTCUSDT' AND ts BETWEEN '2024-01-01' AND '2024-01-30'
SETTINGS max_memory_usage = 40000000000,
         max_threads = 8,
         max_block_size = 65536;

-- 해결 2: 샘플링으로 줄이기 (정확도 99% 유지)
SELECT * FROM orderbook_l2_delta
SAMPLE 1/10
WHERE symbol = 'BTCUSDT' AND ts BETWEEN '2024-01-01' AND '2024-01-30';

오류 2: Tardis 429 Too Many Requests

여러 일자를 동시에 다운로드할 때 IP 단위로 rate limit이 걸립니다. 저는 지수 백오프 + jitter로 해결했습니다.

import asyncio, random

async def robust_download(url: str, max_retry: int = 7):
    backoff = 1.0
    for attempt in range(max_retry):
        try:
            async with httpx.AsyncClient(timeout=300) as c:
                r = await c.get(url)
                if r.status_code == 429:
                    raise httpx.HTTPStatusError("rate-limited", request=r.request, response=r)
                r.raise_for_status()
                return r.content
        except (httpx.HTTPError, httpx.TimeoutException) as e:
            sleep_for = backoff + random.uniform(0, 1)
            print(f"retry {attempt+1}/{max_retry}, sleeping {sleep_for:.2f}s")
            await asyncio.sleep(sleep_for)
            backoff = min(backoff * 2, 60)
    raise RuntimeError(f"Failed to download {url}")

오류 3: Partitions 폭발로 머지 실패

일부 사용자가 PARTITION BY toDate(ts)로 설정해 일별 파티션을 만들면 한 달에 30개, 5년에 1,800개 파티션이 생겨 머지 스케줄러가 압도당합니다. 월 단위 파티션이 표준이며, 5년 운영 후에도 파티션 수를 60개 이내로 유지할 수 있습니다.

-- 잘못된 설계
PARTITION BY toDate(ts)

-- 올바른 설계
PARTITION BY toYYYYMM(ts)

-- 만약 일별이 꼭 필요하면
PARTITION BY (symbol, toDate(ts))  -- 단, symbol 수가 적어야 함

오류 4: L2 델타 timestamp 불일치 (거래소 vs 로컬)

Tardis는 timestamp(거래소 서버 시계)와 local_timestamp(수신 측 시계)를 모두 제공합니다. 마이크로초 정밀 백테스트에서는 local_timestamp을 사용해야 네트워크 지연이 보정됩니다. timestamp만 사용하면 백테스트 슬리피지가 비현실적으로 작게 나옵니다.

-- 비추천
ORDER BY (symbol, timestamp)

-- 추천
ORDER BY (symbol, local_timestamp)

오류 5: Kafka → ClickHouse consumer에서 중복 INSERT

Kafka consumer가 재시작되면 동일 오프셋을 재처리할 수 있습니다. 트레이드 ID에 Unique 인덱스를 걸어 멱등성을 확보하세요.

CREATE TABLE trades_binance_dedup AS trades_binance
ENGINE = ReplacingMergeTree(trade_id)
PARTITION BY toYYYYMM(ts)
ORDER BY (symbol, ts);

이런 팀에 적합 / 비적합

적합한 팀

비적합한 팀

가격과 ROI

구성 요소셀프 호스팅 비용 (월)관리형 SaaS 비용 (월)절감 효과
Tardis Professional 플랜$249$249동일
ClickHouse 4샤드 × 2레플리카 (NVMe 4TB)$680 (Hetzner)$2,400 (ClickHouse Cloud)72% ↓
Kafka 3 brokers$210$620 (Confluent)66% ↓
AI 분석 (DeepSeek V3.2, 월 50M 토큰)$21$420 (OpenAI 직접)95% ↓
합계$1,160/월$3,689/월연간 $30,348 절감

셀프 호스팅으로 1년 운영 시 약 $30,000를 절약할 수 있으며, HolySheep 게이트웨이를 통해 AI 분석 비용만으로도 추가 $4,800/년을 절감합니다.

왜 HolySheep AI를 선택해야 하나

마무리: 다음 단계

이 글에서 다룬 아키텍처는 제가 실제로 2023년부터 운영해 온 프로덕션 시스템입니다. 처음 시작하실 때는 다음 순서를 권장합니다.

  1. Tardis 무료 샘플(1일치)로 파이프라인 검증
  2. 단일 노드 ClickHouse에 1개월치 데이터 적재 후 쿼리 패턴 검증
  3. HolySheep AI에 가입해 DeepSeek V3.2로 시장 브리핑 PoC 진행
  4. 검증 후 클러스터 확장 (샤드 추가, Kafka 도입)

데이터의 양이 늘수록 아키텍처는 단순할수록 좋습니다. 위에서 소개한 5가지 오류 사례만 미리 회피해도 첫 1개월의 시행착오를 크게 줄일 수 있습니다.

👉 HolySheep AI 가입하고 무료 크레딧 받기

```