본문 바로가기

교육/항해99+ 백엔드

동시성 이슈에 대한 조사

나의 시나리오(e-commerce)에서 발생할 수 있는 동시성 이슈

 

선착순 쿠폰 발행 

- 쿠폰이 1개 남았을 경우에 여러유저가 쿠폰 발행을 시도한다면?

주문생성 - 재고감소

- 주문 생성시에 재고가 감소하고 재고가 1개 남았을때 여러 유저가 주문을 시도한다면?

 

 

위의 시나리오에서 예측되는 동시성 이슈에 적용할 수 있는 제어 방법들

 

1. DB lock

구현복잡도 : 간단하다. 

- 낙관적 락 (Optimistic Lock) 

:save시 락을 거는 방법이다.(경합이 별로 안일어날 것이다~ 해서 낙관적 락)

 

성능 

 

- 락을 걸기 전에 select 구문을 사용하지 않기 때문에 성능적으로는 비관적 lock보다 우수하다고 볼 수 있다.

- 트랜잭션을 필요로 하지 않는다.

 

- 효율성 : 경합이 많이 일어나는 상황일 수록 효율이 떨어진다.

 

- 구현 방법

 

1. 반영하고 싶은 Entity에 아래와 같은 어노테이션과 필드를 만들어준다.

 

@Version // 낙관적 락 적용

private Integer version;

 

2, 테스트 코드 스레드에 코드로 만들어준다.

- 비관적 락 (Pessimistic Lock)                                      

 select 시 락을 거는 방법이다.(무조건 경합이 많이 일어날 것이다 해서 비관적 락)

성능 :

비관적 락은 Shared lock(s-lock)과 Exclusive lock(x-lock) 두 종류를 쓴다.

둘다 transaction이 완료 되어야 lock이 해제 되는 경우이므로 낙관적 락에 비해 속도 자체는 떨어진다.(시간이 오래걸린다?) 

 

- 효율성 : 경합이 많이 일어나는 상황일 수록 효율이 좋다.

엄청나게 많은 사람들이 공유 자원을 획득하려고 해도 한명이 획득하게 되면 나머지는 튕겨나가므로 효율이 좋다고 할 수 있다.

또한 락이 걸려 있는 동안의 데이터 정합성을 보장한다.

 

2. Redis, Kafka 와 같은 메세징 시스템 사용

메세징 시스템은 프로그램과 rdbms 사이에 메세징 서버를 두고 락을 중제하는 방법이다.

 

성능 

: db에서 한번에 처리하지 못하는 대규모 시스템을 메세징 시스템을 거치면서 부하 조절이 가능하고

Redis 같은 경우에는 인메모리 db라서 속도가 RDBMS보다 훨씬 빠르다.

 

구현 난이도

낙관적 lock이나 비관적 lock에 비해 메시징 시스템을 따로 설치하는 과정이 추가되고 설정 파일이나 aop 설정등이 필요하기 때문에 구현 난이도는 db lock에 비해 훨씬 어렵다고 볼 수 있다. (메시징 시스템 설계를 어떻게 하느냐에 따라서도 구현 난이도가 올라간다고 한다)

 

효율성

Redis의 sub/pub (발행/구독) 시스템으로 lock을 획득하고 처리하는 과정이 효율적으로 이뤄질 수 있다. 한 클라이언트의 lock이 끝나면 구독한 다른 시스템들에게 메시지가 한번에 발송되기 때문이다.

 

Kafka의 경우에는 message queue 시스템을 통해 순차처리를 효율적으로 할 수 있다. (서버에서 바로 처리하지 않아도 되는 일들을 메시지 큐에 넣어 뒀다가 천천히 처리 가능하다)

 

 

============================================================================================

주요 동시성 문제들

 

1. Race Condition


   두 개 이상의 프로세스나 스레드가 동시에 공유 자원에 접근하고, 그 결과가 실행 순서에 따라 달라지는 상황을 말합니다. 예를 들어, 두 스레드가 동시에 같은 변수를 수정하려고 할 때, 최종 결과는 어떤 스레드가 먼저 실행되느냐에 따라 달라질 수 있습니다. 이는 예기치 않은 동작이나 오류를 초래할 수 있습니다.

 

    지금 진행하고 있는 e-commerce 프로젝트에서 선착순 쿠폰 발급의 경우 - 쿠폰이 1장 남았을 때 thread1유저와 thread2유저가 쿠폰에 접근 하는 순서에 따라 어떤 유저가 쿠폰을 선점하였는지에 대한 결과가 달라질 수 있습니다.

 


2. Deadlock


   Deadlock은 두 개 이상의 프로세스가 서로가 점유하고 있는 자원을 기다리며 무한정 대기하는 상황을 말합니다. 예를 들어, 프로세스 A가 자원 1을 점유하고 자원 2를 요청하고, 프로세스 B가 자원 2를 점유하고 자원 1을 요청하는 경우, 두 프로세스는 서로를 기다리며 교착 상태에 빠지게 됩니다.

 

