Binance Spot 체결 아카이브 데이터를 받아 ClickHouse에 적재하는 ASP.NET Core 인제스션 서버입니다.
이 서버는 큰 JSON 요청을 한 번에 객체 그래프로 역직렬화하지 않습니다. ASP.NET Core Minimal API에서 요청 body를 스트리밍으로 읽고, Utf8JsonReader로 row를 순차 파싱한 뒤 ClickHouse에 RowBinary 포맷으로 chunk 단위 insert합니다.
- ASP.NET Core Minimal API: 얇은 HTTP 엔드포인트와 명시적인 서비스 구성
- Streaming JSON parser:
PipeReader와Utf8JsonReader로 요청 body를 순차 처리 - Chunked ingest: 요청 전체 row를 메모리에 쌓지 않고
CHUNK_ROWS단위로 처리 - ClickHouse RowBinary insert: HTTP API를 통해 RowBinary payload를 스트리밍 전송
- 선택적 기존 데이터 확인:
CHECK_EXISTING_IDS=true일 때 chunk 단위로 기존trade_id조회 - Native AOT 이미지 지원:
Dockerfile.aot로 더 작은 런타임 이미지를 빌드 가능
.
├── src/ArchiveBinance.Api/ # ASP.NET Core API 서버
├── init.sql # 신규 ClickHouse 볼륨 초기화 스키마
├── migrations/ # 기존 ClickHouse 볼륨 수동 마이그레이션
├── Dockerfile # 일반 ASP.NET Core 런타임 이미지
├── Dockerfile.aot # Native AOT 이미지
├── docker-compose.yml # 공통 Compose 설정
├── docker-compose.dev.yml # 개발용 일반 런타임 Compose override
├── docker-compose.prod.yml # 운영용 Native AOT Compose override
└── .env.example # 로컬 실행 환경 변수 예시
환경 변수 파일을 준비합니다.
cp .env.example .env개발용 일반 런타임 컨테이너로 실행합니다.
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build중지할 때도 같은 파일 조합을 사용합니다.
docker compose -f docker-compose.yml -f docker-compose.dev.yml down헬스 체크:
curl http://localhost:3000/healthz기본 서비스:
- API server:
http://localhost:3000 - ClickHouse HTTP:
http://localhost:8123 - ClickHouse Native:
localhost:9000
운영 후보 구성은 Native AOT 이미지를 사용합니다.
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build중지할 때도 같은 파일 조합을 사용합니다.
docker compose -f docker-compose.yml -f docker-compose.prod.yml down이미지만 직접 빌드하려면 다음 명령을 사용합니다.
docker build -f Dockerfile.aot -t archive-binance-app:latest .Binance 체결 row를 ClickHouse의 binance_spot_trades 테이블에 적재합니다.
인증 헤더는 둘 중 하나를 사용합니다.
x-api-key: <API key>
Authorization: Bearer <API key>요청 예시:
{
"symbol": "BTCUSDT",
"rows": [
[
0,
"BTCUSDT:6033469037",
6033469037,
"66973.26000000",
"0.03017000",
"2020.58325420",
1772323200005709,
false,
true
]
]
}row 배열 순서:
| Index | Field | Type | Note |
|---|---|---|---|
| 0 | _row_index |
number | 오류 메시지용 |
| 1 | _dedupe_key |
string | 읽고 버림 |
| 2 | trade_id |
number | Binance trade id |
| 3 | price |
string number | Float64로 파싱 |
| 4 | quantity |
string number | Float64로 파싱 |
| 5 | quote_quantity |
string number | Float64로 파싱 |
| 6 | trade_time |
number | ms 또는 us timestamp |
| 7 | is_buyer_maker |
boolean | |
| 8 | is_best_match |
boolean |
스트리밍 처리를 위해 symbol은 rows보다 먼저 와야 합니다.
성공 응답 예시:
Successfully processed 100000 trades for BTCUSDT. (Inserted: 100000, Skipped: 0, Chunks: 2)
| Name | Default | Description |
|---|---|---|
CLICKHOUSE_URL |
http://localhost:8123 |
ClickHouse HTTP endpoint |
CLICKHOUSE_USER |
admin |
ClickHouse user |
CLICKHOUSE_PASSWORD |
password |
ClickHouse password |
CLICKHOUSE_DATABASE |
default |
ClickHouse database |
API_KEYS |
required | 쉼표 구분 API key 목록 |
MAX_BODY_BYTES |
268435456 |
Kestrel request body 제한 |
MAX_ROWS_PER_REQUEST |
500000 |
요청당 최대 row 수 |
CHUNK_ROWS |
50000 |
내부 insert chunk 크기 |
EXISTING_ID_LOOKUP_ROWS |
20000 |
기존 trade_id 조회 query batch 크기 |
CHECK_EXISTING_IDS |
true |
기존 trade_id 조회 여부 |
PORT |
3000 |
API server port |
요청 전체를 List로 보관하지 않기 때문에 메모리 사용량은 주로 CHUNK_ROWS와 동시 요청 수에 비례합니다.
서버가 요청 처리 중 보관하는 주요 데이터:
- 현재 Kestrel/PipeReader buffer
- 현재 chunk의
TradeRowstruct 목록 - chunk 내부 중복 제거용
HashSet<trade_id> CHECK_EXISTING_IDS=true일 때 기존 id 조회 결과와 lookup batch- insert 대상
trade_id집합
CHECK_EXISTING_IDS=true에서는 chunk 안의 trade_id를 EXISTING_ID_LOOKUP_ROWS 단위로 나눠 조회합니다. 이 값은 ClickHouse 기본 max_query_size를 넘기지 않도록 기본 20000으로 둡니다.
순수 append 성능이 더 중요하고 ClickHouse의 ReplacingMergeTree 병합 중복 제거에 맡길 수 있다면 CHECK_EXISTING_IDS=false를 사용할 수 있습니다.
초기 스키마는 init.sql에 있습니다.
CREATE TABLE IF NOT EXISTS binance_spot_trades
(
symbol String,
trade_time Int64,
trade_id UInt64,
price Float64,
quantity Float64,
quote_quantity Float64,
is_buyer_maker Bool,
is_best_match Bool
)
ENGINE = ReplacingMergeTree
PARTITION BY toDate(fromUnixTimestamp64Micro(trade_time, 'UTC'))
ORDER BY (symbol, trade_id);trade_time은 앱에서 microseconds로 정규화합니다. millisecond timestamp 입력도 허용합니다.
MAX_ROWS_PER_REQUEST를 올릴 때는MAX_BODY_BYTES도 함께 조정해야 합니다.CHUNK_ROWS를 크게 올리면 insert 효율은 좋아질 수 있지만 앱 메모리 사용량도 증가합니다.EXISTING_ID_LOOKUP_ROWS를 크게 올리면 기존 ID 조회 round-trip은 줄지만 ClickHousemax_query_size에 가까워집니다.CHECK_EXISTING_IDS=true에서 더 큰 lookup batch가 필요하면 ClickHouse의max_query_size도 함께 검토해야 합니다.- 기존 ClickHouse 볼륨은
init.sql변경이 자동 반영되지 않습니다. 필요한 경우 migrations/001_rebuild_binance_spot_trades.sql을 수동 적용합니다.
일반 이미지:
docker build -t archive-binance-app:dev .Native AOT 이미지:
docker build -f Dockerfile.aot -t archive-binance-app:latest .