볞묞 바로가Ʞ

SpringBoot

[SpringBoot] 동시성 묞제륌 핎결하자 (Synchronized, MySQL, Redis)

💭 듀얎가며

 

진행하던 프로젝튞에서 위와 같읎 응원 횟수륌 여러번 큎늭하멎 읎에 맞게 횟수가 슝가되는 로직을 구현핎알 했닀.

한 명의 사용자는 여러번 응원 횟수륌 슝가시킬 수 있닀.

읎와 ꎀ렚핎 얎떻게 동시성 묞제륌 핎결할 수 있을지에 대한 고믌을 시작했닀.

✅ 동시성 묞제란?

  • 동시성 묞제가 발생하지 않는 상황

pk가 1읞 응원 데읎터의 응원 횟수가 90읞 상태에서 사용자 A 가 3번 응원 횟수륌 슝가시킀고 사용자 B 가 10번 응원 횟수륌 슝가시쌰닀고 가정핎볎자.

사용자 A 응원 횟수 사용자 B
pk가 1읞 응원 데읎터륌 찟는닀 >> 아 응원 횟수는 90읎구나! 90회  
pk 가 1읞 응원 데읎터의 횟수륌 3회 슝가시킚닀. 93회  
  93회 pk가 1읞 응원 데읎터륌 찟는닀 >> 아 응원 횟수는 93읎구나!
  103회 pk 가 1읞 응원 데읎터의 횟수륌 10회 슝가시킚닀.

위처럌 정상적윌로 요청읎 반영되얎 각각 3회, 10회륌 더한 103회가 결곌로 나올 것윌로 Ʞ대한닀.

 

  • 동시성 묞제가 발생하는 상황
사용자 A 응원 횟수 사용자 B
pk가 1읞 응원 데읎터륌 찟는닀 >> 아 응원 횟수는 90읎구나! 90회  
  90회 pk가 1읞 응원 데읎터륌 찟는닀 >> 아 응원 횟수는 90읎구나!
pk 가 1읞 응원 데읎터의 횟수륌 3회 슝가시킚닀. 93회  
    pk 가 1읞 응원 데읎터의 횟수륌 10회 슝가시킚닀.
  100회  

귞러나, 동시성 묞제가 고렀되지 않는닀멎 위처럌 음부 횟수가 누띜될 가능성읎 졎재한닀.

✅ 동시성 묞제의 원읞 - Race Condition

  • Race condition 읎란?

읎는 두개 읎상의 쓰레드가 공유 데읎터에 액섞슀가 가능한 동시에 변겜을 하고자 하멎 발생하는 묞제읎닀.

Race Condition 에 의핎서 데읎터의 결곌는 공용 데읎터에 대한 접귌에 대한 순서에 의핎 달띌지게 된닀.

 

위의 예시로 닀륎게 읎알Ʞ하자멎, 사용자 A 와 사용자 B 가 순서대로 접귌하게 되멎 정상적윌로 결곌가 반영되지만 동시에 접귌하게 되멎 잘못된 결곌가 나였는 것읎닀.

❌ 묞제가 발생한 윔드

  • request body
// Ʞ졎의 request body
{
	gameTeamId:1,
    cheerCount:101
}

 

  • Repository 윔드
@Modifying
@Query("UPDATE GameTeam t SET t.cheerCount = :cheerCount WHERE t.id = :gameTeamId")
void updateCheerCount(@Param("gameTeamId") Long gameTeamId, @Param("cheerCount") int cheerCount);
  • 테슀튞 윔드
@Test
void 동시에_응원_요청을_볎낌_겜우에도_정상적윌로_요청읎_반영된닀() throws InterruptedException {
    // given
    ExecutorService executor = Executors.newFixedThreadPool(100);
    CountDownLatch latch = new CountDownLatch(100);

    long gameTeamId = 3L;

    // when
    for (int i = 0; i < 100; i++) {
        final int j = i;
        executor.execute(() -> {
            try {
                gameTeamService.updateCheerCount(2L, new GameTeamCheerRequestDto(gameTeamId, j + 1));
            } finally {
                latch.countDown();
            }
        });
    }

    latch.await();
    executor.shutdown();

    // then
    assertThat(gameTeamFixtureRepository.findById(gameTeamId))
            .map(GameTeam::getCheerCount)
            .get()
            .isEqualTo(101);
}

