토스를 다니는 건 아닙니다 썸네일이...
회사에서 곧 진행할 프로젝트 관련 참고 영상을 보며... 정리한 내용
그런데 이거 너무 어려운데...?
토스ㅣSLASH 22 - 토스증권 실시간 시세 적용기 영상을 보며 정리한 내용입니다
: 거래를 위해 서로 사거나 팔고 싶은 가격을 부르는 것
→ 일치하는 상대 주문이 있을 경우 거래가 성사!
성사된 거래 가격들이 쌓인 것 = 주식 차트의 체결 시세
가장 최근의 체결 가격 = 현재가
증권투자자들은 자신에게 유리한 조건으로 거래를 하기 위해 가장 최신의 정보를 근거로 호가를 결정하길 원한다
이 때 가장 기본이 되는 정보 = 시세와 주문 호가
사용자들 → 최신의 시세와 호가 정보를 제공하는 MTS를 선호
증권사들 → 거래소나 시세 제공처로부터 가능한 지연 없이 실시간으로 시세를 전달하는 기능을 제공하고자 함 = 실시간 시세
→ 토스증권의 UX가 주식 초심자들을 주요 대상으로 타겟팅하면서 시세 정보의 실시간성에 크게 민감하지 않을 것이라는 팀내 가설
→ 차트 등 UX 설계 과정에서 유명한 미국 주식 플랫폼 로빈후드팀의 MTS 제품을 벤치마킹
= Polling 방식 채택
→ 주 타겟 층과 별개로 전체적인 사용자 경험을 생각하면 반드시 실시간 시세를 제공해야 한다
Polling을 대체할 기술로 Server Side Event
도 고려되었지만 향후 양방향 통신을 통한 기능 확대의 니즈가 있을 것이라 판단
단방향 통신인 시세 시스템은 SSE로도 충분히 고려해 볼만한 기술인 것 같다
사용자들이 조금 더 빠르게 최근 시세 정보를 확인할 수 있게 됨
시각적으로도 보다 역동성 있는 모습
Unix C 기반의 원장 시스템
Kubernetes Java 기반의 MTS Application 영역이 서로 다른 기술 스택으로 구성
이러한 구조에서 원장에서 수신받은 시세 데이터를 Java 기반 애플리케이션을 거쳐 클라이언트까지 제공하기 위해서는 C 기반의 원장 시스템에서 Java 애플리케이션으로 시세를 전달하기 위한 매개체가 필요했다 → Kafka를 활용
시세를 수신받아 적재하는 과정에서 Kafka에서 WebSocket 서버로 송신 → 해당 WebSocket 서버에 수신하여 클라이언트까지 시세를 전달하는 방식
[인프라 구조]
WebSocket 서버를 DMZ(Demilitarized Zone) 영역에 배치
→ N개의 서버가 각각 고유한 호스트 명을 가지도록 했다
로드밸런싱 및 라우팅 역할을 위한 라우팅서버를 별도로 구성하여 서버-클라이언트 간 연결 정보를 Redis를 통해 관리하도록 함
이렇게 구조를 짠 이유
1. 전자금융감독규정의 망분리 요건 준수
2. WebSocket 서버 상단의 네트워크 홉을 줄이기 위함
Spring WebSocket과 Stomp Protocol을 활용하여 구현
→ WebSocket에서 별도의 프로토콜 구현 없이 빠르게 PUB/SUB 기능을 구현하기에 가장 좋은 선택지
토스 증권에서 사용되는 Topic은 목적지가 종목 코드인 유형, 사용자인 유형 크게 두 가지로 구분
클라이언트는 먼저 라우팅 서버를 통해 연결할 WebSocket 서버의 호스트 정보 획득
획득한 WebSocket 서버로 연결 시도
if(실패 시) {
Polling 방식의 API를 호출
} else {
성공 시 WebSocket 서버는 갱신된 커넥션 정보를 라우팅 서버까지 전달
}
라우팅 서버는 로드 밸런싱을 위한 서버별 커넥션 정보를 Redis에서 관리
클라이언트는 WebSocket을 통해 화면에 노출되는 종목들에 대해 필요한 토픽을 구독 요청
WebSocket 서버에서 거래소로부터 수신된 시세 정보를 Kafka를 통해 전달받아 각 종목을 구독하고 있는 클라이언트로 시세 정보를 전달
종목 정보 수신과는 조금 다르다
실제 갱신된 자산 정보를 WebSocket을 통해 직접 전달하고 있지는 않고 자산 정보의 갱신 신호를 주는 방식으로 활용
초기 연결과정은 기존과 동일하지만
WebSocket 접속을 위한 Handshake 과정에서 사용자 ID를 획득
WebSocket 서버가 Routing 서버에 커넥션 정보를 전달하면 사용자 ID를 기준으로 사용자별 접속 서버 정보를 Redis에 관리하게 됨
원장에서 매매체결 혹은 입출고 등으로 자산 정보의 변경이 발생하면 라우팅 서버에 해당 이벤트를 전달
라우팅 서버는 사용자별 접속 정보를 기준으로 실제 사용자가 연결된 WebSocket 서버에 해당 정보를 포워딩하게 된다
WebSocket 서버는 해당 사용자에게 자산 정보 갱신을 지시
사용자는 자산 API 조회를 통해서 갱신된 최신 자산 정보를 조회할 수 있게 됨
C 기반의 원장 시스템으로부터 시세를 전달받기 위해 Kafka를 적용하면서 latency 최적화가 필요
Kafka 클라이언트에서 latency 개선을 위해 설정값으로 튜닝할 수 있는 요소는 compression type과 acks 설정 두 가지
all은 leader와 replica에 모두 저장이 완료되면 응답하는 방식
1은 leader에만 저장되면 응답
0은 leader에 저장됨도 확인하지 않고 바로 등답하는 방식
→ all과 1일 때 약 5.7배 정도의 평균 latency 차이를 보였다
acks는 1로 설정
lz4가 가장 좋은 결과를 보여주었다 (Producer와 Consumer를 모두 고려한 결과)
장 시작과 동시에 약 2~3분 이내에 그 날 최대 트래픽 발생
초기 런칭 시 Least Connection 방식으로 로드 밸런싱
→ 장 시작과 동시에 너무 많은 사용자가 몰리며 문제 발생
WebSocket 서버의 배포나 재시작이 없는 경우 짧게는 수일에서 길게는 일주일 정도가 지나면 WebSocket 서버군의 커넥션이 점진적으로 증가해 HTTP API 요청 집계 방식으로 측정한 동시접속자 수와 큰 폭의 차이가 나는 현상
정상 종료 시에는 문제가 없었으나 Abnormal 이벤트 발생 시에 정상적으로 종료되지 않는 현상
WebSocket의 경우 현업에서 활용할 때마다 Connection Leak 등 세션 Close와 관련된 문제들을 적잖이 겪게 됨!
이벤트의 성공으로 예상보다 빠른 시점에 큰 트래픽을 겪으면서 발생한 이슈
→ 사용자 트래픽이 급증하면서 실시간 시세 서버 또한 팀에서 에측했던 것보다 훨씬 더 큰 유저 트래픽을 겪게 된다