-
Notifications
You must be signed in to change notification settings - Fork 1
같은 주식 주문이 동시에 여러 번 체결되는 문제
sieun edited this page Dec 5, 2024
·
3 revisions
- 삼성 전자의 주식을 10주 매수 요청하면 체결이 30주가 되는 이상한 현상이 일어남.
- 로컬 환경에서는 해당 현상이 일어나지 않고, 서버에 배포를 했을 경우에만 일어나는 현상이라는 사실을 인지함.
- 서버 로그를 확인해 본 결과, 3개의 컨테이너에서 같은 주문이 중복으로 처리된다는 사실을 알게 됨.
- 원인을 파악해 본 결과, 주문 및 체결 시 아래의 흐름도와 같은 양상을 보임
sequenceDiagram
participant C as Client
participant S1 as Server 1 (구독중)
participant S2 as Server 2 (구독중)
participant S3 as Server 3 (구독중)
participant DB as Database
participant KI as Korea Investment OpenAPI
note over C: 10주 주문
C->>S1: 10주 주문 등록
par
KI-->>S1: 실시간 체결가 반환
S1->>DB: 10주 매수 주문 체결
KI-->>S2: 실시간 체결가 반환
S2->>DB: 10주 매수 주문 체결
KI-->>S3: 실시간 체결가 반환
S3->>DB: 10주 주문 체결
end
note over DB: 30주 체결
- 컨테이너 3개에서 동시에 체결가가 들어와 일어나는 상황이라는 것을 인지하고, 처음에는 기존 트랜잭션 격리 수준을 높여 관리하려고 함.
- 기존에는 mysql의 기본 트랜잭션 격리 수준인
REPEATABLE_READ
을 사용하고 있었으나, 하나의 체결이 진행 중일 때에는 다른 체결이 진행되어서는 안된다고 생각해 아래와 같이SERIALIZABLE
격리 수준으로 변경함.
async checkExecutableBuyOrder(stockCode, value) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.startTransaction('SERIALIZABLE');
try {
const buyOrders = await queryRunner.manager.find(Order, {
where: {
stock_code: stockCode,
trade_type: TradeType.BUY,
status: StatusType.PENDING,
price: MoreThanOrEqual(value),
},
});
await Promise.all(
buyOrders.map((buyOrder) => this.executeBuy(queryRunner, buyOrder)),
);
await queryRunner.commitTransaction();
return buyOrders.length;
} catch (err) {
await queryRunner.rollbackTransaction();
throw new InternalServerErrorException(err);
} finally {
await queryRunner.release();
}
}
- 격리 수준을 변경하니 체결이 중복되어 일어나는 현상은 사라졌으나, 체결 속도가 눈에 띄게 느려진 사실을 확인할 수 있었음.
- 주식 거래는 실시간성이 매우 중요한 요소이기 때문에, 성능 측면에서 보아도
SERIALIZABLE
격리 수준은 적합하지 않다고 생각함.
- 주식 거래는 실시간성이 매우 중요한 요소이기 때문에, 성능 측면에서 보아도
- 성능을 조금 더 향상시키기 위해서,
SERIALIZABLE
로 변경했던 격리 수준을 다시REPEATABLE_READ
로 변경함. 대신 동시성을 처리하기 위해 DB에서 체결 가능한 주문을 찾는 find문에 아래와 같이 lock을 걸기로 결정함.- lock에도 종류가 많아 고민해본 결과, 어떤 체결 가능한 주문이 체결 중일 때에는 다른 트랜잭션에서 읽기, 쓰기 모두 불가능해야 동시에 여러 체결이 진행되어도 독립적일 수 있을 것이라 생각해
pessimistic_write
lock을 걸기로 결정함.
- lock에도 종류가 많아 고민해본 결과, 어떤 체결 가능한 주문이 체결 중일 때에는 다른 트랜잭션에서 읽기, 쓰기 모두 불가능해야 동시에 여러 체결이 진행되어도 독립적일 수 있을 것이라 생각해
async checkExecutableBuyOrder(stockCode, value) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.startTransaction();
try {
const buyOrders = await queryRunner.manager.find(Order, {
where: {
stock_code: stockCode,
trade_type: TradeType.BUY,
status: StatusType.PENDING,
price: MoreThanOrEqual(value),
},
lock: {
mode: 'pessimistic_write',
},
});
await Promise.all(
buyOrders.map((buyOrder) => this.executeBuy(queryRunner, buyOrder)),
);
await queryRunner.commitTransaction();
return buyOrders.length;
} catch (err) {
await queryRunner.rollbackTransaction();
throw new InternalServerErrorException(err);
} finally {
await queryRunner.release();
}
}
- 해당 방식으로 변경하니 컨테이너 3개를 동시에 띄워도 각 주문이 독립적으로 체결되며, 성능에 있어서도 큰 차이가 없었음.
- [FE] 프론트엔드 기술스택
- [FE] 라이브러리 없이 차트 구현 이유
- [FE] Canvas API 사용방법
- [FE] 네비게이션 바 애니메이션 구현
- [FE] Socket.io 사용방법
- [FE] Tanstack Router에 대하여...
- [FE] Intl(Internationalization) API
- [FE] React Suspense 적용
- [FE] 한글 입력 방식의 유연성을 높인 검색 시스템 구현하기
- [BE] 백엔드 기술 스택
- [BE] SSE vs Socket.io
- [BE] Redis를 도입하게 된 계기
- [BE] ACG Rule을 활용한 Secure CI CD 파이프라인 구현
- [BE] Nginx 로드밸런싱을 통해 한국 투자 API 소켓 제한 극복
- [BE] 주가 지수 기능 개발 과정
- [BE] 매수 및 매도 기능 개발 과정
- [BE] 실시간 자산 조회 기능 개발 과정
- [BE] 단위 테스트
- [BE] redis를 이용한 한국투자 Open API 세션 관리
- [BE] 데이터베이스 인덱싱
- [FE] React에서의 DOM 요소 접근 (useRef vs getElementById)
- [FE] Outlet을 활용한 공통 레이아웃 관리
- [FE] react hooks가 특정 조건에서 실행되면 안되는 이유 & useQuery에 query function 매개변수가 undefined일 수도 있을 때 어떻게 해결할까
- [FE] cross‐domain 로컬 환경에서 cookie로 인증 처리하기 with vite proxy
- [FE] 크롬&사파리 Composition 차이
- [FE] useEffect 의존성 배열
- [BE] Naver Cloud Platform HTTPS 무응답 현상
- [BE] 한국투자 Open API에서 access token을 발급받지 못하는 문제
- [BE] 한국투자 Open API와 웹소켓 연결이 되지 않던 문제
- [BE] 한국투자 Open API 웹소켓 연결이 중단되는 문제
- [BE] 같은 주식 주문이 동시에 여러 번 체결되는 문제
- [BE] 한국투자 Open API Websocket 세션을 두 개에서 한 개로 변경하기
- [BE] Nginx 로드 밸런싱 중 Socket bad Request 발생하는 현상
- [BE] 매수/매도 체결 로직에 의해 redis pub/sub이 정상적으로 동작하지 않는 문제