Upcoin 모의투자 거래소 MSA로 전환하여 문제 해결하기

이원찬·2025년 7월 7일
post-thumbnail

Upcoin 암호화폐 모의투자 거래소를 개발하며 겪은 문제들을 기록해봅니다

위 사진은 작년인 2024년 12월에 진행된 교내 모의투자 대회 거래소 화면입니다.
지금보다 거래 가능한 암호화폐 종목 수도 매우 적죠.

이 교내 모의투자 대회는 문제없이 마무리되었습니다.
단. 사용자 입장에서 보았을 때 입니다..ㅠㅠ
개발자(운영자)입장에서 보면 다양한 문제들이 발생했습니다. 심지어, 대회 진행에 영향을 줄 수 있는 문제도 발생 했었습니다.
또한, 대회 진행 중 대회 참가자들의 피드백도 많았습니다. 그래서 이번 아티클에서는 대회 진행 중 발생했던 문제와 피드백 받은 것을 구현하는 여정을 이야기해보겠습니다.

대회를 개최하며 발생했던 문제들이 셀 수 없이 많은데, 대표적으로 7가지(학교 인터넷 업비트 차단 이슈, 데이터 수신 끊김 & 지연 이슈, 사용자 자산 조회 지연 이슈, 지정가 주문, 랭킹, 커뮤니티...etc, 동시성(Race-condition), 원자성 (Atomicity) 이슈, 심각하게 누적된 기술부채, 무중단 코인 상장) 정도 소개해드겠습니다.
"학교 인터넷 업비트 차단 이슈"는 다행이 대회가 종료되고 문제가 발생했습니다. Upcoin 모의투자 거래소의 암호화폐 시세 데이터는 Upbit로부터 제공받아 전남고등학교 실시간 시세처리 서버에서 적절한 처리를 거친후 사용자에게 웹소켓을 통해 시세 데이터를 전송합니다.
그런데 2025년 1월 후반에 사용자에게 시세 데이터가 전송되지 않는 중대한 서비스 장애가 발생하여 확인해보니 학교 서버와 업비트 서버 간의 연결이 되지 않고 있었습니다.
curl로 http 패킷을 보내어 확인해본 결과 학교 방화벽 장비의 심층 패킷 분석(DPI)를 통해 Upbit로 접속하는 것을 감지하고 패킷을 드랍하는 것을 발견하였습니다.
원인을 추측하자면, 새학기가 되어 학교 방화벽 정책이 강화되며 모든 증권, 주식, 암호화폐, 게임 등 관련 사이트가 접속이 차단되도록 방화벽 정책이 적용되어 문제가 발생한 것으로 추측이 됩니다.
그래서 Oracle Cloud Infrastructure의 춘천 데이터센터의 서버 한대를 빌려 업비트에 대신 접속할 수 있는 메시지 브로커 서버를 만들고, 업비트의 시세 정보 데이터를 메시지 브로커 서버가 대신 받고 전남고등학교 실시간 시세 처리 서버에 전송하도록 하여 해결하였습니다.
호가(Orderbook) 데이터 수신 단절 이슈는 대회 중에 발생하였습니다.
다행이 학생들이 호가를 보는 방법과 호가가 무엇인지 몰라서 피해가 크진 않았습니다.
기존에는 시세데이터와 호가 데이터를 클라이언트와 하나의 연결로 송신하고 있었습니다.그러나 사용자가 급증해도 다행히 시세 데이터는 유저에게 송신이 안정적으로 되었지만, 호가 데이터는 송신 단절이 발생하였습니다.
이 문제를 해결하기 위해 시세데이터 송신부와 호가 데이터 송신부를 각각 나눠서 구성하였더니 해결되었습니다.
사용자가 특정 종목 트레이딩 페이지에 접속하면 시세데이터는 상단 티커에 다양한 종목 시세을 보여줘야 하니, 전 종목 시세를 수신하고, 호가데이터 같은 경우 현재 사용자가 보는 트레이딩 페이지의 종목 호가 데이터만 수신받으면 되니 특정 종목만 구독하여 수신을 받도록 설계하였습니다. 시세 데이터 처리 지연 문제는 사용자에게는 큰 문제는 아니였지만 개발자(운영자)입장에서는 1초에 수천건의 거래가 발생하는 암호화폐 시장에서 단 한 건이라도 거래 데이터가 느리게 처리, 즉. 지연이 된다면 큰 문제 였습니다. 이 문제를 해결하는 과정은 "관계형 DB인 MariaDB와 비관계형 인메모리 DB인 Redis와 성능차이 분석"이라는 소논문을 참고해주세요.
사용자 자산 조회 지연 이슈는 대회 중에 발생하여 거래소 시스템 전반에 영향을 주었던 이슈인데요.
자산 조회 지연 이슈를 어떻게 해결하였는지 설명해보겠습니다. 기존 시스템은 위와같이 거래내역을 기반으로 값들을 더하고 뺴고 곱하여 보유자금, 총평가, 보유자산, 평가손익을 계산하고 있었습니다. 보유자금, 총평가, 보유자산, 평가손익을 계산하는데 걸린 시간은 평균 300ms 소요가 되었습니다.
원인을 조사해본 결과, 거래기록을 바탕으로 계산하기 때문에 전체 테이블 스캔이 발생하게 됩니다. 그래서 IO 부하가 심해지며 처리시간이 증가되는데, 계산하는데 300ms라는 긴 시간이 소요되게 됩니다.