동시성 묞제가 발생했던 윔드는 위와 같았닀.

큎띌읎얞튞 잡에서 Ʞ졎의 횟수에 슝가시킬 횟수륌 더핎서 요청을 볎낎멎, 읎륌 반영하도록 했었닀.

슉, Ʞ졎 횟수가 100회읎고 슝가시킬 횟수가 1읎띌멎 큎띌읎얞튞 잡에서 101을 요청윌로 볎냈던 것읎닀.

 

테슀튞 결곌는 닀음곌 같닀.

Ʞ대했던 횟수는 1에서 100번 슝가시킚 101 회였닀.

귞러나, 싀제 반영된 데읎터는 81회임을 확읞할 수 있었닀.

👀 얎떻게 핎결했을까

  • request body
// Ʞ졎의 request body
{
	gameTeamId:1,
    cheerCount:101
} 

// 수정된 request body
{
	gameTeamId:1,
    cheerCount:1
}

큎띌읎얞튞 잡에서 직접 Ʞ졎의 횟수에 새로 슝가할 횟수륌 더핎서 요청을 볎냈던 것에서, 슝가할 횟수만을 요청윌로 볎낎도록 수정했닀.

Ʞ졎의 request body 에는 Ʞ졎의 횟수가 100회읎고 슝가시킬 횟수가 1읎띌멎 101을 볎냈지만, 수정된 방식에서의 request body 에는 1만을 볎낎는 것읎닀.

@Modifying
@Query("UPDATE GameTeam t SET t.cheerCount = t.cheerCount + :cheerCount WHERE t.id = :gameTeamId")
void updateCheerCount(@Param("gameTeamId") Long gameTeamId, @Param("cheerCount") int cheerCount);

위의 쿌늬륌 볎멎 알겠지만, 요청읎 듀얎왔을 때 ê·ž 시점에서의 응원횟수에 새로욎 cheerCount 륌 더한닀.

 

읎렇게 수정하니, ì–žì œ 쓰레드가 데읎터륌 읜었는지와는 ꎀ계가 없얎지므로 슉, race condition 묞제가 자연슀레 핎결되얎 동시성 묞제륌 핎결할 수 있었닀.

👀 아쉬웠던 점

사싀 위의 방식은, 핚께 프로젝튞에 찞여하고 있는 동료분께 추천을 받아 반영했던 방법읎었닀.

따띌서 동시성 묞제에 대한 핎결방안읎 ì–Žë–€ 것듀읎 더 있을지에 대핮 공부하고, 학습하여 추후에 얎떻게 개선핎볎멎 좋을지에 대핮 고믌핎볎고 싶었닀.

 

📝 동시성 묞제 핎결 방안

닀음 강의륌 찞고핎서 작성했습니닀 :)

https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard

 

재고시슀템윌로 알아볎는 동시성읎슈 핎결방법 강의 - 읞프런

동시성 읎슈란 묎엇읞지 알아볎고 처늬하는 방법듀을 학습합니닀., 동시성 읎슈 처늬도 자신있게! ê°„닚한 재고 시슀템윌로 찚귌찚귌 배워볎섞요. 백엔드 개발자띌멎 êŒ­ 알아알 할 동시성 읎슈

www.inflearn.com

💥 Synchronized

✔ synchronized란?

여러 요청읎 듀얎왔을 때 묞제가 발생하는 읎유는 슀레드 간의 동Ʞ화가 되지 않Ʞ 때묞읎닀.

따띌서 synchronized 는 thread-safe 륌 위핎서 여러개의 슀레드가 한개의 자원을 사용하고자 할 때, 현재 데읎터륌 사용하고 있는 슀레드륌 제왞하고는 데읎터에 접귌할 수 없도록 막는 것읎닀.

읎는 변수와 핚수에 사용 가능하닀.

✔ 사용 예시

  @Transactional
    public synchronized void decrease(Long id, Long quantity) {
        Stock stock = stockRepository.findById(id).orElseThrow();
        stock.decrease(quantity);
        stockRepository.saveAndFlush(stock);
    }

✔ 닚점

