Skip to content

릴리즈 1.02 - 커넥션 풀, 트랜잭션 범위 축소, 렌더링 최적화

Latest
Compare
Choose a tag to compare
@SeongHyeon0409 SeongHyeon0409 released this 23 Jan 06:15
c429ae9

개요

이번 릴리즈에서는 커넥션 풀 할당 문제를 해결하고 트랜잭션의 범위를 축소하여 커넥션 풀을 점유하는 시간을 줄여 성능을 개선하였고,

실시간으로 받아오는 SSE 데이터에 의하여 불필요한 리렌더링이 일어나는 것을 발견하고 이것을 개선하였습니다.

새로운 기능

  • 거래 체결 시간 98.86% 단축(초당 1000번의 요청 기준)
  • 불필요한 리렌더링을. 또한 스크립트 실행 시간은 2212ms → 474ms로 약 약 78.6% 감소 (1738ms 단축)

개선 사항

불필요한 렌더링

기존 홈 페이지에 존재하던 코인 리스트는 서버로부터 SSE 데이터를 끊임 없이 전달 받으며 화면에 렌더링 한다. 이때, 가격이 변한 코인 뿐 아니라 가격이 변하지 않는 코인까지 리렌더링 되는 문제가 발생했다.

불필요한 리렌더링이 문제가 되는 이유

불필요한 리렌더링이 문제가 되는 이유
불필요한 리렌더링은 변경이 없는 컴포넌트까지 불필요하게 리렌더링되어 CPU/메모리 자원이 낭비된다. 즉 우리가 렌더링하는 코인 목록이 많으면 많을수록 성능 저하가 심각해진다. 특히 모바일 환경에서는 이러한 성능 저하가 배터리 소모와 직결되며, 사용자들이 실시간으로 코인 가격을 모니터링하는 데 있어 중요한 UX 저하 요인이 된다.

문제 원인 파악

image

문제의 원인을 파악하기 위해 컴포넌트의 구조를 도식화해봤다.

CoinList 컴포넌트를 들여다 보면 다음과 같이 동작한다

image

CoinList 내부 sseData는 useSSETicker 훅을 통해 실시간으로 받아오는 코인의 시세 가격 데이터다. 이 훅은 SSE 연결을 통해 모든 코인의 가격 정보를 한 번에 받아와 상태를 업데이트한다. 이로 인해 단 하나의 코인 가격만 변경되어도 전체 상태가 업데이트된다.

리액트는 부모의 컴포넌트가 리렌더링 되면 자식 컴포넌트가 함께 리렌더링되는 특성이 있다. 이는 리액트의 기본적인 렌더링 최적화 전략으로, 별도의 최적화 처리를 하지 않으면 부모 컴포넌트의 상태 변화가 모든 자식 컴포넌트의 리렌더링을 유발한다.

즉 각 코인 컴포넌트들은 부모 컴포넌트인 CoinList의 상태가 변경되어 자신의 가격 정보와 상관없이 리렌더링 되었던 것이다.

문제 해결

zustand의 전역 상태를 활용해 Coin 컴포넌트가 자신이 필요한 데이터만 구독하는 방식으로 해결해보기로 시도했다. 기존 Coin 컴포넌트는 자신의 코인 정보 외 모든 코인의 시세 가격 데이터를 전달 받고 있어 zunstand를 통해 필요한 데이터만 구독을 하면 위의 불필요한 리렌더링 문제가 해결될 것이라 기대했다. zustand의 selector 기능을 활용하면 각 Coin 컴포넌트가 자신의 market id에 해당하는 데이터만 구독할 수 있어, 다른 코인의 가격 변화에 영향을 받지 않게 된다.

image

다음과 같이 zustand를 사용해 코인의 시세 정보를 담는 전역 상태를 만들어주었다.

image

이후 SSEProvider 를 통해 Coin 컴포넌트에서 자신의 market 정보만 구독하여 자신의 정보만 받아오도록 하였다.

결과

개선 전

image

개선 후

image

불필요한 리렌더링을 줄여 개선 전 대비 CPU 사용량을 개선할 수 있었다. 또한 스크립트 실행 시간은 2212ms → 474ms로 약 약 78.6% 감소 (1738ms 단축)의 성능 개선을 이루었다.

커넥션 풀

문제 1: 잘못된 waitTransaction 함수 사용

image

  • 동시에 여러 미체결 데이터를 생성할 때의 트랜잭션을 방지하기 위해 waitForTransaction 이라는 함수를 통해 동기적으로 실행하는 코드를 구현함.
  • 문제점: 불필요한 대기로 인해 비동기 작업의 성능을 저해.
  • 해결 방법: waitTransaction 함수 제거 및 코드 간소화.

문제 2: 커넥션 풀 할당 문제

  • 그러나 동시 10개 요청 시 무한 대기 현상 발생함
  • 원인은 TypeORM의 기본 커넥션 풀 설정이 10개여서 발생한 문제.
  • 트랜잭션으로 묶여있지 않은 validateUserAccount가 트랜잭션 커넥션 대신 새로운 커넥션 요청 → 커넥션 부족으로 대기 상태 발생.