또한 거래 내역을 바탕으로 하기 때문에 트랜젝션 ACID 미준수로 거래내역에 데이터 오염이 발생할 경우 비정상적으로 평가손익 등 표시되는 문제가 발생하였습니다.이 문제를 해결하기 위해 거래가 발생할 때마다 평균매수단가, 잔액을 미리 계산해두어 Wallet Table에 저장하도록 수정했습니다.
사용자가 자산을 조회할떄 Wallet Table을 참조하여 단순 산술 연산만 하면 되기 때문에 기존 300ms에서 30ms로 감소하는 성능 개선을 하였습니다.
지정가 주문은 대회 중 발생했던 문제는 아니고 대회를 개최하며 받은 피드백입니다.
앞서 설명했다시피, 메시지 브로커를 통해서 체결과 호가 데이터를 적절히 처리한 후, 사용자에게 송신을 하게 됩니다.
지정가 주문은 Redis에 저장되여 다음과 같이 처리하게 됩니다.

매수 주문일경우:
현재 시세보다 높거나 같은 가격의 주문 체결
매도 주문일경우:
현재 가격보다 작거나 같은 가격의 주문 체결

위 사진은 지정가 매도 & 매수 코드 입니다. 1초에 수천건의 거래를 처리해야하니 동시성과 원자성을 생각하며 코드를 작성하였습니다. 시세 데이터가 들어올때 마다 위 지정가 매도 & 매수 함수를 실행시키게되며 Redis에서 지정가 주문을 가져온 후 가장 현제 가격과 근접한 주문 부터 처리하게 위해 배열의 값들을 적절히 배치한 후 다시 한번 Redis에 이 주문들이 있는지 재확인합니다. 재확인 하는 이유는 1초가 수천건의 거래가 발생하게 되고 동시에 거래가 발생하여 동시에 지정가 매수 또는 매도 함수가 실행되게되면 Race-condition 상태가 되어 주문이 중복처리가 되어 자산에 오입금이 될 수 있기 때문에 하나의 작업을 처리하고 계속 주문이 존재하는지 확인하는 작업을 추가하였습니다.다음으로, 자신의 현재 포지션과 손익을 공유할 수 있는 커뮤니티 개발 스토리를 이야기해보겠습니다. 모의투자 대회가 끝나고 커뮤니티가 추가되었습니다. Upcoin 모의투자 거래소의 커뮤니티는 단순히 게시물을 작성하고 댓글을 작성하는 것을 넘어 자신의 현재 포지션을 공유하여 자신의 투자 결정이 옳았는지 다른 트레이더의 생각은 어떠한지 상호작용할 수 있는 공간입니다.
위 사진은 자신의 포지션 공유와 의견을 작성하는 페이지인데 이 페이지를 개발하면서 어려움이 있었습니다.
사용자의 포지션 데이터를 <input type="hidden">을 이용하여 서버로 데이터를 보낸다면 조작될 위험이 매우 높기 때문에 클라이언트에서 조작이 불가능하게 전달할 방법이 필요했습니다.사용자가 자신의 포지션 공유와 함게 글 작성페이지에 오게 되면 그 즉시 사용자의 현재 포지션 데이터를 계산하여 셰션 변수에 저장합니다.
즉. 쉽게 말하면 기존에는 사용자의 포지션 데이터를 클라이언트(사용자의 기기)에 저장했지만 셰션 변수에 저장하는 것으로 변경하여 서버에 저장하도록하여 수정 불가능한 구조로 변경하여 조치하였습니다.모의투자 거래소에서 해결되지 않으면 심각해지는 문제인 거래 과정에서의 동시성 원자성 이슈를 해결하는 과정을 이야기 해보겠습니다.동시성 이슈가 발생한 시장가 매수 거래 과정에 대해 상세히 설명드리겠습니다.
가장 먼저, 사용자가 시장가 매수 주문을 요청하는 것으로 전체 프로세스가 시작됩니다.
서버는 이 요청을 받아서, 가장 먼저 정상적인 요청인지 검증하는 단계를 거칩니다. 예를 들어, 주문 수량에 문자를 입력했거나 0을 입력하는 등의 비정상적인 값을 걸러내고, 정상적으로 로그인된 사용자인지 확인합니다.
요청이 유효하다고 판단되면, 다음으로 현재 코인의 시세를 확인해야 합니다. 저희는 이 과정에서 인메모리 데이터베이스인 Redis를 활용했습니다. 이를 통해 중요한 데이터가 저장되는 관계형 데이터베이스에 직접적인 부하를 주지 않으면서도, 빠르고 정확하게 현재 시장 가격을 조회하여 사용자에게 제공합니다.
코인 시세를 확인한 후에는, 사용자의 원화 계좌에 주문을 처리할 만큼 충분한 잔액이 있는지 조회합니다. 만약 '주문 수량'과 '현재 시세'를 곱한 금액보다 잔액이 부족할 경우, 거래를 중단하고 사용자에게 잔액 부족 알림을 보내게 됩니다.
잔액이 충분하다면, 본격적인 거래 처리 단계로 넘어갑니다. 먼저 이 거래를 기록하고, 거래에 따른 수수료와 사용자의 평균 매수 단가 등을 계산합니다.
계산이 완료되면, 사용자의 원화 계좌에서 매수 금액만큼을 차감하고, 코인 계좌에는 매수한 수량만큼의 코인을 추가해줍니다.
마지막으로, 앞서 계산된 수수료는 거래소 분석을 위해 별도의 관리자 계좌로 입금하여 자산을 명확하게 분리하여 관리하도록 구현했습니다.지정가 매수 거래 과정에 대해 설명드리겠습니다.
먼저 시장가 거래 과정과 동일하게 정상적인 요청인지 확인합니다. 요청이 적절하다면 잔액이 충분한지 조회합니다.
잔액이 충분하다면, 이 주문은 즉시 체결되지 않고 저희의 주문 원장(Order Book)으로 사용되는 Redis에 등록됩니다. 이때 저희는 Redis의 Sorted Set 자료구조를 활용하여, 매수 주문은 '높은 가격 순'으로 효율적으로 정렬하여 저장합니다.
그렇다면 주문은 언제 체결될까요? 저희 Node.js로 작성된 실시간 데이터 처리 서버는 업비트(UPbit)로부터 실시간 시세 데이터를 웹소켓을 통해 수신합니다.
새로운 시세 데이터가 들어올 때마다, 체결 엔진(Matching Engine)이 작동합니다. 체결 엔진은 현재 시장 가격을 기준으로, Redis에 저장된 주문들을 즉시 조회합니다. 구체적으로, '사용자가 지정한 매수 희망 가격'이 '현재 시장 가격'보다 높거나 같은 모든 매수 주문들을 체결 대상으로 찾아냅니다.
처리되어야 할 주문들은 시장가 거래와 똑같이 거래를 기록하고, 거래에 따른 수수료와 사용자의 평균 매수 단가 등을 계산합니다.
계산이 완료되면, 사용자의 원화 계좌에서 매수 금액만큼을 차감하고, 코인 계좌에는 매수한 수량만큼의 코인을 추가해줍니다.
마지막으로 거래기록을 기록하고 관리자 계좌에 수수료를 입금하여 거래 과정이 종료됩니다.

