Published 2024. 6. 18. 21:33
728x90

제가 일하고 있는 분야에서 RDBMS 에 보단 NoSql(Elasticsearch) 를 주로 쓰다 보니 공부하는 겸 공유를 하게 되었습니다.

예전부터 궁금했던것들중에 하나가 선착순으로 지급해주는 이벤트성은 DB에서 어떻게 처리할까? 였습니다. 

DB lock에서 어느정도 궁금증을 해소하게 되었습니다. 혹시나 저처럼 이러한 생각을 한 분들은 이미 DB lock에 대해서 공부를 하셨다고 생각이 들지만 그럼에도 글을 공유하게 되었습니다.


DataBase는 데이터를 영속적으로 저장하는 시스템입니다. 즉 같은 자원에 대해서 동시에 접근하는 경우가 생길 수밖에 없습니다.

 

Lock 이란 트랜잭션 처리의 순차성을 보장하는 방법이라고 했습니다. 트랜잭션이란 DB의 나누어지지 않는 최소한의 처리 단위입니다. 

2가지의 lock 종류가 존재하는데 Shared LockExclusive Lock이 있습니다. Shared Lock은 다른 말로 Read Lock이라고 불리며 Exclusive Lock 은 Write Lock이라고 불립니다. 

 

Shared Lock( 공유락) 공유 Lock 은 데이터를 읽을 때 사용되어지는 lock 이다. 
공유 Lock 끼리는 동시에 접근이 가능합니다.
즉, 하나의 데이터를 읽는 것은 여러 사용자가 동시에 할수 있다는 뜻입니다. 하지만 공유 Lock이 설정된 데이터에 베타 Lock을 사용할 수 없습니다.
Exclusive Lock(베타락) 데이터를 변경하고 할때 사용됩니다. 트랜잭션이 완료될 때까지 유지됩니다. 베타락은 Lock이 해제될 때까지 다른 트랜잭션(읽기 포함)은 해당 리소스에 접근할 수 없습니다. 또한 해당 LocK은 다른 트랜잭션이 수행되고 있는 데이터에 대해서는 접근 하여 함께 Lock을 설정할 수 없습니다.

 

간단한 예를 들어, 서비스를 제공하는 있는 회사에서 이벤트 성으로 선착순 100명에게 커피 쿠폰을 뿌린다고 가정했을 때를 생각해 봅시다.

 

동시다발적으로 100명 이상의 사람들이 접속을 하였지만 누군가에겐 당첨 / 꽝이 나올 겁니다. 이러한 이유는 바로 데이터베이스 잠금을 통해서입니다. 즉 DB lock은 동시에 여러 사용자가 데이터를 접근하려고 할 때 데이터 일관성을 유지하기 위해 사용된다. 

 

흐름을 한번 가정해 봅시다.

 - 이벤트 준비 단계

  •   데이터베이스 테이블 설계 :  이벤트 참여자 정보를 저장할 테이블을 설계한다. 예를 들어) 테이블에는 참여자의 이름, 이메일, 발급된 쿠폰 여부 등의 필드가 포함될 수 있습니다.

 - 쿠폰 발급 과정 : 

  • 사전 준비 : 이벤트 시작 전에 DB Lock을 설정하고, 쿠폰 발급을 준비해야 합니다.
  • 참여자의 요청 처리 : 참여자가 이벤트 페이지에 접속하여 쿠폰을 신청하면, DB lock을 걸어 다른 사용자가 동시에 같은 자원에 접근하지 못하도록 합니다.
  • 조건 확인 : DB에서 현재까지 발급된 쿠폰 수를 확인하여, 발급 가능한 쿠폰의 잔여 수가 100개 이내인지 확인합니다.

 - 쿠폰 발급 및 DB lock 해제

  • 쿠폰 발급 : 잔여 쿠폰 수가 100개 이내일 경우, 새로운 참여자에게 쿠폰을 발급하고, DB lock을 해제합니다.
  • 쿠폰 발급 실패 시 처리 : 잔여 쿠폰 수가 100개 이상이라면, 쿠폰 발급을 실패로 처리하고, 참여자에게 해당 사실을 통보한다.

- 동시 접근 관리 

  • 여러 사용자가 동시에 쿠폰을 신청할 때 DB loc을 통해 데이터 일관성을 유지하고, 데드락(deadlock)이나 경합 상태(race condition)를 방지합니다. 

 - 로그와 모니터링 

  • 쿠폰 발급 로그를 기록하고, 이벤트 참여 현황을 모니터링하여, 문제 발생 시 빠르게 대응할 수 있는 시스템을 유지합니다.

   

위 흐름에서 쿠폰 발급 과정 2단계, 즉 이용자가 이벤트 참여를 클릭하고 서버에 요청을 보낼 때 잠금을 수행하는 것이 가장 좋다고 생각을 할 수 있습니다.

 

예제 코드

-- MySQL syntax

-- 트랜잭션 시작
START TRANSACTION;

-- 사용 가능한 쿠폰이 있는지 확인합니다(쿠폰이라는 테이블이 있다고 가정).
SELECT COUNT(*) INTO @remaining_coupons FROM coupons WHERE used = 0;

-- 사용 가능한 쿠폰이 남아 있는지 확인
IF @remaining_coupons < 100 THEN
    -- Rollback the transaction if there are not enough coupons
    ROLLBACK;
    SELECT 'No more coupons available' AS message;
ELSE
    -- 현재 사용자에 대해 사용 가능한 첫 번째 쿠폰을 업데이트합니다(쿠폰_배정이라는 테이블이 있다고 가정).
    UPDATE coupons 
    SET used = 1, user_id = 'user_id_here' -- Replace '해당 이용자 ID' 실제 ID
    WHERE coupon_id = (
        SELECT coupon_id 
        FROM coupons 
        WHERE used = 0 
        ORDER BY coupon_id ASC 
        LIMIT 1
    );

    -- 쿠폰 할당이 성공하면 거래를 커밋합니다.
    COMMIT;
    SELECT 'Coupon assigned successfully' AS message;
END IF;

 

좀 더 쉽게 설명을 해드리자면,

  1. 트랜잭션이 시작 : 데이터 일관성을 유지하기 위해 트랜잭션을 시작한다.
  2. 남은 쿠폰 수 조회 : 사용하지 않은 쿠폰의 수를 조회하여 '@remaining_coupons' 변수에 저장합니다.
  3. 쿠폰 수 체크 : '@remaining_coupons' 변수를 이용하여 남은 쿠폰 수가 100개 미만인지 확인합니다.
  4. 쿠폰 발급 : 남은 쿠폰 수가 100개 이상일 경우, 사용하지 않은 첫 번째 쿠폰을 해당 사용자에게 할당합니다.  'UPDATE' 문을 사용하여 'coupons' 테이블에서 'used' 상태를 '1'로 업데이트하고, 사용자 ID를 저장합니다. 
  5. 트랜잭션 완료('COMMIT;') : 쿠폰 할당이 성공적으로 이루어졌을 때, 트랜잭션을 커밋하여 변경 사항을 영구적으로 반영합니다.
  6. 오류 처리 : 남은 쿠폰이 100개 미만이면 롤백을 수행하여 이전 상태로 복구하고, 쿠폰이 모두 소진되었음을 사용자에게 알린다.

정말 간단한 예시이며, 실제 환경에서는 보안 및 성능을 고려해서 추가적인 작업들이 필요하다고 생각이 듭니다. 

(사용자 인증, 인덱스 설정 등...) DB 종류에 따라 문법이 다를 수 있다는 점 유의 하시면 좋겠습니다.

 

 

728x90
복사했습니다!