1. @Transactional 얎녞테읎션의 프록시 객첎

synchronized 는 슀프링부튞의 @Transactional 얎녞테읎션곌 핚께 사용하멎 사싀상 횚력읎 없닀.

왜냐하멎, @Transactional 얎녞테읎션은 AOP 읎Ʞ 때묞에 프록시 객첎가 만듀얎지며 동작하는데 읎 때 프록시 객첎는 메서드의 시귞니처듀만 복사핎였Ʞ 때묞에 synchronized 는 누띜된닀.

 

2. 하나의 프로섞슀 안에서만 볎장된닀.

한 대의 서버 낎부에서는 synchornized 킀워드가 의믞가 있닀.

귞러나, 서버가 두대 읎상읞 겜우에는 프로섞슀 낎부에서의 접귌만을 막는 synchronized 가 의믞가 없얎지게 된닀.

따띌서 읎는 싀묎에서 죌로 사용되지 않는닀.

💥 MySQL 의 Pessimistic Lock (비ꎀ적 띜)

비ꎀ적 띜도 띜읎닀옹

✔ Pessimistic Lock 읎란?

읎는 데읎터에 띜을 걞얎서 정합성을 맞추는 방법읎닀.

읎는 튞랜잭션 시작 시에 띜을 걞고 시작한닀.

닀륞 튞랜잭션에서는 띜읎 핎제되Ʞ 읎전에 데읎터륌 가젞갈 수 없닀.

읎와 같읎, 튞랜잭션 1에서 데읎터베읎슀로 접귌하게 되멎 닀륞 튞랜잭션은 대Ʞ핎알 한닀.

읎렇게 되멎 위에서 섀명한 Race Condition 을 핎결할 수 있닀. 띜에 의핎 동시에 데읎터가 공유되지 못하Ʞ 때묞읎닀.

✔ 사용 예시

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithPessimisticLock(Long id);

✔ 죌의할 점

데드띜을 죌의핎알 한닀.

💥 MySQL 의 Optimistic Lock (낙ꎀ적 띜)

✔ Optimistic Lock 읎란?

싀제로 띜을 거는 것읎 아니띌, 버저닝을 읎용핎 핎결한닀.

튞랜잭션 1읎 컀밋할 때의 쿌늬는 닀음곌 같을 것읎닀.

update set version = version+1, quantity = quantity-10
from stock
where version = 1 and id = 1

튞랜잭션 2가 컀밋할 때의 쿌늬는 닀음곌 같을 것읎닀.

update set version = version+1, quantity = quantity-5
from stock
where version = 1 and id = 1

귞러나, 앞선 튞랜잭션 1의 컀밋윌로 읞핎 version 은 1에서 2로 슝가했닀.

따띌서 튞랜잭션 2가 업데읎튞하고자 하는 버전읎 1읞 동시에 pk 가 1읞 데읎터는 더 읎상 졎재하지 않는닀.

읎 겜우에 조회륌 닀시 하고, ê·ž 읎후에 업데읎튞륌 하게 된닀.

✔ 사용 예시

@Entity
public class Stock {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Version
    private Long version;
    
    // 쀑략 ..
}
@Lock(LockModeType.OPTIMISTIC)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithOptimisticLock(Long id);
@Transactional
public void decrease(Long id, Long quantity) {
    Stock stock = stockRepository.findByIdWithPessimisticLock(id);
    stock.decrease(quantity);
    stockRepository.save(stock);

}
@Component
public class OptimisticLockStockFacade {

    public void decrease(Long id, Long quantity) throws InterruptedException {
        // 업데읎튞 싀팚 시의 재시도
        while (true) {
            try {
                optimisticLockStockService.decrease(id, quantity);
                break;
            } catch (Exception e) {
                Thread.sleep(50);
            }
        }

    }
}

 

낙ꎀ적 띜을 사용하는 겜우에는 튞랜잭션 2의 겜우처럌, 조회했던 당시의 version 곌 업데읎튞하고자 할 때의 version 읎 음치하지 않윌멎 닀시 조회가 읎뀄지는 로직읎 필요하닀.

따띌서 OptimisticLockStockFacade 에서 감소 로직을 시도하고, 읎가 성공하Ʞ까지 반복적윌로 시도한닀.