하지만 개발 과정에서 문제점을 발견했습니다. 바로 원자성(Atomicity) 문제입니다.
앞서 말했던 일반적인 시장가 매수 거래 흐름을 보여줍니다. 잔액을 조회하고, 원화를 차감한 뒤, 코인을 지급하는 일련의 과정입니다. 하지만 이 과정들이 분리되어 있다면 어떤 문제가 발생할까요?
만약 원화 계좌에서 돈이 차감된 직후, 서버에 장애가 발생해 코인 지급에 실패한다면 어떻게 될까요? 사용자는 돈을 잃고 코인은 받지 못하는, 데이터 부정합 상태에 빠지게 됩니다. 저희는 이처럼 '모두 성공하거나, 모두 실패해야 하는' 원자성이 보장되지 않는 문제를 발견했습니다.더 심각한 문제는 바로 동시성 문제였습니다. 위 사진은 실제로 발생했던 경쟁 상태(Race Condition) 시나리오를 보여줍니다.
한 사용자의 잔액이 50,000원인 상황을 가정해 보겠습니다.
10시 1분 1초, 사용자가 1만 원 매수 주문을 요청합니다. 저희 서버는 잔액이 5만 원임을 확인하고, 이 거래를 처리하기 시작합니다.
하지만 바로 그 다음 순간인 10시 1분 2초, 첫 번째 거래가 데이터베이스에 완전히 반영되기 전에, 또 다른 1만 원 매수 주문이 들어옵니다. 비동기적으로 동작하는 서버는 이 두 번째 요청에 대해서도 잔액을 조회하는데, 이때 아직 잔액이 5만 원으로 조회됩니다.
결과적으로 두 개의 주문이 모두 '잔액 충분'으로 판단되어 처리됩니다. 사용자는 총 2만 원을 사용했지만, 데이터베이스에는 최종 잔액이 4만 원으로 잘못 기록되는, 돈이 증발하는 것과 같은 심각한 데이터 정합성 오류가 발생했습니다.