2-1. 데드락 발생 조건


- 상호 배제(Mutual Exclusion): 자원은 한 번에 하나의 프로세스만 사용할 수 있어야 하며, 다른 프로세스는 해당 자원을 사용할 수 없다.

- 점유 대기(Hold and Wait): 프로세스가 자원을 점유한 상태에서 다른 자원을 요청할 수 있어야 한다. 즉, 프로세스가 자원을 점유한 채로 다른 자원을 기다리는 상황이 발생해야 한다.

- 비선점(Non-preemption): 이미 할당된 자원은 강제로 빼앗을 수 없다. 프로세스가 자원을 자발적으로 반납할 때까지 다른 프로세스가 해당 자원을 사용할 수 없다.

- 순환 대기(Circular Wait): 프로세스들이 서로 자원을 기다리는 순환 구조가 형성되어야 한다

. 즉, 프로세스 P1이 P2의 자원을 기다리고, P2가 P3의 자원을 기다리며, P3가 P1의 자원을 기다리는 형태가 되어야 한다.

 

 

DB 관련

 

3. Dirty Read (더티 리드)


   더티 리드는 트랜잭션이 아직 커밋되지 않은 다른 트랜잭션의 데이터를 읽는 상황을 말합니다. 예를 들어, 트랜잭션 A가 데이터를 수정하고 있지만 아직 커밋하지 않은 상태에서, 트랜잭션 B가 그 데이터를 읽는 경우입니다. 만약 트랜잭션 A가 롤백되면, 트랜잭션 B는 잘못된 데이터를 기반으로 작업을 수행하게 됩니다.

4. Non-repeatable Read


   비반복 읽기는 같은 트랜잭션 내에서 동일한 쿼리를 두 번 실행했을 때, 결과가 다르게 나오는 상황을 말합니다. 예를 들어, 트랜잭션 A가 특정 데이터를 읽은 후, 트랜잭션 B가 그 데이터를 수정하고 커밋하면, 트랜잭션 A가 다시 같은 데이터를 읽을 때 다른 결과를 얻게 됩니다.

5. Phantom Read (팬텀 리드)


팬텀 리드는 트랜잭션이 특정 쿼리를 실행한 후, 다른 트랜잭션이 새로운 데이터를 삽입하거나 삭제하여, 다음에 같은 쿼리를 실행했을 때 결과 집합이 달라지는 상황을 말합니다. 예를 들어, 트랜잭션 A가 특정 조건을 만족하는 레코드를 읽은 후, 트랜잭션 B가 새로운 레코드를 삽입하면, 트랜잭션 A가 다시 같은 쿼리를 실행했을 때 새로운 레코드가 포함된 결과를 얻게 됩니다.

 

 

DB의 동시성 문제와 제어 방법

 

1. DB Transaction Isolation Levels

 

   데이터베이스 트랜잭션의 격리 수준은 트랜잭션이 서로 간섭하지 않도록 보장하는 방법을 정의합니다. 주요 격리 수준으로는 Read Uncommitted, Read Committed, Repeatable Read, Serializable이 있으며, Serializable이 가장 높은 수준입니다.(DB별로 제공하는 트랜잭션 격리 수준 레벨이 조금씩 다름)

 

1-1. Read Uncommitted

가장 낮은 격리 수준입니다.
트랜잭션이 커밋되지 않은 데이터를 읽을 수 있습니다. 즉, 다른 트랜잭션에서 변경한 데이터가 아직 커밋되지 않았더라도 읽을 수 있습니다.
이로 인해 "더티 리드(Dirty Read)"가 발생할 수 있습니다. 즉, 한 트랜잭션이 다른 트랜잭션의 변경 사항을 읽고, 그 변경 사항이 롤백될 경우 일관성이 깨질 수 있습니다.

 

1-2. Read Committed

트랜잭션이 커밋된 데이터만 읽을 수 있습니다.
더티 리드는 방지되지만, "비결정적 읽기(Non-repeatable Read)"가 발생할 수 있습니다. 즉, 같은 트랜잭션 내에서 동일한 쿼리를 두 번 실행했을 때, 두 번째 쿼리에서 다른 결과를 얻을 수 있습니다. 이는 다른 트랜잭션이 데이터를 변경하고 커밋했기 때문입니다.

 

1-3. Repeatable Read

트랜잭션이 시작된 이후에 읽은 데이터는 트랜잭션이 완료될 때까지 변경되지 않습니다.
더티 리드와 비결정적 읽기는 방지되지만, "팬텀 리드(Phantom Read)"가 발생할 수 있습니다. 이는 트랜잭션이 실행되는 동안 다른 트랜잭션이 새로운 행을 삽입하여, 같은 조건의 쿼리를 다시 실행했을 때 결과가 달라지는 현상입니다.
MySQL의 기본 격리 수준입니다.

 