✔ 장점

1. 튞랜잭션읎 컀밋되Ʞ 전까지는 띜을 걞지 않는닀. 

컀밋되는 시점 (업데읎튞 되는 시점)에만 띜읎 걞늬고 읎전까지는 아묎런 조치륌 췚하지 않는 것곌 같닀.

 

2. 비ꎀ적 띜에 비핎 성능상의 읎점을 지닌닀.

비ꎀ적 띜은 닚순히 조회하고자 하는 튞랜잭션의 접귌마저 막지만, 낙ꎀ적 띜은 컀밋 읎전까지는 띜을 걞지 않Ʞ에 닚순 조회하고자 하는 튞랜잭션의 접귌을 허용한닀.

✔ 닚점

1. 싀팚 시 재시도에 소요되는 시간읎 졎재한닀.

2. 싀팚 시 재시도 로직을 직접 작성핎쀘알 한닀. (OptimisticLockStockFacade의 decrease)

💥 MySQL 의 Named Lock

✔ Named Lock 읎란?

읎늄을 가진 띜읎닀.

예륌 듀얎, 특정 데읎터의 pk 륌 띜의 읎늄윌로 지정한닀멎 핎당 pk 륌 가진 데읎터에 접귌하고자 하는 튞랜잭션은 핎당 pk 륌 읎늄윌로 하는 띜읎 걞렀있는지, 걞렀있지 않은지 여부륌 확읞하고 접귌할 수 있는 것읎닀.

여Ʞ서 Lock 은 entity 가 아닌 별도의 공간에 저장된닀. 핎당 엔티티가 특정 작업을 수행하는 동안에만 사용 가능한 띜을 별도의 읎늄읎나 식별자로 섀정한닀.

✔ 사용 예시

@Component
public class NamedLockStockFacade {

   @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void decrease(Long id, Long quantity) {
        Stock stock = stockRepository.findById(id).orElseThrow();

        stock.decrease(quantity);

        stockRepository.saveAndFlush(stock);
    }

}
public interface LockRepository extends JpaRepository<Stock, Long> {

    @Query(value = "select get_lock(:key, 30000)", nativeQuery = true)
    void getLock(String key);

    @Query(value = "select release_lock(:key, 30000)", nativeQuery = true)
    void releaseLock(String key);

}

Named Lock 은 튞랜잭션 종료 시에 자동윌로 띜읎 핎제되지 않Ʞ 때묞에 별도의 명령얎로 직접 핎제핎쀘알 한닀.

💡@Transactional(propagation = Propagation.REQUIRES_NEW)

위의 윔드륌 삎펎볎멎, @Transactional(propagation = Propagation.REQUIRES_NEW) 가 있는 것읎 볎읞닀.

읎는 항상 decrease 메서드륌 싀행 할 때는 새로욎 튞랜잭션읎 싀행되도록 하는 것읎닀.

읎는 상위의 (읎전의) 튞랜잭션곌 핎당 메서드륌 싀행할 때의 튞랜잭션을 분늬하는 것읎닀. 슉, 하나의 튞랜잭션읎었던 것읎 서로 독늜적윌로 싀행되게 되는 것읎닀.

왜 닀륞 띜을 구현할 때는 필요하지 않았던 띜읎 Named Lock 을 구현할 때만 필요할까?

  • 비ꎀ적 띜 : 비ꎀ적 띜은 데읎터륌 읜Ʞ 시작할 때부터 묎조걎 띜을 거는 방식읎Ʞ 때묞에 튞랜잭션을 분늬하지 않아도 된닀.
    • 슉, 동음한 데읎터에 동시에 접귌하더띌도 읎믞 띜읎 걞렀 있Ʞ 때묞에 튞랜잭션을 분늬할 필요가 없는 것읎닀.
  • 낙ꎀ적 띜 : 낙ꎀ적 띜은 여러 튞랜잭션읎 동음한 데읎터륌 읜고 업데읎튞륌 하더띌도, 충돌은 데읎터 변겜 시점에만 발생한닀.
    • 낙ꎀ적 띜은 동시에 여러 튞랜잭션읎 같은 데읎터륌 읜는 것을 막지 않는닀.
    • 충돌은 데읎터 변겜 시점에만 발생하게 되므로 굳읎 튞랜잭션을 분늬하지 않아도 된닀.
  • Named Lock : Named Lock 은 전역적윌로 띜을 ꎀ늬한닀. 따띌서 여러 튞랜잭션읎 하나의 띜을 읜고자 하멎 묞제가 발생할 수 있Ʞ에 튞랜잭션은 분늬되얎알 한닀.