해결 방법은 무엇일까?

바로 락(lock)을 활용했습니다.
락(lock)에는 비관적 락(Pessimistic Lock)과 낙관적 락(Optimistic Lock)이 있습니다.
비관적 락은 말 그대로 ‘비관적’으로 접근하는 방식입니다. 이 데이터의 Row를 다른 프로세스가 분명히 접근할 것이라고 가정하고, 아예 먼저 락을 걸어 다른 프로세스의 접근을 차단합니다. 주로 동시성이 매우 높은 환경에서 사용됩니다.
반면, 낙관적 락은 ‘낙관적’으로 접근하는 방식입니다. 다른 프로세스가 접근하지 않을 것이라고 가정하고, 별도의 락을 걸지 않고 우선 작업을 진행합니다. 이후 저장 시점에 충돌이 발생했는지를 검사해, 문제가 없으면 정상 저장하고 충돌이 나면 다시 시도하거나 에러를 반환합니다.
주로 충돌 가능성이 낮고 읽기 작업이 많은 환경에서 사용됩니다.
그러나 저는 이 문제를 해결하기위해 비관적 락(Pessimistic Lock)을 사용하였습니다.

원자성 문제를 해결하기 위해, 트랜잭션(Transaction)을 도입했습니다.$conn->begin_transaction(); 이 구문으로 거래의 시작을 선언하고, 관련된 모든 데이터베이스 작업을 하나의 try 블록 안에 묶었습니다. 만약 이 과정 중 단 하나라도 실패하여 예외(Exception)가 발생하면, catch 블록 안에 있는 $conn->rollback();이 실행됩니다. 이 롤백은 try 블록 안에서 실행된 모든 거래 작업들을 없었던 일로 되돌려, 데이터 정합성 오류가 발생되지 않도록 하였습니다. 이것이 바로 '모두 성공하거나, 모두 실패하거나'를 준수하는 원자성입니다.