1-4. Serializable

가장 높은 격리 수준으로, 트랜잭션이 순차적으로 실행되는 것처럼 보이게 합니다.
모든 트랜잭션이 서로 독립적으로 실행되며, 더티 리드, 비결정적 읽기, 팬텀 리드를 모두 방지합니다.
성능이 저하될 수 있으며, 트랜잭션 간의 충돌이 발생할 가능성이 높아집니다.

 

 

2. DB Lock (S-lock, X-lock)

 

   데이터베이스에서 S-lock(Shared Lock)은 여러 트랜잭션이 동시에 읽을 수 있도록 허용하지만, X-lock(Exclusive Lock)은 단일 트랜잭션만이 해당 자원을 수정할 수 있도록 제한합니다. S-lock은 읽기 전용 작업에 사용되고, X-lock은 쓰기 작업에 사용됩니다.

 

https://dev.mysql.com/doc/refman/9.1/en/innodb-locking.html#innodb-shared-exclusive-locks

 

MySQL :: MySQL 9.1 Reference Manual :: 17.7.1 InnoDB Locking

This section describes lock types used by InnoDB. Shared and Exclusive Locks InnoDB implements standard row-level locking where there are two types of locks, shared (S) locks and exclusive (X) locks. If transaction T1 holds a shared (S) lock on row r, then

dev.mysql.com

 

 

 

https://dev.mysql.com/doc/refman/9.1/en/glossary.html#glos_shared_lock

 

MySQL :: MySQL 9.1 Reference Manual :: MySQL Glossary

MySQL 9.1 Reference Manual  /  MySQL Glossary These terms are commonly used in information about the MySQL database server. A .ARM file Metadata for ARCHIVE tables. Contrast with .ARZ file. Files with this extension are always included in backups produce

dev.mysql.com

 

shared lock

A kind of lock that allows other transactions to read the locked object, and to also acquire other shared locks on it, but not to write to it. The opposite of exclusive lock.

 

다른 트랜잭션이 lock 객체를 읽고, shared lock을 획득할 수 있지만,  write는 할 수 없는 잠금 의 일종입니다 . exclusive lock 의 반대입니다.

 

-> 공유 락의 경우 읽기 하는 사람이 계속 늘어나면 락이 계속 전파된다. 

 

https://dev.mysql.com/doc/refman/9.1/en/glossary.html#glos_exclusive_lock

 

MySQL :: MySQL 9.1 Reference Manual :: MySQL Glossary

MySQL 9.1 Reference Manual  /  MySQL Glossary These terms are commonly used in information about the MySQL database server. A .ARM file Metadata for ARCHIVE tables. Contrast with .ARZ file. Files with this extension are always included in backups produce

dev.mysql.com

 

exclusive lock

A kind of lock that prevents any other transaction from locking the same row. Depending on the transaction isolation level, this kind of lock might block other transactions from writing to the same row, or might also block other transactions from reading the same row. The default InnoDB isolation level, REPEATABLE READ, enables higher concurrency by allowing transactions to read rows that have exclusive locks, a technique known as consistent read.

 

다른 트랜잭션이 같은 행(row)을 잠그지 못하도록 하는 lock 유형입니다 . 트랜잭션 격리 수준 에 따라 이러한 잠금 유형은 다른 트랜잭션이 같은 행에 쓰는 것을 차단하거나 다른 트랜잭션이 같은 행을 읽는 것을 차단할 수도 있습니다. 기본 격리 수준인 REPEATABLE READ 는 트랜잭션이 배타적 잠금이 있는 행을 읽을 수 있도록 하여 더 높은 동시성을 가능하게 하며, 이 기술을 일관된 읽기 라고 합니다 . InnoDB

 

-> x-lock은 격리 수준이 REPEATABLE READ 이면 다른 트랜잭션이 읽기는 가능하게도 할 수 있다.

아마 Serializable 인 경우에 읽기와 쓰기 모두 불가능하게 하는 것으로 보인다.

 

 

 

3. Optimistic Lock vs Pessimistic Lock

 

3. 비관적 락 vs 낙관적 락 비교

특성                             비관적 락 (Pessimistic Lock)                                      낙관적 락 (Optimistic Lock)

동작 방식 데이터 접근 시 락 설정 데이터 수정 시점에 충돌 검증
데이터 충돌 가능성 데이터 충돌 가능성이 높을 때 적합 데이터 충돌 가능성이 낮을 때 적합
성능 성능 저하 가능성 (락 사용) 높은 성능 (락 없음, 충돌 발생 시 롤백)
데드락 발생 가능성 있음 없음
충돌 처리 방식 충돌이 사전에 방지됨 충돌 발생 시 예외 처리
JPA 락 모드 PESSIMISTIC_READ, PESSIMISTIC_WRITE OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT