동시성이슈(Concurrency Issue)

개발자·2023년 2월 18일

spring

목록 보기
1/1
post-thumbnail

재고시스템으로 알아보는 동시성이슈 및 해결방법

해당 포스팅은 인프런 최창용님의 '재고시스템으로 알아보는 동시성이슈 해결방법'을 학습 후 정리한 내용입니다.

Race Condition이란?

멀티쓰레드 환경에서 둘 이상의 쓰레드가 공유 자원에 접근하려 할 때, 그 공유 자원의 최종 상태가 쓰레드 실행 순서에 따라 달라지는 상황을 말합니다.

문제

예를 들어, 하나의 변수를 여러 쓰레드가 동시에 읽고 쓰기를 시도한다면, 각 쓰레드가 읽고 쓰는 순서에 따라 변수 값이 예상치 못한 결과를 가져올 수 있습니다. 이러한 상황이 발생하면 프로그램의 정확성을 보장할 수 없으며, 디버깅이 어려워집니다.

  • 여러 쓰레드가 동시에 공유 자원에 접근하는 경우
  • 쓰레드가 공유 자원에 접근하는 시점과 다른 쓰레드가 공유 자원을 수정하는 시점이 겹치는 경우
  • 쓰레드가 공유 자원을 수정할 때, 다른 쓰레드가 그 공유 자원을 읽는 경우

해결방법

  • 첫 번째는 락(lock)을 사용하는 것입니다.
    • 락은 공유 자원에 대한 접근을 제어하여 동시에 여러 스레드 또는 프로세스가 동시에 접근하지 못하도록 합니다.
  • 두 번째로는 트랜잭션(transaction)을 사용하는 것입니다.
    • 트랜잭션은 데이터베이스 등에서 여러 개의 작업을 하나의 단위로 묶어서 실행하는 것으로, 이를 통해 데이터의 일관성을 유지할 수 있습니다.

Syncronized이란 ?

synchronized는 자바에서 동시성 문제를 해결하기 위해 제공되는 키워드 중 하나로, 멀티쓰레드 환경에서 동기화를 제공합니다.

여러 쓰레드가 동시에 하나의 객체에 접근할 때, 동기화 없이 동시에 접근하면 예측할 수 없는 결과가 발생할 수 있습니다. 따라서 synchronized를 사용하여 객체에 대한 여러 개의 쓰레드의 접근을 동기화시키면, 하나의 쓰레드가 객체에 접근하고 수정하는 동안 다른 쓰레드들은 대기하게 됩니다.


@Transactional이란 ?

@Transactional은 스프링 프레임워크에서 제공하는 어노테이션 중 하나로, 메소드 실행 시 트랜잭션을 적용하기 위해 사용됩니다. 트랜잭션은 데이터베이스의 상태를 변화시키는 작업을 모두 하나의 논리적인 작업 단위로 묶어서 실행시키기 때문에, 데이터 무결성과 일관성을 유지할 수 있습니다.

@Transactional 어노테이션을 사용하면 메소드 실행 시 자동으로 트랜잭션을 시작하고, 메소드가 정상적으로 종료될 때 트랜잭션을 커밋합니다. 만약 메소드 실행 중 예외가 발생하면, 트랜잭션을 롤백합니다. 이를 통해 개발자는 직접 트랜잭션을 관리하지 않고도 간편하게 데이터베이스 작업을 수행할 수 있습니다.

@Transactional 어노테이션은 클래스나 메소드에 적용할 수 있으며, 클래스에 적용하면 해당 클래스의 모든 메소드에 트랜잭션을 적용할 수 있습니다. 기본적으로 @Transactional은 읽기 작업에는 적용되지 않습니다. 만약 읽기 작업에도 트랜잭션을 적용하고 싶다면 readOnly 속성을 true로 설정해야 합니다.

@Transactional(readOnly = true)
public void readData() {
    // ...
}

**@Transactional을 사용할 때는 올바른 격리 수준을 설정하고, 최적의 트랜잭션 관리 방식을 선택하는 것이 중요합니다.**

@Transactional과 함께 사용할 때 주의할점

@Transactional 어노테이션은 메서드에서 실행되는 모든 데이터베이스 작업을 트랜잭션으로 묶어줍니다. 이는 여러 개의 동시 요청이 같은 트랜잭션에 영향을 미칠 수 있다는 것을 의미합니다.

따라서 synchronized 키워드와 함께 사용될 경우 동시성 이슈를 완전히 해결하지 못할 수 있습니다.

동시성 이슈를 해결하려면 @Transactional 어노테이션의 격리 수준을 높여 동시성 이슈를 해결할 수 있습니다. 기본적으로 스프링은 READ_COMMITTED 격리 수준을 사용합니다. 이를 높이면 트랜잭션 범위 내에서 다른 쓰레드의 작업을 차단하여 동시성 이슈를 해결할 수 있습니다.

@Transactional(isolation = Isolation.SERIALIZABLE)
public void doSomething() {
    // ...
}

동시성 이슈 발생

@Transactional 어노테이션은 트랜잭션 범위 내에서 다른 쓰레드에게 접근을 허용하므로, synchronized 키워드와 함께 사용될 경우 동시성 이슈를 완전히 해결하지 못할 수 있습니다.

따라서, synchronized 키워드와 @Transactional 어노테이션을 함께 사용하는 것은 권장되지 않습니다. 대신, 데이터베이스에서 적절한 동시성 제어 방법을 사용하거나, @Transactional 어노테이션의 격리 수준을 높여 동시성 이슈를 해결할 수 있습니다.

해결방법

synchronized키워드는 메서드 전체에 적용되어 있지만, 필요한 부분만 synchronized블록으로 변경하면 성능 개선을 할 수 있습니다

@Transactional
public synchronized void decrease(Long id, Long quantity) {
		//실행메소드
}

Database를 활용해서 Race Condition해결해보기

  1. Optimistic Lock (낙관적인 락)
    1. lock 을 걸지않고 문제가 발생할 때 처리합니다.대표적으로 version column 을 만들어서 해결하는 방법이 있습니다.
  2. Pessimistic Lock (exclusive lock) (비관적인 락)
    • row나 table 단위로 적용
    1. 다른 트랜잭션이 특정 row 의 lock 을 얻는것을 방지합니다.
      1. A 트랜잭션이 끝날때까지 기다렸다가 B 트랜잭션이 lock 을 획득합니다.
    2. 특정 row 를 update 하거나 delete 할 수 있습니다.
    3. 일반 select 는 별다른 lock 이 없기때문에 조회는 가능합니다.
  3. named Lock 활용하기
    • Meta-data에 lock을 적용
    1. 이름과 함께 lock 을획득합니다. 해당 lock 은 다른세션에서 획득 및 해제가 불가능합니다.

참고문헌
https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_exclusive_lock
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html

profile
Developer

0개의 댓글