이제 동시성 문제가 남았습니다. 동시성이란, 여러 주문이 거의 동시에 들어왔을 때, 각 트랜잭션이 서로 겹치면서 잘못된 잔액을 읽어가는 '경쟁 상태'가 발생하는 것을 의미합니다.
저는 동시성 문제를 이 문제를 해결하기 위해, 바로 이 트랜잭션 안에서 SQL의 SELECT 구문 뒤에 FOR UPDATE 라는 옵션을 추가했습니다.
이 SQL 구문 뒤에 FOR UPDATE를 추가하면 데이터베이스 서버에 다음과 같이 말하는 것과 똑같습니다.

"지금부터 이 트랜잭션이 user_id가 1번인 사용자의 KRW 지갑 데이터를 조회하고 수정할 것이다. 이 트랜잭션이 COMMIT 또는 ROLLBACK으로 완전히 끝날 때까지, 다른 어떤 트랜잭션도 이 데이터에 접근하지 못하도록 잠금(락)을 걸어라!"

즉, 먼저 시작된 트랜잭션이 해당 데이터에 배타적 잠금(Exclusive Lock)을 걸게 됩니다.
동시에 들어온 다른 주문은 앞선 트랜잭션이 끝날 때까지 줄을 서서 기다리게 됩니다. 이렇게 비동기적인 요청 흐름을 데이터베이스 단에서 강제로 동기적으로 처리되도록하여, 한 번에 하나씩 순서대로 안전하게 처리하도록 하였습니다.

다음으로 “심각하게 누적된 기술 부채”에 대해 이야기하겠습니다.

학업과 모의투자 거래소의 유지보수를 병행해야 했기 때문에, 제한된 시간 안에 빠르게 수정하고 조치해야 했습니다.
그러다 보니 코드의 품질을 고려하기보단 당장 돌아가게 만드는 데 집중할 수밖에 없었고, 그 결과 시간이 지나면서 점점 수정하기 어려운, 지저분한 코드가 되어버렸습니다.

결국 Upcoin 모의투자 거래소는 복잡하고 엉켜버린 코드 덩어리가 되어버렸고, 유지보수는 점점 어려워졌습니다. 이렇게 쌓이고 쌓인 코드 문제는 결국 기술 부채로 이어졌습니다.
기술 부채는 보통 설계 부채, 코드 부채, 테스트 부채, 인프라 부채 이렇게 네 가지로 구분할 수 있습니다.
그 중에서도 Upcoin 모의투자 거래소는 설계 부채와 코드 부채가 주된 문제였습니다.
이 두 가지가 계속 누적되면서 결국 서비스 장애까지 발생하게 되었습니다.
심각하게 누적된 기술 부채를 청산
하기 위해, 저는 먼저 문제의 원인을 찾는 것부터 시작했습니다.
가장 큰 원인은 코드가 너무 거대하고 복잡해진 점이었습니다.
어느 한 부분을 수정하면 다른 부분에서 오류가 발생했고, 서로 얽힌 의존성으로 인해 어떤 문제가 터질지 쉽게 예측조차 할 수 없는 상황이었습니다.

생각해낸 해결방법은 "복잡하고 엉켜버린 레거시 코드를 다시 리펙토링 작업을 하고, MSA(Micro-Service-Architecture)를 도입하여 안정적이고, 유지 관리 보수가 쉽게 아키텍쳐를 변경하자" 였습니다.

