SiliconValley_survivor

SiliconValley_survivor

Mobile Design Robinhood, 실시간 시세와 주문

실시간 시세는 필요한 상태만 빠르게 보여주고 주문은 중복 없이 접수하며 앱 복귀 후 실제 체결 상태를 다시 확인하는 모바일 트레이딩 설계

SiliconValley_survivor's avatar
SiliconValley_survivor
Sep 25, 2026
∙ Paid

Introduction & Requirements

사용자가 관심 종목 목록을 보고 있습니다.

여러 종목의 가격이 빠르게 바뀌는 가운데 한 종목을 선택하고, 수량과 지정가를 입력한 뒤 매수 버튼을 누릅니다. 그 순간 지하철이 터널에 들어가면서 네트워크 연결이 끊깁니다.

화면에는 제출 중이라는 표시가 남아 있습니다.

사용자는 주문이 실패했다고 생각해 버튼을 다시 누릅니다. 하지만 서버에서는 첫 요청을 이미 접수했고, 주문 일부가 체결되었을 수도 있습니다.

이때 같은 주문을 새로 만들면 중복 매수가 발생합니다. 반대로 실패했다고 표시하고 아무것도 하지 않으면, 사용자는 실제로 진행 중인 주문을 확인하지 못합니다.

다른 문제도 있습니다.

앱을 다시 열었을 때 가격은 이전 값인데 초록색 연결 표시만 켜져 있습니다. 주문 목록에는 접수됨이라고 나오지만 서버에서는 이미 일부가 체결되었습니다. 잠시 뒤 도착한 취소 알림과 체결 알림의 순서도 서로 다릅니다.

트레이딩 앱에서는 화면에 숫자를 빨리 표시하는 것과 주문 결과를 정확하게 설명하는 것이 서로 다른 문제입니다.

시세는 짧은 시간에 여러 번 바뀝니다. 가격 표시를 위해 모든 중간 값을 화면에 한 번씩 그릴 필요는 없습니다.

그러나 주문 제출과 체결은 같은 방식으로 생략할 수 없습니다. 체결 네 건 중 마지막 한 건만 남기면 주문 수량과 잔고를 잘못 계산할 수 있습니다.

Robinhood의 공개 설명도 화면에 표시된 가격과 주문이 실제로 체결되는 가격을 구분합니다. 지정가에 도달한 것처럼 보여도 해당 조건의 수량이 없으면 주문이 체결되지 않을 수 있습니다. 이 차이는 UI에 표시한 시세를 주문 실행의 확정값으로 사용하지 않아야 하는 이유입니다.

이번 설계는 iOS와 Android에서 동작하는 증권 트레이딩 앱을 대상으로 합니다. 실시간 시세, 주문 확인, 멱등 제출, 체결 반영과 앱 복귀 후 상태 복구를 연결하겠습니다.

아래의 프로토콜, 데이터 모델, 수치와 서버 구성은 제안하는 설계입니다. Robinhood가 내부에서 동일한 컴포넌트나 API를 사용한다고 가정하지 않습니다.

메신저 앱에서는 오프라인에서 작성한 메시지를 나중에 전송하는 것이 자연스럽습니다. 주문에서는 그 정책을 그대로 가져오지 않겠습니다.

오프라인에서 주문 초안을 보관할 수는 있습니다. 하지만 사용자가 보던 가격과 시장 상황이 달라진 뒤, 예전에 작성한 주문을 아무 확인 없이 자동으로 제출해서는 안 됩니다.

