다온소프트랩 · 업무 프로젝트
Bitria
멀티 거래소 연동 암호화폐 거래 플랫폼
여러 암호화폐 거래소를 연동해 현물·선물·마진 거래를 제공하는 플랫폼입니다.
웹·모바일 앱의 프론트엔드와 백엔드 API 개발을 담당하고, 인프라 운영에 참여했습니다.
- 모바일 홈 총 차단 시간(TBT)
- 49%감소
- 앱 트레이딩 화면 누적 렌더 시간
- 60%감소
443 → 226ms · 로컬 비교 측정
1,028 → 416ms · 12초 측정
담당 업무
- 거래 화면
- TradingView 차트, 실시간 시세·호가·체결, 현물·선물·마진 주문 및 포지션·체결 내역 구현
- 백엔드 API
- 주문 생성·조회·취소·수정 API를 개발하고 요청 검증·권한 확인·주문 처리 서비스와 연동했습니다.
계좌별 자산·USDT 환산·스냅샷·일별 손익 조회도 구현했습니다. - 인증·보안
- 회원가입·소셜 로그인, 2단계 인증(2FA)·패스키·신뢰 기기·세션·계정 복구 기능을 개발했습니다.
고객확인(KYC)·입출금 연동과 API 키 관리도 구현했습니다. - 멀티 플랫폼
- 하나의 코드베이스로 웹·iOS·Android·Electron 앱 빌드, 모바일 앱 스토어 출시, 네이티브 푸시·소셜 로그인·생체인증 연동
- 공통 구조
- FSD(Feature-Sliced Design) 구조와 시맨틱 디자인 토큰을 설계했습니다.
다크·라이트 테마와 브랜드 2종·6개 로케일을 지원했습니다.
웹·앱별 코드를 분리하고 컴포넌트 API 기준을 문서화했습니다. - 서비스 운영
- 가격 조건별 알림·클라이언트 오류 수집·앱 버전 정책·고객 문의(CS) API 개발, k3s 프로덕션 배포·운영 및 장애 대응
교차 마진 주문 가능액 계산 오류 수정
레버리지 모달과 주문 폼이 최대 주문 가능액을 따로 계산해 같은 조건에서도 표시 금액이 최대 56배 차이 났습니다.
거래소 문서와 API 응답을 비교해 두 원인을 찾았습니다.
교차 마진 계산에 격리 마진용 리스크 한도를 적용했고, 잔고에 레버리지를 단순 곱하는 계산도 문제였습니다.
표시값만 고치지 않고 계산 로직을 공통 모듈로 통합했습니다.
수수료·안전 여유분·리스크 한도·레버리지 상한·최소 주문 단위를 반영했습니다.
주문 수량의 부동소수점 오차는 BigNumber로 처리했습니다.
두 화면의 계산값을 거래 단위까지 일치시켰습니다.
경계 사례 20건을 검증하고, 운영 환경에서 발견한 양방향 포지션의 조건부 주문 누락도 수정했습니다.
모바일 홈 JavaScript 실행 시간 개선
프로덕션 모바일 홈에서 Lighthouse 성능 점수 38점, 총 차단 시간(TBT) 1,610ms를 측정했습니다.
JavaScript 청크 실행 시간을 분석해 shared/ui의 일괄 재수출 구조가 불필요한 모듈까지 같은 청크에 포함하는 것을 확인했습니다.
optimizePackageImports를 적용했지만 청크 수와 전체 번들이 늘어 이를 되돌렸습니다.
대신 컴포넌트를 직접 import하도록 바꾸고 three.js 배경은 늦게 불러오도록 했습니다.
로컬 프로덕션 빌드 비교에서 TBT가 443ms에서 226ms로 약 49% 줄었습니다.
일부 청크 크기는 늘었지만 초기 로드에서 불러오는 JavaScript는 22KB 줄었습니다.
Lighthouse 모바일 설정(CPU 4배 스로틀·느린 4G)에서 워밍업 후 3~5회 측정했습니다.
프로덕션 진단값 1,610ms와 로컬 비교값은 측정 환경이 다릅니다.
실시간 갱신 범위를 표시 컴포넌트로 제한
페이지 최상위에서 실시간 스냅샷을 구독해 시세가 갱신될 때 주문 폼·하단 패널·닫힌 UI까지 다시 렌더링됐습니다.
웹에서는 같은 현상이 재현되지 않았습니다.
앱 화면에서는 데이터 구독을 실제 시세 표시 컴포넌트로 옮기고, props와 구독 인자의 참조를 안정화했습니다.
12초 프로파일링에서 누적 렌더 시간 1,028 → 416ms(약 60% 감소), 렌더 횟수 2,802 → 1,323회(약 53% 감소)를 확인했습니다.
앱 현물 트레이딩 화면에서 조작 없이 실시간 데이터만 갱신한 구간입니다.
해당 구간에서 주문 폼·하단 패널의 불필요한 렌더링도 줄었습니다.
구현 구조와 설계
FSD 아키텍처와 화면 분기 기준
화면과 도메인이 늘어나도 의존성이 뒤섞이지 않도록 app → views → widgets → features → entities → shared 순의 단방향 규칙을 정했습니다.
조회(query)는 entity에, 변경(mutation)은 feature에 두어 각 레이어의 책임을 분리했습니다.
웹과 앱의 라우트가 다르면 폴더를 분리했습니다.
화면 크기 차이가 작으면 prop으로 분기하고, 마크업 차이가 크면 디바이스별 파일을 두도록 기준을 정했습니다.
실시간 데이터 전달과 구독 구조
거래소 WebSocket 데이터는 수집 서비스의 심볼 정규화와 Redis Pub/Sub을 거쳐 백엔드에 전달됩니다.
프론트엔드는 REST 스냅샷과 WebSocket 스트림으로 차트·호가·시세를 표시합니다.
실시간 구독을 값을 표시하는 컴포넌트로 옮기고, 티커 데이터도 항목별로 구독하도록 분리했습니다.
보이지 않는 오더북과 닫힌 드로어는 구독을 중지하고, 체결 내역 행에는 memo를 적용해 갱신 범위를 줄였습니다.
차트 로딩 실패의 원인 분석과 해결
간헐적인 차트 로딩 실패를 클라이언트 오류 로그와 서버·CDN 응답 시간으로 추적했습니다.
심볼을 빠르게 바꾸면 이전 위젯의 콜백이 이미 해제된 iframe에 접근해 새 차트가 초기화되지 않는 상황을 재현했습니다.
라우트가 바뀔 때 페이지 전체가 다시 마운트되는 구조가 원인이었습니다.
차트 뷰를 레이아웃에 두어 불필요한 재마운트를 막고, 위젯 초기화 중 심볼이 바뀌어도 최신 심볼을 적용하도록 수정했습니다.
단일 코드베이스의 멀티 플랫폼 대응
Next.js·TypeScript 코드베이스를 웹, Capacitor 기반 iOS·Android 앱, Electron 데스크톱 앱으로 빌드했습니다.
모바일 앱을 스토어에 출시하고 푸시(APNs·FCM)·소셜 로그인·기기 식별 기능을 연결했습니다.
앱 강제 업데이트 정책을 연동하고, Electron 앱의 API 요청을 식별하는 헤더를 추가했습니다.
사용 기술
TypeScript · Next.js 16 · React 19 · Tailwind CSS · Zustand · TanStack Query · Capacitor · Electron · Node.js · Express · Prisma · MariaDB · Redis · Docker · k3s