위 사진은 MSA로 전환 후, 각 서비스를 다이어그램으로 시각화한 사진입니다. 각 기능을 작은 서비스로 쪼개서 유지 관리 보수가 이전 보다 쉬워졌고, 코드 변경이 되어도 서비스 간의 의존성을 줄여 새로운 코드를 배포 시 예상되는 문제를 쉽게 예측 할 수 있게 되었습니다.
또한, 이전에는 어떤 기능에 버그가 생겨 새로운 코드로 사용자들에게 제공해야할때 서버에서 실행 중인 코드를 수정하고, 전체 서버 재시작을 해야했지만, MSA 도입을 통하여 문제가 생긴 서비스만 재시작하면 되니, 이는 사용자 경험 향상으로 이어졌습니다.다음으로는 MSA로 전환하여 문제가 드러난 "간혈적 서버 간 지연시간 증가"에 대해 말해보겠습니다. 앞서 말했다시피, 메시지브로커가 Upbit로부터 시세데이터를 수신하고, 전남고등학교 실시간 시세 처리 서버로 전송합니다.
일반적인 상황이라면 P2P Direct 연결로 낮은 지연시간으로 빠르게 시세데이터를 전송했겠지만, 방화벽 차단 등과 같이 미상의 이유로 인해 서버들 간의 P2P 연결이 설립되지 않았다면, 연결을 유지하기 위해 해외의 전세계에 위치한 Relay Server를 거쳐서 통신하게 됩니다.이 사진은 전세계에 위치한 Relay Server 지도인데요. 만약 메시지 브로커 서버가 업비트로부터 시세데이터를 받고 전남고 실시간 시세처리 서버에 전달을 해주어야 하는데 P2P 연결이 설립이 안되었을 경우에는 대한민국에서 가장 가까운 Tokyo에 위치한 Relay Server에 연결하게됩니다. Tokyo의 Relay 서버가 혼잡하면 홍콩에 있는 Relay Server로 연결이 됩니다.
이렇게 해외에 있는 Relay 서버에 연결하게 되면 메시지 브로커 서버에서 전남고등학교 실시간 시세처리서버까지 60ms ~ 140ms (0.06 ~ 0.14초) 매우 긴 지연 시간이 생기게 됩니다.

그럼 어떻게 지연시간을 단축시킬까요?
바로 "직접 가장 최적의 위치에 Relay 서버를 구축하자!"입니다.

메시지 브로커서버는 강원도 춘천 삼성 SDS 데이터센터에 위치하고, 시세데이터 처리 서버는 광주 전남고등학교에 위치합니다.
각 서버가 위치한 춘천과 광주를 일직선으로 직선을 긋게 되면 대전광역시와 세종특별자치시를 지나게 됩니다.
그럼 대전광역시 또는 세종특별자치시 주변에 서버를 구축하면 지연시간이 최단시간으로 되겠죠? 그래서 세종특별자치시에 위치한 네이버 클라우드 플랫폼의 네이버 각 세종 데이터센터에 서버를 구축하기로 결정했습니다.
보시는 사진처럼 수많은 서버들 중에 표시한 서버를 빌려 Tailscale Relay 서버를 구축하였습니다.
가장 상단에 있는 사진은 Relay 서버를 구축하지 않아 해외로 연결되어 통신을 했을 경우 지연 시간입니다. 최고 148ms 에서 최저 68ms로 지연시간이 굉장히 높을 겻을 볼 수 있죠.
반면에 하단 좌측에 있는 사진은 세종에 구축 Relay 서버를 이용하여 통신한 지연시간 인데요. 27.5ms 0.027초가 걸립니다. 하단 우측에 있는 사진은 Relay 서버를 거치지 않고, P2P 연결이 된 상태입니다. 15.8ms로 가장 지연시간이 짧은 것을 볼 수 있습니다.

해외에 있는 서버를 이용했을 경우보다 지리적으로 최적의 위치에 구축된 Relay Server를 이용한 통신의 지연시간이 최대 약 4배 가량 단축이 된 것을 볼 수 있고, 어떤 상황에서는 Direct 연결보다 빠른 지연시간을 보여줄 때도 있었습니다.

새로운 코인(종목)을 상장할때 서버를 정지 시키지 않고 상장하게하는 제작 스토리를 이야기 해보겠습니다.
초기 시스템에서는 실시간 시세를 받아올 코인 목록을 DEFAULT_COINS라는 배열에 하드코딩하여 관리했습니다. 이 방식은 간단해 보이지만, 매우 치명적인 한계를 가지고 있었습니다.
새로운 코인을 상장하려면, 개발자가 직접 코드의 이 배열에 코인 심볼을 추가하고, 변경사항을 적용하기 위해 서버를 재시작해야만 했습니다.
서버 재시작은 곧 서비스 중단을 의미합니다. 단 몇 초, 몇 분의 중단이라도 실시간으로 변동하는 암호화폐 시장에서는 서비스 중단은 사용자에게 손실로 이어집니다. 사용자는 이 시간 동안 원하는 가격에 매수하거나 매도할 기회를 놓치게 되고, 이는 곧 투자의 손실로 직결될 수 있는 매우 심각한 문제였습니다. 저는 이러한 방식이 안정적인 서비스를 제공해야 하는 거래소에 적합하지 않다고 판단하여 이것을 해결하고자 했습니다.