image

  • 개선점: 트랜잭션 내에서 동일한 커넥션(queryRunner)을 사용하도록 수정.

    불필요한 커넥션 요청 제거 및 대기 시간 단축.

테스트 결과

조건 1: 단일 서버

  • 1000명 동시 매수 요청 (분할 체결, 한 번에 체결 고려하지 않음)
  • 커넥션 풀 리미트: 10개

image

image

요청 처리 시간 최소 최대
단일 서버 1초 69초

조건 2: 로컬 서버 2개

  • 1000명 동시 매수 요청
  • 서버 2개 → 커넥션 풀 두 배로 증가

image

image

요청 처리 시간 최소 최대
로컬 서버 2개 1초 15초

서버가 두개인 경우 그만큼 커넥션 풀두 배로 할당되어 성능이 개선되는 것으로 추측

트랜잭션, 쿼리분석

개요

앞서 수행한 성능 개선 작업을 통해 스레드 및 프로세스의 문제가 아닌, 로직 자체에 문제가 있음을 확인하였습니다. 이에 의심되는 부분을 선별하여 분석을 진행하였습니다.

분석 대상

  1. 슬로우 쿼리의 존재 여부
  2. 트랜잭션 범위 축소

문제 1: 슬로우 쿼리 분석

기존 매수 로직에서는 두 가지 트랜잭션을 사용하고 있었습니다.

  1. 매수 등록: 4개의 쿼리
  2. 매수 체결: 9개의 쿼리

부하 테스트를 위해 데이터베이스에 1,000개의 데이터를 채운 후 각 쿼리를 개별적으로 분석한 결과, 모든 쿼리가 정상적으로 작동함을 확인하였습니다.

참고자료

https://docs.google.com/spreadsheets/d/1K_AV-zJWKhUIBxHs92WnTzckEtmP_De4VHK48_nHYr4/edit?gid=1602479191#gid=1602479191

문제 2: 트랜잭션의 범위 축소

트랜잭션이 완료되기 전까지 커넥션 풀이 점유되므로, 커넥션 풀에 부하가 발생할 수 있음을 학습하였습니다. 이에 거래 체결 시 사용되는 트랜잭션의 범위를 축소하기로 하였습니다.

수정 전 트랜잭션

  1. 등록된 미체결 데이터 검색
  2. 거래 기록 테이블에 데이터 삽입
  3. 유저 계좌 정보 업데이트
  4. 유저 자산 정보 업데이트
  5. 미체결 정보 업데이트 또는 삭제

수정 후 트랜잭션

  1. 등록된 미체결 데이터 검색
  2. 거래 기록 테이블에 데이터 삽입 (비동기 처리)
  3. 유저 계좌 정보 업데이트 (비동기 처리)
  4. 유저 자산 정보 업데이트 (비동기 처리)
  5. 미체결 정보 업데이트 또는 삭제

트랜잭션 범위를 축소함으로써 주요 작업을 비동기적으로 처리하여 트랜잭션 내 커넥션 점유 시간을 줄였습니다. 예외 처리를 위한 보상 트랜잭션 구조는 추후 설계가 필요합니다.

테스트 결과

테스트 조건

  • 사용자: 1,000명 동시 매수 요청
  • 커넥션 풀 제한: 10개

수정 전

  • 매수 요청부터 체결까지 소요 시간: 10초 ~ 40초

  • 1,000건 매수 데이터 체결 총 소요 시간: 30초

    test1.csv

수정 후

  • 매수 요청부터 체결까지 소요 시간: 11초 ~ 25초

  • 1,000건 매수 데이터 체결 총 소요 시간: 14초

    test2.csv

총 1,000건 거래 체결 시간을 30초에서 14초로 약 50% 개선하였습니다.

문제 3: 커넥션 풀 증가

개요

트랜잭션 범위 축소 후 첫 거래 체결까지 11초가 소요되었습니다. 이는 거래 등록 시와 체결 시 모두 트랜잭션으로 인해 커넥션을 점유하여 자원이 부족해 발생한 문제로 판단되었습니다. 이에 커넥션 풀을 기존 10개에서 100개로 증가시켰습니다.

테스트 결과

테스트 조건

  • 사용자: 1,000명 동시 매수 요청
  • 커넥션 풀 제한: 100개

수정 후 커넥션 풀 증가

  • 매수 요청부터 체결까지 소요 시간: 0.8초 ~ 2.6초
  • 1,000건 매수 데이터 체결 총 소요 시간: 1.6초

test_connection_100.csv

커넥션 풀을 10개에서 100개로 증가시킴으로써, 전체 거래 체결 시간을 14초에서 1.6초로, 첫 거래 체결 시간을 11초에서 0.8초로 단축하였습니다.

추가정보

https://docs.google.com/spreadsheets/d/1K_AV-zJWKhUIBxHs92WnTzckEtmP_De4VHK48_nHYr4/edit?gid=1602479191#gid=1602479191