제가 일하고 있는 분야에서 RDBMS 에 보단 NoSql(Elasticsearch) 를 주로 쓰다 보니 공부하는 겸 공유를 하게 되었습니다.
예전부터 궁금했던것들중에 하나가 선착순으로 지급해주는 이벤트성은 DB에서 어떻게 처리할까? 였습니다.
DB lock에서 어느정도 궁금증을 해소하게 되었습니다. 혹시나 저처럼 이러한 생각을 한 분들은 이미 DB lock에 대해서 공부를 하셨다고 생각이 들지만 그럼에도 글을 공유하게 되었습니다.
DataBase는 데이터를 영속적으로 저장하는 시스템입니다. 즉 같은 자원에 대해서 동시에 접근하는 경우가 생길 수밖에 없습니다.
Lock 이란 트랜잭션 처리의 순차성을 보장하는 방법이라고 했습니다. 트랜잭션이란 DB의 나누어지지 않는 최소한의 처리 단위입니다.
2가지의 lock 종류가 존재하는데 Shared Lock 과 Exclusive 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;
좀 더 쉽게 설명을 해드리자면,
- 트랜잭션이 시작 : 데이터 일관성을 유지하기 위해 트랜잭션을 시작한다.
- 남은 쿠폰 수 조회 : 사용하지 않은 쿠폰의 수를 조회하여 '@remaining_coupons' 변수에 저장합니다.
- 쿠폰 수 체크 : '@remaining_coupons' 변수를 이용하여 남은 쿠폰 수가 100개 미만인지 확인합니다.
- 쿠폰 발급 : 남은 쿠폰 수가 100개 이상일 경우, 사용하지 않은 첫 번째 쿠폰을 해당 사용자에게 할당합니다. 'UPDATE' 문을 사용하여 'coupons' 테이블에서 'used' 상태를 '1'로 업데이트하고, 사용자 ID를 저장합니다.
- 트랜잭션 완료('COMMIT;') : 쿠폰 할당이 성공적으로 이루어졌을 때, 트랜잭션을 커밋하여 변경 사항을 영구적으로 반영합니다.
- 오류 처리 : 남은 쿠폰이 100개 미만이면 롤백을 수행하여 이전 상태로 복구하고, 쿠폰이 모두 소진되었음을 사용자에게 알린다.
정말 간단한 예시이며, 실제 환경에서는 보안 및 성능을 고려해서 추가적인 작업들이 필요하다고 생각이 듭니다.
(사용자 인증, 인덱스 설정 등...) DB 종류에 따라 문법이 다를 수 있다는 점 유의 하시면 좋겠습니다.
'DB' 카테고리의 다른 글
N+1 문제 해결: Java 백엔드 개발자를 위한 데이터베이스 쿼리 최적화 (0) | 2024.06.05 |
---|---|
PL/SQL 이란 (0) | 2022.01.10 |
데이터베이스 모델링 -1 (0) | 2022.01.07 |
SQL 사용자 권한 (0) | 2022.01.05 |
SQUENCE INDEX(순차 적으로 증가하는 값) (0) | 2022.01.05 |