정적인 배열 대신, 서버 메모리에서 동작하는 Set 자료구조를 도입하여 현재 구독 중인 코인 목록(activeSubscriptions)을 동적으로 관리하도록 구조를 변경했습니다.
이 addSubscription 함수가 무중단 상장 프로세스를 명확하게 보여줍니다.
먼저, 상장할 코인이 결정되면 이 함수가 호출되어 메모리에 있는 activeSubscriptions Set에 새로운 코인 심볼을 즉시 추가합니다.
다음으로, 현재 업비트와 연결된 웹소켓 연결이 살아있는지 확인합니다.
연결이 활성화 상태라면, 업데이트된 Set의 전체 코인 목록을 담아 새로운 구독 요청 메시지를 생성합니다.

이 생성한 새로운 메시지를 새롭게 연결한 웹소켓을 통해 전송합니다. 새롭게 연결한 웹소켓에 신규 상장한 코인의 시세 데이터가 수신되기 시작하면 기존의 웹소켓 연결은 끊고 중복된 메시지를 제거합니다.
이렇게 핫 스왑(Hot Swap) 방식을 통해 중단없는, 완벽한 무중단으로 코인 상장을 구현하였습니다.

모의투자거래소를 운영하며 광주 지역 학생들의 투자 분석에대해 설명하겠습니다.
이 그래프는 시간대별 거래량 추이 그래프입니다.
그래프를 보면 특히, 오전 11시엔 4억원, 10시엔 3.8억원으로 가장 거래량이 많습니다.위 그래프는 상위 30 자산 별 총 손익 그래프입니다.
그래프를 보면, 가장 손익이 많은 자산은 SOPH, KAITO 입니다. 각각 SOPH는 약 4.1억원, KAITO는 약 2.7억원으로 다른 자산보다 이익을 많이 얻을 것을 볼 수 있습니다. SOPH와 KAITO 자산의 공통점은 단기적으로 크게 가격이 오르거나 내리는 등 변동성이 많은 자산이었습니다. 그래서 이 단기적인 변동성을 이용한 투자하였기 때문에 손익 많은 것으로 추정됩니다.위 그래프는 상위 30 자산별 총 거래대금 그래프입니다.
그래프를 분석하면, 비트코인이 60억원으로 다른 코인 자산보다 많은 것을 알 수 있습니다. 이 이유는 비트코인이 다른 코인보다 인지도가 높은 코인이라 암호화폐 투자에 모르는 사람도 "일단 사볼까"라는 생각으로 거래를 하게되어 높은 것으로 추정됩니다.위 그래프는 투자 유형 별 총 손익입니다. 평균적으로 단기 투자자는 1억원 손실, 장기 투자는 0.2억원 손실을 기록했습니다. 단기 투자자는 잦은 거래로 인한 수수료로 장기 투자자보다 손실을 더 많이 기록 한 것으로 추측됩니다.다음으로는 투자 유형별 사용자 수 입니다. 전체 투자자 중 단기 투자자는 350명, 장기 투자자는 110명, 구별 할 수 없는 투자자는 255명 이었습니다.다음으로는 대회 참여 학생들의 학교 분포 입니다.
1위로는 당연하게도 전남고등학교입니다. 그다음, 송원고, 광덕고, 석산고, 광주여고, 상일여고, 조대여고,전남여고, 수피아여고, 기타 순 이었습니다.

다음으로는 Upcoin 모의투자거래소의 실적을 설명하겠습니다.

Upcoin 모의투자 거래소 서비스 오픈 후, $1150 수익과 1,160명 사용자를 확보했습니다.그래서 이 $1150의 수익 어떻게 처리하였는지 설명드리겠습니다.
매출 $1150달러의 세금 10%, $115 납부 후 운영비 $384 제외한 결과 순수익은 $651입니다.
$651 중 50%는 나스닥 상장 상위 100기업에 투자하는 ETF인 KODEX 미국 나스닥 100에 투자를 하고 있고, 20%는 저축, 나머지 30%는 운영비로 사용하고 있습니다.날짜별 수익 누적 추이 그래프를 보면, 5월 중순까지는 완만한 증가세를 보이다가 6월부터 급격히 상승하는 양상을 확인할 수 있습니다. 이는 5월 후반 광주소프트웨어축전에서 Upcoin 모의투자거래소를 활용해 부스를 운영한 영향으로, 접속자 수가 증
가하면서 수익 또한 함께 상승한 것으로 추정됩니다.날짜별 누적 사용자 증가 추이를 살펴보겠습니다. 날짜별 수익 누적 추이 그래프와 같이 5월 후반에 광주소프트웨어축전에서 Upcoin 모의투자거래소를 활용한 결과로 사용자가 급격히 증가하는 것을 볼 수 있습니다.

