완전히 새롭게 구축한 코빗의 가상자산 거래 시스템
생각보다 훨씬 복잡한 가상자산 거래소
전통 금융 시스템은 역할이 명확히 분리되어 있습니다. 주식 시장에서 증권사, 한국거래소, 예탁결제원은 주문 접수, 매매체결, 청산결제 등을 나누어 담당하며 결제 주기는 'T+2'로 운영됩니다.1 반면, 가상자산 거래소는 이 모든 역할을 혼자 수행해야 합니다. 주문 접수부터 매매체결, 시세분배, 청산결제까지의 전 과정을 365일 24시간 멈추지 않고, 결제까지 즉시 처리해야 합니다.
코빗은 2025년 10월 현재 시장점유율이 높지 않지만, 일일 천만 건 단위의 주문 트랜잭션을 처리하고 있습니다. 초당 수천 건의 주문과 취소 요청을 바로 처리할 수 있어야 하므로, 가상자산 거래소는 단순한 웹, 앱 서비스가 아니라 고도의 설계를 요구하는 미션 크리티컬 시스템이라고 보는 것이 정확합니다.
왜 전면 재구축을 선택했나
가상자산 거래소의 거래 시스템은 초당 최소한 수천 건 이상의 자산 처리 요청을 빠르게 처리해야 하고, 24시간 무중단으로 운영하며, 장애에도 끄떡없는 복원력까지 갖추어야 합니다. 채팅 메시지야 일부 유실되어도 치명적이지 않을 수 있지만, 돈이 걸려 있는 주문 메시지는 그렇지 않습니다.
코빗은 2013년 국내 최초로 설립된 거래소이기에 그만큼 레거시의 무게도 컸습니다. 2018년부터 2024년 상반기까지 쓰이던 거래 시스템은 가상자산 시장이 더욱 성장하면서 그 한계가 분명해졌고, 2024년 초까지 거래량 급증 시 장애가 간헐적으로 반복되었습니다.
이 문제를 근본적으로 해결하기 위해 제가 코빗의 CTO가 된 지 얼마 후인 2023년 초, 전면 재구축을 결정했고 1년여의 대장정을 거쳐 새롭게 설계한 거래 시스템을 2024년 6월 운영 환경에 배포했습니다.
레거시의 한계와 과감한 결단
이전 시스템은 어느샌가 유지보수도 쉽지 않은 레거시 시스템이 되어 있었습니다. 거대한 모놀리식 구조와 동기 처리 방식이 결합되어 있었기에 특정 기능이 지연되면 주문, 체결, 잔고 갱신 등이 연쇄적으로 지연되며 결국 전체 시스템이 다 흔들리는 식이었습니다. 데이터가 단일 데이터베이스에 집중되어 병목도 상당했고, 이런저런 기능 추가가 반복되면서 기술 부채도 크게 늘어나 있었습니다.
이에 저희는 아키텍처 자체를 바꾸는 대규모 전환을 추진했습니다. 목표는 분명했습니다. 이벤트 기반(Event-Driven), 그리고 마이크로서비스 아키텍처(Microservice Architecture, MSA)로 완전히 새로운 시스템을 구축하는 것이었죠.
MSA를 선택한 이유
대부분의 서비스는 잘 설계된 모놀리스로도 충분하다고 생각합니다. MSA가 항상 정답은 아닙니다. MSA는 오히려 복잡성을 크게 증가시킵니다. 그럼에도 코빗이 MSA를 택한 이유는 명확합니다. 일일 수천만 건의 트랜잭션을 처리하면서도, 높은 처리량(High-throughput)과 낮은 지연(Low-latency)이라는 성능 지표나 장애 상황에 대비한 높은 가용성까지도 모두 달성해야 했습니다. 이를 위해 도메인을 명확히 분리해 각각 독립적으로 서비스를 수평, 수직 확장할 수 있게 하고, 각 서비스 간에는 Apache Kafka를 통한 비동기 이벤트로 통신하게 했습니다.
즉, 그저 유행하거나 멋져 보이는 기술을 좇은 것이 아니라, 저희의 구체적인 요구에 맞는 선택을 했던 셈입니다.
실패를 전제로 한 시스템
새로운 시스템을 설계하며 저희가 세운 핵심 목표는 성능, 안정성, 복원력의 세 가지입니다.
분산 환경에서 네트워크 호출은 언제든 실패하거나 지연될 수 있습니다. 견고한 시스템은 바로 이 실패 가능성을 배제하지 않고 받아들여야만 합니다. 특정 서비스에 장애가 발생하더라도 시스템은 전체 서비스가 중단되지 않도록 부분적인 실패를 감내(Fault-tolerant)하고 스스로 정상 상태를 되찾을 수 있어야 합니다(Resilient).
이 목표를 달성하기 위한 핵심적인 축이 바로 Kafka 중심의 비동기 이벤트 아키텍처였습니다. Kafka는 아주 높은 성능에 더해 강력한 내구성을 제공해 문제 상황에서도 데이터 유실 없이 복구가 가능하도록 합니다. 결론적으로, 이벤트 중심 설계는 새로운 거래 시스템의 가장 중요한 철학이 되었습니다.
아키텍처, 단단하게 다시 만들기
정리하자면, 새로운 시스템의 중심에는 Kafka가 이벤트 버스 역할을 합니다. 각 마이크로서비스는 필요한 순간에 이벤트를 발행(Produce)하고 또 소비(Consume)하면서 각자 맡은 작업을 수행합니다.
이벤트 흐름의 구체적인 예시
우선, 주요 이벤트의 흐름은 다음과 같습니다.
- 주문이 접수되면 주문 서비스가 이벤트를 발행합니다.
- 체결 엔진이 이를 소비해 매칭(Matching)을 수행합니다.
- 매칭 결과에 따른 이벤트가 발행됩니다.
- 잔고 서비스와 시세 서비스가 이를 소비해 각자 상태를 갱신합니다.
서비스 간 직접 호출을 최소화해 결합도를 낮추었고 시스템 상태 변경은 Event Sourcing2을 적용해 불변 이벤트로 기록함으로써 전체 변경 이력의 추적이 가능해졌고, 특정 시점의 시스템 상태도 정확하게 복원할 수 있게 되었습니다.
Kafka, 시스템을 지탱하는 근간
주문, 체결, 잔고 변경 같은 주요 이벤트 흐름은 모두 Kafka를 통해 전달되며 처리 순서가 보장되고 필요하면 재생이 가능한 구조로 만들었습니다.
그리고 Kafka를 직접 운영하는 경우의 부담은 AWS MSK(Amazon Managed Streaming for Apache Kafka)로 상당 부분 해소했습니다. 또한, 이를 3개 가용 영역(AZ)으로 나누어 구성했기에 단일 AZ 장애 시에도 서비스가 지속됩니다. 또, 보안 패치 같은 것도 무중단으로 AWS에서 제공하므로 부담이 크게 줄었습니다.
RabbitMQ나 NATS 등도 검토했지만, 고성능, 고가용성에 더해 완전관리형으로 제공되는 매니지드 서비스(MSK)의 이점을 고려할 때 Kafka가 가장 적합하다고 판단했습니다.
데이터 일관성: Outbox와 Event Sourcing
이벤트 기반 시스템에서 가장 위험한 것은 데이터베이스에는 기록되었는데 이벤트가 발행되지 않았거나 그 반대의 경우가 발생하는 상황일 것입니다. 저희는 이를 방지하기 위해 Transactional Outbox와 Event Sourcing을 결합했습니다. 모든 상태 변경을 이벤트로 저장하고 데이터베이스 트랜잭션과 Kafka 발행을 하나로 묶어서 처리하는 Outbox 패턴을 적용해서 데이터 일관성(Consistency)을 갖추었습니다.
각 이벤트는 고유 ID를 부여하고 멱등성3을 확보해 같은 이벤트가 중복 수신되어도 결과는 동일하게 유지됩니다.
장애를 대하는 태도
"절대 장애가 안 나게 하자"는 현실적이지 않습니다. 저희는 "장애가 나도 시스템은 버티고, 신속하게 복구되게 하자"는 전략를 세웠습니다.
예를 들어, 인메모리(In-memory) 방식으로 동작하는 체결 엔진은 Active/Standby 이중화 구조로 운영해 두 매칭 엔진이 Kafka를 통해 상태를 지속적으로 동기화하면서 Active에 장애가 발생하면 즉시 Standby로 전환이 이루어지게 했습니다.
또한, 어떤 네트워크 통신이든 실패할 수 있다는 것을 전제로 설계했고 모든 이벤트가 보존되므로, 인프라에 문제가 생겨도 시스템을 복구할 수 있습니다. 주기적으로 오더북(Orderbook) 스냅샷을 저장하고, Outbox 전송 내역을 데이터베이스와 S3에 보존하고 있어 만일의 경우에도 이벤트를 재생해 상태를 재구성할 수 있기 때문입니다.
운영 효율성을 위한 관리형 서비스 활용
Amazon Aurora는 기존에 사용하던 MySQL 5.7 대비 향상된 성능과 장애 대응 관련 기능을 제공하여 안정적인 기반을 제공합니다.
또, 저희는 모든 마이크로서비스를 Amazon EKS에서 운영하고 있는데 트래픽이 많이 늘어나는 상황에서도 스케일링이 어렵지 않아 효율적인 운영이나 장애 대비가 모두 손쉬워졌습니다.
Amazon MSK를 포함해 이런 관리형 서비스의 진짜 가치는 한정된 엔지니어링 리소스를 효율적으로 사용할 수 있게 해 준다는 점입니다. 저희처럼 규모가 크지 않은 회사에서는 큰 이점이 있습니다.
관찰 가능성(Observability)
마이크로서비스 환경에서는 분산 추적이 필수적입니다. API Gateway에서부터 주문 서비스, 매칭 엔진, 잔고 처리 서비스 등을 거치는 전체 경로를 동일한 ID로 연결하여 완전한 추적이 가능하게 했습니다. 그리고 New Relic을 활용해 실시간 모니터링 시스템을 구축했습니다. 주문 처리 지연이 발생하거나 서비스별 핵심 지표가 임계치를 벗어나면 즉시 알림이 발송되며 Amazon RDS나 MSK의 성능 지표 같은 것들도 지속적으로 확인해 문제의 가능성을 사전에 감지합니다.
실전에서 검증된 효과
새로운 거래 시스템을 개발해 2024년 6월에 운영 환경에 배포한 후, 크게 개선된 성능 지표와 안정성을 확인할 수 있었습니다. 특히 2024년 12월 3일 밤, 갑작스러운 계엄 선포로 인해 국내 가상자산 거래소들마다 시세가 크게 출렁일만큼 대량의 주문이 쏟아졌고 저희 코빗도 평소의 4배 이상 트래픽이 유입되었습니다.
저희는 EKS를 통해 계정계나 BFF(Back-End for Front-End)를 비롯한 여러 서비스들과 Aurora가 이에 맞춰 대폭 확장해 부하를 분산시켰고 심지어 새로 구축한 거래 시스템 쪽은 별도의 스케일 아웃조차 필요하지 않았습니다. 거래 시스템 쪽은 아무런 지연 없이 원활한 거래가 이루어졌습니다.
일부 가상자산 거래소들이 이른바 '계엄빔'으로 거래가 일시적으로 중단될 때, 코빗의 거래 시스템은 단 0.1초의 중단도 없었습니다. 관련해서 '코빗만 멀쩡했다'는 뉴스 기사가 나올 정도였습니다. 전면 재구축한 거래 시스템의 성과를 실전에서 검증한 사례였습니다.
데이터 마이그레이션
물론, 모든 과정이 순탄했던 것은 아닙니다. 내부적으로도 개발에 어려움이 있었지만, 기억할 만한 도전적인 과제로는 데이터 마이그레이션도 그 중 하나입니다.
수십만 사용자의 자산과 거래 기록을 단 1건의 오류도 없이 새로운 시스템으로 옮겨야 했고 가능하면 전체 시스템을 중단하는 일은 피하고 싶었기에 일부 종목의 거래만 중단하면서 순차적으로 전환하는 방식을 선택했습니다. 시행착오가 없지는 않았지만 결과적으로 무사히 전환을 마쳤고 그 과정에서 얻은 경험은 소중한 자산이 되었습니다.
숫자로 증명된 성과
운영 환경에서 주문 처리 성능은 최소 10배 이상 높아지고, 주문 API 응답 시간은 최대 84% 단축되어 140ms에서 22ms를 기록하기도 했습니다(동기, 비동기 특성으로 인한 차이는 있습니다). 기능 측면에서도 Time-In-Force나 Best Bid Offer 같은 글로벌 거래소 수준의 주문 기능을 구현했습니다.
무엇보다 중요한 점은, 2024년 6월 신규 거래 시스템 배포 이후 현재까지 트래픽 급증이나 설계 결함으로 인한 시스템 장애가 전혀 없었다는 것입니다. 물론, 다른 이유로 코빗에 몇달 전 장애는 있기는 했었습니다만, 그 원인은 거래 시스템과는 무관했습니다. 새로운 거래 시스템 덕분에 코빗은 그간 신규 거래지원(코인 상장)이나 대규모 이벤트 진행 시 반복되던 고질적인 문제를 완전히 잊을 수 있었습니다.
마무리하며: 거래소다운 아키텍처
이번 시스템 재구축을 통해 저희는 진짜 가상자산 거래소다운 아키텍처를 만들었습니다. 초당 수만 건의 주문에도 끄떡없는 성능, 장애가 나도 바로 복구되는 복원력, 신속한 확장과 배포가 가능한 유연성, (레거시를 제거했다는 점에서) 개발의 생산성까지 목표를 달성했습니다.
이벤트 기반 MSA로 데이터 일관성과 시스템 확장성을 달성했고, 장애 허용 설계를 통해 복원력을 강화했습니다. 또한, 최고의 성능과 안정성을 위해 체결 엔진과 시세 처리 시스템을 Rust로 개발했습니다. 자세하게는 코빗 기술 블로그의 Go와 Rust, 코빗은 이렇게 나눠 씁니다에서 보실 수 있습니다.
코빗 기술 블로그를 통해 앞으로도 개발 후기와 실전 팁을 계속 공유하겠습니다. 지속적인 기술 혁신을 통해 더 안전하고 빠른 거래 경험을 제공하고 업계의 기술 발전을 선도해 나가겠습니다. 읽어주셔서 감사합니다.
- 1.
즉, 주식을 팔면 2거래일 후에 돈을 받을 수 있습니다. 2025년 10월 현재 뉴스에 따르면, 한국거래소와 예탁결제원은 현행 T+2 주기를 미국 증시처럼 T+1 주기로 바꾸는 것을 검토 중입니다.
- 2.
Event Sourcing: '주문 생성', '주문 취소' 등 모든 행위를 단순한 데이터 변경이 아닌, 시간 순서에 따른 이벤트로 저장하는 패턴입니다. 이 이벤트 기록만 있으면 언제든지 특정 시점의 시스템 상태를 정확하게 복원하고 전체 변경 과정을 추적할 수 있습니다.
- 3.
멱등성(Idempotency): 동일한 연산을 여러 번 수행하더라도 결과가 변하지 않고 항상 같음을 보장하는 성질입니다. 예를 들어, 네트워크 오류로 인해 동일한 '주문 생성' 요청이 두 번 전송되더라도, 시스템은 이를 한 건의 주문으로만 처리합니다.
