Search
🙏

초고속 거래 체결을 지탱하는 Kinesis 패턴: 'Hot Shard'를 회피하는 Split & Aggregate 전략

일론 머스크 트윗 한 방에 국내외 크립토 거래소에서는 $DOGE 코인이 주문이 폭증 했던 사건이 있었습니다
평소에는 잔잔하던 트래픽이 특정 이벤트(호재, 악재)로 인해 특정 종목(Symbol)에만 미친 듯이 쏠리는 현상. 우리는 이것을 시스템적으로 어떻게 감당해야 할까요?
단순히 서버를 늘리는 것으로는 해결되지 않는 문제가 있습니다. 바로 데이터의 순서(Ordering)샤드(Shard) 병목 사이의 딜레마입니다. 주식 또는 코인과 같은 금융 시스템에서 데이터의 순서(Ordering)는 생명과 같습니다. 하지만 순서를 지키기 위해 특정 파티션 키를 고집하다 보면, 트래픽이 몰리는 순간 Hot Shard(특정 샤드 과부하) 문제에 직면하게 됩니다. 먼저, 문제에 직면해보고, 데이터의 무결성을 해치지 않으면서 처리량을 무한대로 확장하는 Split & Aggregate 전략을 알아보겠습니다.

1. 딜레마: 순서를 지키자니 샤드가 터지고, 분산하자니 순서가 꼬인다

AWS Kinesis Data Streams의 성능은 전적으로 샤드(Shard)의 개수에 달려 있습니다. 샤드 하나는 초당 1MB 또는 1,000건의 데이터를 처리할 수 있는 물리적인 파이프입니다.
여기서 엔지니어는 파티션 키를 설계할 때 2가지 선택의 기로에 섭니다. 자 아래 2가지 시나리오를 볼께요.

시나리오 A: Random Key 사용 (분산 최적화)

트래픽을 모든 샤드에 골고루 뿌리기 위해 PartitionKey를 랜덤으로 설정합니다.
결과: 특정 코인($DOGE)이 폭등해도 모든 샤드가 20%씩 골고루 부하를 나눠 갖습니다.
치명적 문제: 순서가 보장되지 않습니다.
A 유저가 10:00:01에 매수하고, 10:00:02에 매도했습니다.
운 나쁘게 매도 데이터가 처리 속도가 빠른 샤드에 들어가 먼저 처리된다면 어떻게 될까요?
보유 코인 부족(Not Enough Balance)과 같은 에러가 발생하게 되죠. 금융 거래 시스템에서는 절대 용납될 수 없는 상황이 발생하게 됩니다.

시나리오 B: Hash Key 사용 (순서 보장)

순서를 보장하기 위해 PartitionKey를 특정 코인으로 설정합니다. (예: PartitionKey="DOGE")
결과: 도지 코인의 모든 거래는 특정 샤드(Shard-1)로만 들어가므로 순서가 100% 보장됩니다.
치명적 문제: Hot Shard 발생
도지코인 트래픽이 초당 50MB가 들어오는데, 샤드 하나는 1MB밖에 처리를 못 합니다.
실무에서 발생한다면 생기는 ProvisionedThroughputExceededException 에러가 발생하며 데이터가 유실되기 시작합니다.
나머지 99개의 샤드는 텅텅 비어 있는데, 샤드 하나 때문에 서비스가 마비됩니다.
그럼 여기에서 여러분들은 이런 생각을 해볼수 있겠죠. 1. 순서도 보장하고 싶고, 2. 특정 거래 코인도 갑작스럽게 트래픽이 몰려도 이를 분산하고 싶다.

2. 대안책: 'Split & Aggregate' 전략

대안책으로는 "논리적인 하나의 키를 물리적으로 찢어서 저장하고(Split), 읽을 때 다시 순서를 맞추는(Aggregate)" 전략입니다.

Phase 1. Producer: 교묘하게 쪼개기 (Split)

생산자(Producer)는 트래픽이 몰리는 특정 키(Hot Key)에 대해 인위적으로 접미사(Suffix)를 붙여 트래픽을 분산시킵니다.
기존: PartitionKey = "DOGE" (모두 Shard-1로 진입)
변경: PartitionKey = "DOGE-" + Random(1 ~ N)
데이터는 DOGE-1, DOGE-2 ... DOGE-5라는 5개의 물리적 키로 나뉩니다.
Kinesis 내부 해시 함수에 의해 이 데이터들은 서로 다른 5개의 샤드로 분산됩니다.
효과: 처리 용량이 1MB/sec에서 5MB/sec로 즉시 확장됩니다. (N을 늘리면 무한 확장 가능)
#Pseudo Code import random def get_partition_key(symbol, split_factor=5): """ Hot Key(예: DOGE)인 경우 접미사를 붙여 분산, 일반 Key(예: BTC)는 그대로 사용하여 순서 보장 유지. """ if symbol in HOT_SYMBOLS: suffix = random.randint(1, split_factor) return f"{symbol}-{suffix}" # 예: DOGE-3 return symbol
Python
복사

Phase 2. Consumer: 정교하게 합치기 (Aggregate)

이제 데이터는 5개의 샤드에 흩어져 있습니다. 샤드 간에는 순서가 보장되지 않으므로, 소비자가 이를 재조립해야 합니다. 여기서 바로 Trade-off가 발생합니다.
1.
Buffering (기다림): 데이터를 받자마자 처리하지 않고, Time Window (예: 200ms) 동안 메모리 버퍼에 모읍니다.
2.
Sorting (줄 세우기): 생산자가 데이터 내부에 심어 보낸 EventTimestamp를 기준으로 오름차순 정렬합니다. (Kinesis에 도착 시간으로 결정되지 않는다는 걸 기억하세요)
3.
Processing (처리): 정렬이 완료된 순서대로 체결 엔진에 주입합니다.
Note: 물론, 이 과정에서 약간의 Latency가 발생하지만, 대용량 트래픽 상황에서의 시스템 안정성과 맞바꿀수 있는 일종의 비용이라 생각하시면 되요. 보통 현업에서는 KCL(Kinesis Client Library) 위에서 별도의 Aggregator 클래스를 구현하거나, Kinesis Data Analytics(Flink)를 활용해 구현합니다.
초당 수만에서 수십만 건의 거래가 체결되는 금융 시스템에서 데이터의 순서시스템의 확장성은 양보할 수 없는 가치입니다. 단순한 Random KeyHash Key 설정만으로는 이처럼 막대한 트래픽을 감당하기 힘들수 있습니다. Split & Aggregate 패턴도 하나의 대안이지 100% 완벽한 솔루션은 아닙니다. 그때 그때 비즈니스, 기술적 상황에 맞추어 견고한 파이프라인을 구축하는 노력이 필요합니다.