그래서 결론적으로 Upcoin 모의투자 거래소의 성장 관성을 갖게 해준 것은 광주소프트웨어축전에서 Upcoin 모의투자거래소를 활용해 부스를 운영 것이 크다고 볼 수 있습니다.

마치며

작년에 개최한, 교내 모의투자 대회를 성공적으로 마치고 세특에는 ‘성공적으로 프로젝트를 마무리했다’는 기록이 남았다. 하지만 나에게 그 기록은 끝이 아니라, 해결해야 할 문제 덩어리였다. 사용자들이 남긴 피드백과 대회 중 발생했던 아슬아슬한 문제들을 해결해야 했었다. 특히, 동시에 여러 주문이 들어올 때 자산이 꼬일 수 있었던 ‘동시성 이슈’는, 모의투자 거래소 서비스에 대한 신뢰가 한순간에 무너질 수 있었던 중대한 문제였다.

다양한 문제를 해결하는 과정은 단순한 버그 수정을 넘어, 소프트웨어를 다루는 개발자로서의 자세를 다시 생각하게된 계기였다. 작년의 나는 ‘어떻게든 돌아가게 만들자’는 생각에 급급했지만, 대회를 마치고, ‘광주’라는 더 큰 규모를 생각하니, 생각이 달라졌다. 동시성 문제를 해결하기 위해 추가한 FOR UPDATE 한 줄은 단순히 코드가 아니라, 900명이 넘는 사용자의 자산을 지키는 ‘안전장치’였고, 복잡한 코드를 정리하고 MSA 구조로 전환한 것은 나중에 더 많은 사용자가 유입되고, 새로운 기능을 추가하더라도 안정적으로 서비스될 것이라는 보험 같은 존재였다.

가장 뿌듯했던 순간은 작년 세특에 ‘개선을 계획함’이라고 적었던 것을 실현했을 때이다. 거래 기록을 기반으로 자산을 계산하던 비효율적인 방식은, 광주 전체 학생들을 대상으로 하기엔 무리였다. 이를 해결하기 위해 설계한 ‘Wallet Table’이 적용되어, 300ms가 걸리던 자산 조회가 30ms로 단축되는 성능 향상이 있었을 때, “사용자들에게 빠르고 안정적인 서비스를 제공할 수 있겠구나”라는 생각이 들어, 광주 전체 학생들을 대상으로 대회를 열어도 걱정이 없겠구나라는 생각이 들었다.

학교 교내에서 시작한 작은 모의투자 프로젝트가 광주광역시 고등학생 200명 이상이 참여하는 대회, 가입 유저 수가 900명이 넘는 서비스로 성장하는 것을 보며, 내가 만든 모의투자 거래소 안에서 학생들이 웃고, 아쉬워하고, 투자 전략을 공유하는 모습을 보며 단순히 소프트웨어를 만드는 것을 넘어, 모의투자 거래소의 운영 모토를 ‘서비스를 만들고 유저 경험을 설계하며 책임지고 관리한다’라고 설정하게 되었다. 전에는 개발자는 단순히 소프트웨어를 개발하고 운영하는 역할이라고 생각했지만, 이제는 소프트웨어를 개발하고 관리하는 능력뿐만 아니라, 소프트웨어를 만들고 운영하는 것도 중요하다고 생각한다.

더 나아가, 소프트웨어를 개발하고 운영하는 것을 넘어 기록하는 것도 중요하다고 생각한다.
이렇게 계획을 세우고, 계획한 것을 현실로 만들며, 현실로 만든 것을 이 글처럼 블로그에 회고하는 습관을 들이는 것이 진정한 소프트웨어 엔지니어라고 생각이 들었다.

profile
안녕하세요!

0개의 댓글