본문 바로가기
Node js/Nest js 강의 내용

6-2 비관적락을 이용한 동시성 제어(with Prisma)

by Bill Lab 2025. 1. 27.
728x90

1. 개발한 소스에 동시성 테스트 실시

 

 

1000명의 사용자가 10초 내 30개의 재고상품이 있는 상품을 구매할 수 있는 동시성(부하테스트)를 실행한 결과이다.

서비스 로직을 보면,

const result: boolean = await this.prisma.$transaction(async (tx) => {

로 트랜젝션 단위를 보장하고 있는데, 이러한 구조로 인해 일부 동시성 이슈가 나타나지 않는것이다.

좀더 자세히 설명하면??

 

  1) 트랜잭션 사용

      : prisma.$transaction 내에서 여러 DB 작업을 묶어서 원자적으로 처리할 수 있는데, 트랜잭션이 끝날 때까지 중간에 다른 요청이 개입할 수 없다. 트랜잭션이 성공적으로 커밋되기 전에는 데이터 변경이 실제로 반영되지 않기 때문에, 두 사용자가 동시에 주문할 때도 트랜잭션을 통해 하나의 주문만 먼저 처리되고, 그 후에 다른 주문이 처리된다.

 

  2) 재고 수량 차감 로직

      : 재고를 감소시키는 부분에서 "await this.productRepository.updateProductByIds(detailWithOrderNo.product_id, detailWithOrderNo.qty, tx)"가 트랜잭션 내에서 처리되기 때문에, 해당 상품의 재고를 업데이트하는 시점까지 다른 트랜잭션에서 같은 상품의 재고를 차감하려고 해도 트랜잭션을 기다리게 된다.

 

  3) DB 락 (데이터베이스의 잠금)

      : Prisma를 사용한 트랜잭션에서는 보통 데이터베이스 자체의 잠금 메커니즘이 동작하게 되는데, 재고가 부족한 상품을 주문하려 할 때, 하나의 트랜잭션에서 UPDATE 문을 실행하여 재고를 차감하고, 다른 트랜잭션은 해당 행을 수정할 수 없도록 row level 락을 걸게 된다.(비관적락 발생)

 

  4) 순차적 처리

       : 주문 내역이 담긴 orderDetails 배열을 순차적으로 처리하면서 각 주문이 완료된 후에만 재고 업데이트가 이루어지기 때문에, 여러 사용자가 동시에 재고를 업데이트하려는 상황에서 하나의 트랜잭션이 끝날 때까지 다른 트랜잭션이 대기하게 된다.

 

 

2. 여전히 동시성 발생가능성은 존재

    : 하지만, 다른 외부 트랜젝션의 개입으로 재고 수량에 대한 동시성 이슈가 발생할 가능성은 일부 존재한다. 이를 해결하기 위한 조치로는 상품정보를 조회하는 시점에 low level 락을 걸어서(update 하는 시점이 아닌) 동시성 이슈를 더 타이틀하게 보장하는 것이다.

   

async getProductByIdsWithLock(
    productIds: Array<number>,
    tx: Prisma.TransactionClient = this.prisma,
  ): Promise<product[]> {
    const productInfo = await tx.$queryRaw<product[]>`
    SELECT * 
    FROM product 
    WHERE id IN (${Prisma.join(productIds)}) 
    FOR UPDATE;
  `;
    return productInfo;
  }

https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries

 

Raw queries | Prisma Documentation

Learn how you can send raw SQL and MongoDB queries to your database using the raw() methods from the Prisma Client API.

www.prisma.io

 

 

728x90