Functional Requirements

  • 사용자는 관심 종목과 종목 상세 화면에서 실시간 시세를 확인하며, 앱은 현재 화면에서 필요한 종목만 구독합니다.

  • 앱은 시세의 연결 상태, 데이터 신선도, 시장 상태와 지연 제공 여부를 구분하여 표시합니다.

  • 사용자는 주문 내용을 검토하고 필요한 재인증을 거친 뒤 주문을 제출할 수 있습니다.

  • 주문 제출 결과를 받지 못해도 앱은 원래 주문 키를 유지하고, 동일 주문의 접수 결과를 확인할 수 있습니다.

  • 앱은 접수, 부분 체결, 체결, 취소와 거부 상태를 구분하고, 주문 취소 요청과 실제 취소 완료도 따로 표시합니다.

  • 앱은 소켓 이벤트를 놓쳤거나 순서가 바뀌어 도착해도 서버 조회와 버전 비교를 통해 주문 상태를 다시 맞춥니다.

  • 앱은 백그라운드 진입 시 불필요한 시세 구독과 소켓을 정리하고, 복귀 시 스냅샷을 확보한 뒤 새로운 변경을 적용합니다.

  • 체결 알림을 선택하면 앱은 현재 계정과 주문을 확인하고 서버의 최신 상태를 조회합니다.

  • 오프라인에서는 이전에 저장한 주문과 시세를 확인할 수 있지만, 이를 최신 상태나 제출 가능한 상태로 오인하게 하지 않습니다.

Target & Scale, Traffic & Capacity Assumptions

시세 조회량과 주문 제출량은 별도로 계산합니다

일간 활성 사용자를 100만 명으로 가정하겠습니다. 한 명이 하루 평균 10분 동안 시세 화면을 사용하고, 서비스의 주요 사용량이 하루 10시간에 분포한다고 두겠습니다.

여기서 10시간은 계산을 위한 관측 구간입니다. 특정 시장의 실제 거래 시간을 의미하지 않습니다.

평균 동시 사용자는 약 1만 6,667명입니다. 피크를 평균의 6배로 두면 동시 사용자는 10만 명입니다.

화면에 평균 10개 종목이 보인다면 피크 시점의 활성 구독 관계는 100만 개입니다.

이 수치는 연결 수와 다릅니다. 사용자 한 명이 소켓 하나로 여러 종목을 구독할 수 있기 때문입니다.

서버는 연결 수뿐 아니라 연결마다 구독한 종목 수와 실제 전송량을 함께 봐야 합니다.

원본 시세와 사용자별 전달량을 구분합니다

서비스가 1만 종목을 다루고, 종목마다 평균 초당 20개의 시세 변경이 들어온다고 가정하겠습니다.

상류 시세 처리량은 초당 20만 변경입니다.

그런데 피크 사용자 10만 명이 각각 10개 종목을 같은 빈도로 받는다면 사용자별 전달량은 초당 2천만 상태가 됩니다.

상태 하나를 평균 200바이트로 가정하면 초당 약 4GB, 비트 기준으로 약 32Gbps입니다.

여기에는 TLS, 프레임 헤더, 재연결과 스냅샷 비용을 포함하지 않았습니다.

이 규모에서 모든 사용자에게 모든 중간 시세를 전달할 필요가 있는지부터 검토하겠습니다.

이번 기본 시세 화면에서는 종목별 최신 상태를 최대 초당 5회 전달하는 정책을 초기 가정으로 둡니다. 해당 종목이 계속 갱신된다는 조건에서 전달량은 초당 500만 상태, 약 1GB가 됩니다.

단말 한 대에서는 같은 계산으로 초당 약 40KB가 10KB로 줄어듭니다. 10분 사용 시 순수 상태 데이터는 약 24MB와 6MB입니다.

이는 가정한 데이터 형식과 갱신 빈도에서의 계산입니다. 실제 통신량과 절감률은 구독 종목, 압축, 배치와 네트워크 계층에 따라 달라집니다.

대신 추가되는 지연이 있습니다. 최대 초당 5회 전달하면 상태를 모으는 동안의 대기 시간이 생깁니다. 상세 화면과 주문 검토 화면에 같은 정책이 적절한지는 별도로 측정해야 합니다.

주문은 양이 적어도 더 엄격하게 처리합니다

일간 활성 사용자의 10퍼센트가 하루 평균 네 번 주문을 제출한다고 가정하겠습니다.

하루 신규 주문은 40만 건입니다. 같은 10시간 관측 구간에서 평균은 초당 약 11건이며, 순간 피크를 평균의 50배로 두면 초당 약 556건입니다.

주문당 평균 0.5건의 취소 요청이 추가된다고 가정하면 신규 주문과 취소 명령은 하루 60만 건입니다. 같은 피크 계수를 적용한 명령 처리량은 초당 약 833건입니다.

