ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 토스증권 실시간 시세 적용기 - 토스(박성우)
    세미나, 영상 요약정리 2022. 7. 9. 00:01
    728x90

    https://www.youtube.com/watch?v=WKYE-QtzO6g 

     

    토스 증권 마켓 플랫폼 팀에서 백엔드 엔지니어로 재직 중인 박성우 개발자님의 영상을 정리한 내용입니다.

     

    주제

    - 토스 증권 MTS(Mobile Trading System)의 실시간 시세를 개발하면서 겪었던 이슈들과 문제 해결 과정

    - 증권사 개발이라고 하면 막연히 도메인의 어려움과 레거시가 떠오르지만 토스에서는 어떤 일을 하고 어떤 것들에 집중하는지 소개합니다.

     

    증권 도메인 소개

    주식 시장에서 구매자와 판매자들은 거래를 하기 위해 서로 사거나 팔고 싶은 가격을 부릅니다.

    이를 "주문호가"라고 하는데 주문 호가 중 일치하는 상대 주문이 존재하는 경우에 거래가 체결됩니다.

    이렇게 성사된 거래 가격들이 쌓인 것들이 우리가 흔히 보는 주식 차트의 체결시세가 됩니다.

     

    실시간 시세

    투자자들은 가장 빠르게 최신의 시세와 호가 정보를 선호합니다.

    증권사들은 거래소나 시세 제공처로부터 가능한 지연 없이 실시간으로 시세를 전달하고자 합니다.

    이를 일반적으로 실시간 시세라고 부릅니다.

     

    국내 종목은 모든 증권사에서 무료로 제공합니다.

    해외 종목은 조건부 무료 혹은 유료로 제공합니다.

    토스 증권에서는 실시간 시세 정보를 조건 없이 무료로 제공합니다.

     

    Polling과 WebSocket

    처음부터 실시간을 도입할 예정은 없었고 API Polling 방식을 채택하였습니다.

    이유는 토스증권의 타깃은 주식 초심자들을 주요 타깃으로 보고 이들은 시세 정보의 실시간에 크게 민감하지 않을 것이라는 가설이 존재했습니다.

     

    하지만 론칭을 앞두고 사용자에게 가능한 실시간 시세를 제공해야 한다는 의견에 팀원들이 공감하였으며 WebSocket을 채택하게 됩니다.

     

    이외에 Server Side Event도 고려되었지만 이후 양방향 통신 기능 확장성을 위하여 WebSocket을 채택하였습니다.

     

    다만 SSE가 방화벽 관련 이슈가 덜 발생하기 때문에 단방향 통신인 시세 시스템은 SSE도 충분히 고려해볼 만할 것 같습니다.

     

    WebSocket상황이 비정상적인 상황에서는 미리 개발한 Polling시스템을 적극 활용함으로써  Fallback System으로 활용됩니다.

     

    전체 시세 시스템 아키텍쳐

    국내 시세 정보는 한국거래소에서 생성되며 해외 정보는 연합인포맥스로부터 제공받습니다.

     

    토스 증권은 현재 Unix C 기반의 원장 시스템과 K8s Java 기반의 MTS App 영역이 서로 다른 기술 스택으로 구성되어 있습니다.

     

    원장에서 수신받은 시세 데이터를 Java 기반 App을 거쳐 Client로 제공되기 위해서는 C 기반의 원장 시스템에서 Java App으로 시세를 전달하기 위한 매개체가 필요했고 이를 Kafka를 활용하여 해결했습니다.

     

    망분리 요건을 고려한 인프라 구조

    전자금융 감독규정의 망분리 요건을 준수하기 위해, WebSocket 서버 상단의 네트워크 홉을 줄이기 위하여 위와 같은 인프라 구조를 채택하였습니다.

     

    웹소켓 서버는 공개된 서버로 DMZ(Demilitarized Zone) 영역에 배치됩니다.

    보통 우리에게는 DMZ 비무장지대로 더 친숙한 용어일수도 있습니다.

    증권사 내부에 직접 접속하지 말고 DMZ라는 분리된 네트워크 공간을 통해 웹 서버에 접근하는 방식입니다.

     

    N개의 서버가 각각 고유한 호스트 명을 가지고 있습니다.

    로드밸런싱 및 라우팅 역할을 위한 라우팅 서버를 별도로 구성하여 서버-클라이언트 간 연결 정보를 Redis를 통해 관리하도록 하였습니다.

     

    Spring WebSocket With Stomp

    WebSocket 서버는 Spring WebSocket과 Stomp Protocol을 활용하여 구현되었습니다.

    Stomp는 WebSocket에서 별도의 프로토콜 구현 없이 빠르게 Pub/Sub 기능을 구현하기에 가장 좋은 선택지였습니다.

     

    사용되는 topic은 목적지가 종목코드인 유형 / 사용자인 유형으로 2가지로 구분됩니다.

    종목코드 유형의 topic은 종목의 현재가, 호가, 시장정보 등을 전달합니다.

    사용자 유형의 topic은 보유종목 등 개인 자산의 갱신에 사용됩니다.

     

    사용자가 종목별 시세를 수신받는 과정

    클라이언트는 라우팅 서버를 통해 연결할 WebSocket 서버의 호스트 정보를 획득합니다.

    획득한 WebSocket 서버로 연결을 시도하고 실패 시 Polling 방식의 API를 사용합니다.

    연결에 성공하면 갱신된 커넥션 정보를 라우팅 서버까지 전달합니다.

    라우팅 서버는 로드밸런싱을 위한 서버별 커낵션 정보를 Redis에서 관리합니다.

    클라이언트는 웹소켓을 통해 화면에 노출되는 종목들에 대해 필요한 토픽을 구독 요청합니다.

    수신된 시세 정보를 kafka를 통해 전달받아 각 종목을 구독하고 있는 클라이언트에게 시세 정보를 전달하게 됩니다.

     

    사용자별 자산 정보를 갱신하는 과정

    초기 연결과정은 기존과 동일합니다.

    WebSocket 접속을 위한 핸드 셰이크 과정에서 사용자 ID를 획득하게 됩니다.

    웹소켓 서버가 라우팅 서버에 커넥션 정보를 전달하면 사용자 ID를 기준으로 사용자별 접속 서버 정보를 Redis에 관리합니다.

    원장에서 매매체결 혹은 입출고 등으로 자산 정보의 변경이 발생하면 라우팅 서버에 해당 이벤트를 전달합니다.

    라우팅 서버는 사용자별 접속 정보를 기준으로 실제 사용자가 연결된 웹소켓 서버에 해당 정보를 포워딩하게 됩니다.

    웹소켓 서버는 해당 사용자에게 자산 정보 갱신을 지시하게 되고, 사용자는 자산 API 조회를 통해 갱신된 자산 정보를 조회할 수 있습니다.

     

    Kafka Latency 최적화

    C 기반의 원장 시스템으로부터 시세를 받아오기 위해 Kafka를 적용하면서 지연속도 최적화가 필요했습니다.

    지연속도 개선을 위해 설정값으로 튜닝할 수 있는 요소는 compression type과 acks 2가지가 있습니다.

     

    acks부터 살펴보겠습니다.

    all은 leader와 replica에 모두 저장되면 응답합니다.

    1은 leader에만 저장되면 응답합니다.

    0은 leader에 저장됨도 확인하지 않고 응답합니다.

     

    이는 안정적인 데이터의 저장 여부 vs 속도로 볼 수 있습니다.

     

    0은 속도가 매우 빠르지만 실제 서비스에는 무리한 설정이라는 판단하게 1로 설정하였습니다.

     

    compression type

    압축 알고리즘의 효율과 데이터의 성격에 따라 더 좋은 성능이 결정됩니다.

    producer와 consumer를 모두 고려한 상황에서 lz4가 가장 빨랐기 때문에 lz4를 설정하였습니다.

     

    론칭 이후에 겪었던 가장 기억에 남는 4가지 이슈

    MTS의 트래픽 특성을 살펴보면 장 시작과 동시에 2~3분 이내에 그날의 최대 트래픽이 발생하는 패턴이 있습니다.

    보통 주식은 9시에 장이 열리자마자 매매가 활발히 이루어지기 때문입니다.

     

    첫 번째로 겪은 이슈 : Load Balancing 방식에 따른 Connection 지연

    Least Connection 방식을 사용하여 LB를 진행하였는데 장 시작과 동시에 너무 많은 사용자가 몰리다 보니 문제가 발생하였습니다.

     

    Least Connection 방식은 현재 활성화된 연결이 적은 서버로 배분합니다.

     

    서버 간 커넥션 분산은 잘 이루어졌지만 순간적으로 특정 서버에 다수의 커넥션이 몰리는 현상이 발생하였고 CPU 사용량 증가와 더불어 커넥션 지연이 발생하는 원인이 되었습니다.

     

     이런 현상을 해결하기 위해 Round Robin과 Least Connection을 혼합하여 적용하였습니다.

     

    두 번째로 겪은 이슈 : WebSocket Connection Leak

    웹소켓 서버의 배포나 재시작이 없는 경우에 일주일 정도 지나게 되면 웹소켓 서버군의 커넥션이 점진적으로 증가하여 현재 접속한 동접자 수와 큰 폭의 차이가 나는 현상이 있었습니다.

     

    Spring WebSocket의 close 이벤트를 디버깅해보니 정상 종료 시에는 문제가 없었지만 비이상적인 이벤트 발생 시에 웹소켓이 정상적으로 종료되지 않는 현상을 확인할 수 있었습니다.

     

    가장 먼저 클라이언트/서버 Heartbeat 설정을 체크해 보았습니다.

    이때 HeartbeatTimeout은 정상 동작하는 것을 확인할 수 있었습니다.

    Heartbeat란 일정 주기로 신호를 보냄으로써 connection이 잘 연결되었는지 확인하는 기법입니다.

     

    다음으로 SessionDisonnectEvent를 수신받아 비이상 종료가 발생하는 사례를 수집해 보았습니다.

    장표에 소개된 1001, 1002, 1006 case가 빈번하게 발생하는 것을 확인할 수 있었습니다.

     

    로컬 개발 환경에서 더 자세히 알아보았습니다.

    Stomp protocol의 세션을 잘 정리가 되었지만 Tomcat의 웹소켓 세션은 닫히지 않았습니다.

    doClose 메서드의 closeSocket option이 false로 실행되는 것이 실제 문제의 원인입니다.

     

     

    WebSocket Handler Decorator Factory를 통하여 decorate를 등록하고 afterConnectionClosed시  webSocketSession의 onclose를 실행되도록 하여 문제를 해결할 수 있었습니다.

     

    1001, 1002, 1006 발생 시에 명시적으로 Close 하도록 처리하였습니다.

     

    네트워크가 자주 변경되는 모바일 환경의 특성상 예기치 않은 다양한 종료 이벤트가 발생하게 되고 이러한 것들을 적절히 처리해 주어야만 문제없이 WebSocket 기능을 활용할 수 있는 것 같습니다.

     

    세 번째로 겪은 이슈 : 주식 1주 선물 받기 이벤트의 성공으로 예상보다 큰 유저 트래픽 발생

    트래픽이 몰리는 장 초반에 종목 상세 등 주요 화면의 로딩 속도가 느려지고 시세 연결에 실패하는 등 크고 작은 장애를 수차례 겪게 됩니다.

     

    빠르게 서버를 추가 배치하는 등 여러 가지 노력을 기울였지만 트래픽이 급증하는 날이면 지연 현상이 발생하였습니다.

     

    또한 응답 지연이 실시간 서버에서만 발생하는 것이 아니라 전체 시스템에 거쳐 발생하였습니다.

     

    장 시작 시점에 여러 번의 테스트를 거치면서 시세 서버의 Outbound 트래픽 증가로 인한 방화벽과 보안 장비 처리량의 이슈임을 파악하게 됩니다.

     

    인터넷 회선과 방화벽 그리고 보안 장비 등은 전체 서버군이 공유하고 있었기 때문에 발생한 문제입니다.

     

    인터넷 회선을 분리하고 높은 처리량을 가진 방화벽으로 개선하였습니다.

     

    애플리케이션 밖의 요인이다 보니 쉽게 실마리를 찾기 못했고 이 문제의 원인을 파악하기까지 꽤나 고생을 했다고 합니다.

     

    네 번째로 겪은 이슈 : Scale out의 어려움

    트래픽 증가로 인한 크고 작은 장애를 겪으며 여러 번의 scale out 과정을 거침으로써 론칭 초기에 비해 5배의 크기로 서비스하고 있습니다.

     

    하지만 On promise 구성이다 보니 스케일 아웃에 드는 비용과 속도에 한계가 존재했습니다.

     

    온프레미스 : 클라우드 환경의 반대인 자체적으로 보유한 전산실 서버에 직접 설치해 운영하는 방식

     

    이를 해결하기 위해 AWS 클라우드 환경으로 이전하는 것을 고민하였고 현재는 이전을 계획하고 있습니다.

     

    댓글

Designed by Tistory.