✔ 장점

- 타임아웃을 쉜게 구현할 수 있닀.

-- 'my_named_lock'읎띌는 읎늄의 띜을 얻Ʞ 위핎 최대 10쎈까지 대Ʞ
SELECT GET_LOCK('my_named_lock', 10);

위와 같읎 10쎈륌 섀정핎두멎, 띜을 10쎈간 얻지 못하멎 타임아웃읎 되는 Ʞ능읎 MySQL 에는 낎장되얎 있닀.

✔ 닚점

- 튞랜잭션 종료 시에 띜읎 핎제되지 않Ʞ 때묞에 별도의 명령얎로 핎제핎쀘알 한닀.

- 싀제로 구현할 시에는 데읎터 소슀륌 분늬하는 것읎 좋닀.

💥 Redis 의 Lettuce

✔ Redis 의 Lettuce 방식읎란?

출처 :&nbsp;https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard

위와 같읎, 쓰레드 1읎 key 가 1읞 데읎터에 대핮 lock 을 얻고자 한닀멎 현재 걞렀 있는 lock 읎 없Ʞ에 읎륌 획득하게 되고 성공읎 반환된닀. 쓰레드 2 역시도 key 가 1읞 데읎터에 대핮 lock 을 얻고자 한닀멎 읎믞 걞렀 있는 lock 윌로 읞핎 싀팚가 반환된닀.

읎때, 띜 획득에 싀팚한 쓰레드의 겜우에는 spin lock 방식윌로 대Ʞ한닀. 읎는 계속 돌고 돌며 띜을 대Ʞ하닀가 획득하는 방식읎닀.

 

💡setnx

redis 의 명령얎 쀑 하나로, "SET if Not eXists" 의 앜자읎닀.

읎는 죌얎진 킀에 대한 값을 섀정하는데, 만앜 핎당 킀가 읎믞 졎재한닀멎 아묎 작업도 수행하지 않지만 킀가 졎재하지 않는닀멎 지정된 값윌로 킀와 값을 섀정한닀.

 

redis의 cli 륌 읎용핎 확읞핎볎자!

❯ docker exec -it 043d5ca6952f redis-cli
127.0.0.1:6379> setnx 1 value
(integer) 1 
127.0.0.1:6379> setnx 1 value
(integer) 0
127.0.0.1:6379> del 1
(integer) 1
127.0.0.1:6379> setnx 1 value
(integer) 1
127.0.0.1:6379>

- setnx 1 value 륌 하니, Ʞ졎에 1읎 없Ʞ에 성공 (1 반환)

- 읎후에 setnx 1 value 륌 하니, Ʞ졎에 1읎 있윌므로 싀팚 (0 반환)

- 읎후에 del 1 (key 1을 삭제) ⇒ 성공 (1 반환)

- 닀시 setnx 1 value 륌 하니, 1 읎 삭제된 읎후에 시도한 것읎므로 성공 (1 반환)

 

✔ 사용 예시

implementation 'org.springframework.boot:spring-boot-starter-data-redis'
@Component
public class RedisLockRepository {

    public Boolean lock(Long key) {
        return redisTemplate
                .opsForValue()
                // key 는 stock 의 id
                // value 는 "lock" 읎띌는 묞자엎
                .setIfAbsent(generateKey(key), "lock", Duration.ofMillis(3_000));
    }

    public Boolean unlock(Long key) {
        return redisTemplate
                .delete(generateKey(key));
    }

    public String generateKey(Long key) {
        return key.toString();
    }
}
@Component
public class LettuceLockStockFacade {

    public void decrease(Long id, Long quantity) throws InterruptedException {

        // 띜읎 획득할 때까지 계속핎서 시도륌 하고, 싀팚하멎 쓰레드륌 100 밀늬섞컚동안 잠재욎닀.
        while (!repository.lock(id)) {
            Thread.sleep(100);
        }

        try {
            stockService.decrease(id, quantity);
        } finally {
            repository.unlock(id);
        }
    }
}

앞서 섀명했던 것곌 같읎, Lettuce 방식에서는 spin lock 방식윌로 띜을 얻Ʞ륌 대Ʞ하Ʞ 때묞에 읎에 대한 로직을 개발자가 직접 작성핎쀘알 한닀.

while 묞에서 볌 수 있듯, Lock 을 획득하멎 StockService의 decrease 메서드륌 싀행하고, 핎당 lock 을 핎제한닀.

귞러나 Lock 을 획득하지 못하는 겜우에는 쓰레드륌 100 ms 정도 쀑지했닀가, 계속핎서 시도륌 한닀.

✔ 장점

- 구현읎 간닚하닀.

- named lock 곌 거의 유사하지만, 섞션 ꎀ늬륌 신겜쓰지 않아도 된닀.

- Ʞ볞 띌읎람러늬읎Ʞ 때묞에 별도의 띌읎람러늬가 필요하지 않닀.

✔ 닚점

- spin lock 방식읎Ʞ에 쓰레드가 여러개 대Ʞ 쀑읎띌멎 레디슀에 부하가 갈 수도 있닀.

💥 Redis 의 Redisson

✔ Redis 의 Redisson 방식읎란?

출처 :&nbsp;https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard

Redisson 방식은 pub-sub 구조읎닀. 따띌서 대Ʞ 쀑읞 쓰레드듀읎 계속핎서 띜 획득을 시도하지 않아도 된닀.

왜냐하멎, 획득을 시도했던 띜읎 핎제되었을 때 띜 획득을 시도할 수 있도록 메시지륌 전달하Ʞ 때묞읎닀.

 

redis-cli 로 삎펎볎자.

127.0.0.1:6379> subscribe ch1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ch1"
3) (integer) 1
1) "message"
2) "ch1"
3) "hello"
127.0.0.1:6379> publish ch1 hello
(integer) 1

위와 같읎, ch1 륌 subscribe 핎두고 닀륞 쪜에서 ch1 에 publish 륌 하멎 subscribe 륌 핮둔 쪜에 메시지륌 전달한닀.

✔ 사용 예시

implementation group: 'org.redisson', name: 'redisson-spring-boot-starter', version: '3.25.2'

 

@Component
public class RedissonLockStockFacade {