주문 하나에서 접수와 체결, 취소 등을 포함해 평균 여섯 개의 상태 이벤트가 생기고, 이벤트 하나가 1KB라면 하루 이벤트 데이터는 약 2.4GB입니다.

시세보다 데이터량은 작지만 처리 의미가 다릅니다.

시세 화면은 오래된 중간 표시를 생략할 수 있습니다. 주문은 같은 명령을 두 번 실행하거나 체결 기록을 놓치면 사용자 자산 상태가 달라질 수 있습니다.

두 경로를 같은 큐와 같은 유실 정책으로 처리하지 않겠습니다.

Non-Functional Requirements

아래 목표와 한도는 제안하는 제품의 초기 시험 기준입니다.

  • 주문 제출 선택부터 로컬의 제출 중 상태 표시까지 p95 100ms 이내를 목표로 합니다. 이 표시는 서버 접수 완료를 의미하지 않습니다.

  • 정상적인 시세 제공과 네트워크 조건에서는 서버의 시세 상태 생성부터 화면 반영까지 p95 500ms 이내를 초기 목표로 둡니다.

  • 단말이 완전한 시세 상태를 받은 뒤 화면에 반영하기까지 p95 100ms 이내를 목표로 하고, 메인 스레드의 변경 작업량을 별도로 제한합니다.

  • 주문 API는 정상 조건에서 내부 접수 결과를 p95 700ms 이내에 반환하는 것을 목표로 합니다. 거래소나 외부 실행 경로의 체결 시간은 이 목표에 포함하지 않습니다.

  • 같은 계정의 같은 주문 키와 같은 주문 내용은 재시도되어도 하나의 업무 결과에 연결되어야 합니다.

  • 주문 결과를 확인하지 못한 경우 실패나 취소로 추정하지 않고 확인 불가 상태로 유지합니다.

  • 앱 복귀 후 정상적인 네트워크와 서버 조건에서 미확정 주문 및 최근 변경을 p95 3초 이내에 확인하는 것을 목표로 합니다.

  • 시세 대기 데이터와 화면 준비 작업 수에 상한을 두며, 시세 부하가 주문 제출 및 취소 처리를 장시간 지연시키지 않도록 합니다.

  • 계정과 세션이 변경되면 이전 응답을 현재 계정의 상태에 반영하지 않습니다.

  • 민감한 인증 정보와 주문 내용은 필요한 범위로만 저장하며, 일반 성능 로그와 백그라운드 화면에 노출하지 않습니다.

Out of Scope

  • 거래소 매칭 엔진, 최선집행과 주문 라우팅 알고리즘의 내부 구현은 직접 설계하지 않습니다.

  • 옵션, 복합 주문, 공매도와 소수점 주식의 세부 규칙은 제외하고 정수 수량의 주식 지정가 주문을 기본 예시로 사용합니다.

  • 전체 호가창의 모든 단계와 모든 거래 틱을 단말에 보존하는 전문 거래 터미널은 기본 범위에서 제외합니다.

  • 입출금, 결제와 정산의 전체 원장은 구현하지 않지만 주문 가능 금액과 보유 수량의 서버 검증 경계는 포함합니다.

  • 가격 알림과 자동 주문 조건을 단말의 백그라운드 소켓만으로 실행하는 기능은 제공하지 않습니다.

Architecture

현재 시세와 실제 주문을 다른 경로로 다룹니다

사용자는 한 화면에서 가격과 주문 버튼을 봅니다. 그러나 시스템에서는 서로 다른 데이터입니다.

시세는 외부 시장 상태를 관측한 결과입니다. 주문은 사용자가 특정 조건의 거래를 요청한 명령입니다.

Market data

관측한 가격과 시장 상태를 표시

Order command

사용자가 승인한 조건으로 거래를 요청

Execution state

서버가 확인한 실제 주문 진행 결과

시세가 100.12달러라고 표시되었다는 이유로 100.12달러에 체결된 주문을 로컬에서 만들지 않습니다.

사용자가 지정가 100.15달러를 입력했다면 그 가격은 사용자 명령의 일부입니다. 이후 시세가 바뀌어도 주문 입력값을 자동으로 새 시세로 덮어쓰지 않습니다.

시세 소켓과 주문 채널의 부하를 분리합니다

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2026 실리콘밸리_생존자 · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture