Upcoin 모의투자 거래소 - Redis를 이용하여 서비스 간 통신 문제를 해결

이원찬·2025년 5월 4일

드디어 중간고사가 끝났다. 시험 기간 내내 손도 못 대고 있던 Upcoin 모의투자 거래소 프로젝트의 서비스간 통신문제를 해결하였다. 곧 다가올 소프트웨어 축전에서 Upcoin 모의투자 거래소로 부스를 운영할 예정인데, 이를 대비해 진행한 유지 보수 과정에서 가장 걱정하던 문제를 해결한 경험을 기록으로 남기고자 한다.

시작 🚀

이전 Upcoin 모의투자 거래소 차트 시스템은 다음과 같이 동작하고 있었다.

  1. Node.js 서버가 웹소켓으로 Upbit에서 실시간 시세를 받아 1분봉 데이터를 만든다
  2. PHP는 클라이언트 요청이 오면 DB에서 과거 데이터를 가져오고
  3. 현재 진행 중인 분봉은 Node.js 서버의 HTTP API를 호출해서 가져온다
  4. 이렇게 완성된 데이터를 클라이언트에게 제공한다

처음에는 별 문제가 없었다. 각 서비스는 사용자의 요청을 잘 처리하고 있었다. Node.js는 열심히 WebSocket 메시지를 처리하고, PHP는 필요할 때마다 HTTP로 "지금 진행 중인 분봉 어떻게 되고 있어?"라고 Node.js 서버에 물어보면 그만이었다. 하지만 요청 프로토콜이 HTTP이다보니 데이터를 받아오는 데 약간의 지연이 발생했고, 이 부분을 어떻게 개선할지 고민하게 되었다.

소프트웨어 축전으로 인한 대규모 트래픽 대비

곧 다가올 소프트웨어 축전에서 Upcoin 모의투자 거래소에 많은 사용자들이 동시에 접속할 가능성이 있었다.

"Upcoin 모의투자 거래소 시스템이 이 정도 트래픽을 감당할 수 있을까?"

대규모 트래픽 대비 겸 테스트 서버에서 부하 테스트를 진행해보았다. 결과는 예상보다 좋지 않았다. 동시 접속자가 늘어날수록 Node.js 서버의 CPU 사용률이 급격히 올라갔고, 특히 PHP가 요청하는 /current-minute-data HTTP API 호출이 병목 지점으로 확인됐다.

생각해보니 PHP가 Node.js에게 HTTP 요청을 할 때마다 TCP 연결을 맺고, 헤더를 교환하고, JSON 응답을 파싱하는 등의 오버헤드가 발생한다. 그리고 더 큰 문제는 실시간성이었다. HTTP 요청은 '요청 시점'의 데이터만 가져올 수 있어서, 거래가 활발한 시간대에는 차트가 약간 지연되어 표시되는 문제가 있었다.

이틀 동안 고민 🤔

이틀동안 컴퓨터 앞에 앉아 계속 고민했다. 축전 당일에 거래소에 장애가 발생하여 체험을 못하고 방문자들이 가버리는 건 생각도 하기 싫다. 더 실시간성 있고 효율적인 통신 방법이 필요했다.
고민 끝에 세 가지 방안을 떠올렸다.

"Kafka를 도입해볼까?"

장점: 실시간 데이터 처리에 강함, 데이터 유실 없음
단점: 설치하고 운영하는 것이 너무 복잡함. 학교 서버에 새로운 시스템을 설치하기에 복잡하고 한번도 안해봄, 간단한 상태 공유에 Kafka는 과한 느낌

"gRPC는 어떨까?"

장점: HTTP보다 훨씬 빠름, Protocol Buffers로 직렬화하니 효율적
단점: 결국 '요청-응답' 모델이라 근본적인 문제 해결은 안 됨. 개발 복잡도도 증가

"Redis Pub/Sub 기능을 써볼까?"

장점: 실시간으로 데이터 변경을 알림 받을 수 있음
단점: 구독자가 연결되어 있을 때만 메시지 수신. PHP는 동기적이라서 항상 연결되어 있진 않음

Redis 캐시 개발 중 떠오른 아이디어 🌟

기존에 시세데이터를 MariaDB에 저장하였는데 관계형 데이터베이스라서 느려서 이를 redis로 바꾸는 작업 중 아이디어가 떠올랐다.
시세 데이터는 이미 Redis에 캐시하고 있었는데, 현재 캔들 데이터도 Redis에 캐시하면 되겠다는 생각이 들었다. 그리고 바로 Redis의 기본 기능인 Hash 자료구조와 HSET/HGETALL가 떠올랐다.
그래서 Redis를 사용하면:

  1. Node.js는 최신 분봉 데이터를 계산할 때마다 Redis에 HSET으로 저장
  2. PHP는 필요할 때 Redis에서 HGETALL로 가져오기만 하면 됨

너무 단순해서 오히려 고려하지 않았던 방법이었다. 고급 메시징 시스템 없이, 추가 인프라 없이, Redis라는 '공유 게시판'만으로도 충분했다. 이렇게 하면 실시간성도 향상될 것 같았다.

개발 시작!

Node.js에서는 HTTP API 코드를 떼어내고, 웹소켓 메시지 처리 로직에 Redis 업데이트 코드를 추가했다:

// 웹소켓 메시지 처리 중...
if (parsedData.type === 'ticker') {

    // Redis에 저장할 현재 캔들 데이터 객체 (모든 값을 문자열로)
    const dataToStore = { 
        time: formattedTime,
        open: String(candleData.open ?? '0'),
        high: String(candleData.high === -Infinity ? (candleData.open ?? '0') : candleData.high),
        low: String(candleData.low === Infinity ? (candleData.open ?? '0') : candleData.low),
        close: String(closePrice),
        volume: String(candleData.volume ?? '0')
    };

    // Redis에 Hash 형태로 저장! 
    await redisClient.hSet(redisKey, dataToStore); 
}

PHP 부분에서는 file_get_contents() 대신 Redis 호출로 교체했다:
// HTTP API 호출 대신
$current_candle_redis = $redis->hGetAll("current_minute_candle:KRW-" . $code);

테스트 서버에 배포하고 실험해보았다. 응답 시간이 확연히 빨라졌다! Node.js CPU 사용률도 눈에 띄게 안정되었다. 무엇보다 실시간 데이터 반영이 더 빨라져서 체결 메시지가 많을때 지연없이 최신 데이터를 볼 수 있게 되었다.

성과 & 결론 📒

운영서버에 배포한 후, 전반적인 서버 메트릭을 분석해보았다.

  1. API 응답 시간: 평균 15% 개선
  2. Node.js CPU 사용률: 피크 시간대에 30% 감소
  3. 실시간성: 데이터 업데이트가 거의 즉각적으로 반영됨
  4. 에러율: 거의 없음 (HTTP 요청 간헐적 실패가 사라짐)

'대규모 트래픽 환경에서 최신 1분봉 데이터 공유'라는 문제를 해결하기 위해 Kafka부터 gRPC, Redis Pub/Sub까지 다양한 기술들을 고민하였지만, 결국 가장 단순한 Redis HSET/HGETALL 방식을 선택하였고, 그 결과는 매우 좋았다.

이제 웹소켓 서버인 node.js의 코드가 변경되면 코드 반영을 위해 다운타임이 생기는 문제가 있는데 무중단으로 배포하는 방법에 대해서 탐구해봐야겠다.

profile
안녕하세요!

0개의 댓글