    public void decrease(Long id, Long quantity) {
        RLock lock = redissonClient.getLock(id.toString());

        try {
            boolean available = lock.tryLock(10, 1, TimeUnit.SECONDS);

            if (!available) {
                System.out.println("lock 획득 싀팚");
                return;
            }

            stockService.decrease(id, quantity);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

}

 

Redisson 방식에서는 띜 핎제와 획득을 위한 윔드륌 따로 구현하지 않고, 띌읎람러늬에 낎장된 메서드륌 읎용하멎 된닀.

반복적윌로 띜 획득을 요청하Ʞ 위한 윔드도 필요 없닀.

✔ 장점

- 띜 획득 / 재시도륌 Ʞ볞윌로 제공한닀.

- pub / sub 방식읎Ʞ 때묞에 부하가 덜 ê°„ë‹€.

 

✔ 닚점

- 별도의 띌읎람러늬륌 추가핎알 한닀.

- 띜을 띌읎람러늬 찚원에서 ꎀ늬하Ʞ 때묞에 읎에 대한 러닝 컀람가 졎재한닀.


재시도가 필요하지 않윌멎 lettuce, 필요하지 않윌멎 redisson 을 죌로 읎용한닀.

 

📚 현재의 방식곌 비교핎볎자!

@Modifying
@Query("UPDATE GameTeam t SET t.cheerCount = t.cheerCount + :cheerCount WHERE t.id = :gameTeamId")
void updateCheerCount(@Param("gameTeamId") Long gameTeamId, @Param("cheerCount") int cheerCount);

현재는 위와 같읎 update 쿌늬륌 사용하고 있닀.

읎는, 업데읎튞 할 때 한번 더 응원 횟수륌 조회하고 update 륌 할 때는 Ʞ볞적윌로 핎당 row 에 띜읎 걞늬게 되Ʞ 때묞에 동시성 읎슈가 발생하지 않는닀.

닀륞 방법듀곌 비교핎볎자!

 

1. update 쿌늬 vs 비ꎀ적 띜

비ꎀ적 띜을 읎용하멎 핎당 row 전첎에 띜읎 걞늬게 된닀.

읎로 읞핎 닀륞 쪜에서 핎당 row 륌 닚순 조회 읎왞의 수정읎나, 띜을 걞고 싶은 겜우에 대Ʞ륌 핎알 한닀. 

따띌서 비교적 좁게 update 시에만 띜읎 걞늬는 현재의 쿌늬가 ë‚«ë‹€.

 

2. update 쿌늬 vs 낙ꎀ적 띜

둘 ë‹€ update 쿌늬륌 읎용하므로 사싀상 수정하는 시점에 띜읎 걞늬는 것은 같닀.

귞러나, 낙ꎀ적 띜의 겜우에는 싀팚 시에 재시도 로직곌 버전에 대한 ꎀ늬가 필요하닀.

싀팚 시에 재시도 로직읎 필요하닀는 점에서 비교적 띜읎 자죌 걞늬지 않을 것윌로 Ʞ대되는 겜우에 낙ꎀ적 띜을 사용한닀.

귞러나, 응원 횟수의 겜우에는 게임 진행 쀑에는 늘 띜읎 걞늎 것윌로 예상핎알 하Ʞ에 현재의 update 쿌늬가 ë‚«ë‹€.

 

3. update 쿌늬 vs named lock

우선, named lock을 읎용하Ʞ 위핎서는 별도의 늬소슀가 필요하닀.

또한, 읎는 띜의 읎늄을 읎용핎서 특정 조걎에 맞게 섞밀한 띜을 섀정할 수 있닀.

귞러나, 현재는 섞밀한 조정읎 필요하지도 않을 뿐더러 띜의 범위가 컀질 겜우도 없Ʞ에 named lock 을 도입하는 마땅한 읎유가 없닀.

 

4. update 쿌늬 vs 레디슀

현재 응원 횟수의 겜우에는 치명적읞 비지니슀 로직읎 아니닀.

횟수 몇 번읎 누띜된닀고 핎서 사용자의 겜험읎나, 데읎터 상의 영향을 죌지 않는닀.

닚순 엔터테읞 요소에 가깝Ʞ 때묞에 응원 횟수의 동시성 읎슈륌 위핎 새로욎 데읎터베읎슀 슀택을 도입하는 것은 묎늬띌고 판닚된닀.

 

💬 ì°žê³  링크

https://seunghyunson.tistory.com/11

 

[ACID #2] Atomicity란? 아토믹한 튞랜잭션읎란?

✔ Atomicity란? 💡 Atomicity는 RDBMS륌 정의하는 ACID 튞랜잭션 특성 쀑 A에 핎당하는 특성입니닀. 한Ꞁ로 직역하멎 원자성읎띌는 뜻을 가지며 왜 튞랜잭션에서 Atomicity란 특성읎 쀑요한지에 대핮 알

seunghyunson.tistory.com

https://zzang9ha.tistory.com/443

 

좋아요 Ʞ능을 통핎 삎펎볎는 동시성 읎슈 (synchronized)

안녕하섞요, 읎번 포슀팅에서는 동시성(Concurrency)에 대핮 삎펎볎겠습니닀. (예제 윔드는 깃허람에서 확읞하싀 수 있습니닀.) 동시성(Concurrency) 개념 넀읎버 사전에 검색핎볞 동시성은 닀음곌 같

zzang9ha.tistory.com

https://sabarada.tistory.com/175

 

[database] 낙ꎀ적 띜(Optimistic Lock)곌 비ꎀ적 띜(Pessimistic Lock)

안녕하섞요. 였늘은 낙ꎀ적 띜곌 비ꎀ적 띜의 개념에 대핎서 알아볎는 시간을 가젞볎도록 하겠습니닀. DB 충돌 상황을 개선할 수 있는 방법 database에 접귌핎서 데읎터륌 수정할 때 동시에 수정읎

sabarada.tistory.com