Concurrency issues

Concurrency issues

์žฌ๊ณ ์‹œ์Šคํ…œ์œผ๋กœ ์•Œ์•„๋ณด๋Š” ๋™์‹œ์„ฑ์ด์Šˆ ํ•ด๊ฒฐ๋ฐฉ๋ฒ• ๊ฐ•์˜๋ฅผ ๋“ฃ๊ณ  ์š”์•ฝํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

Race Condition

Race condition

๊ฒฝ์Ÿ์ƒํƒœ๋Š” ๋‘ ๊ฐœ ์ด์ƒ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ณต์œ  ๋ฐ์ดํ„ฐ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๊ณ , ๋™์‹œ์— ๋ณ€๊ฒฝ์„ ํ•˜๋ ค๊ณ  ํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.

@Service
@RequiredArgsConstructor
public class StockService {

    private final StockRepository stockRepository;

    public synchronized void decrease(Long id, Long quantity) {
        final Stock stock = stockRepository.findById(id).orElseThrow();
        stock.decrease(quantity);

        stockRepository.saveAndFlush(stock);
    }
}

์šฐ๋ฆฌ๋Š” ์•„๋ž˜ ํ…Œ์ŠคํŠธ์—์„œ 0์˜ ๊ฒฐ๊ณผ๋ฅผ ์˜ˆ์ƒํ•˜์ง€๋งŒ, ๋™์‹œ์— ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ๋“ค์ด ๊ฐฑ์‹  ์ „ ๊ฐ’์„ ์ฝ๊ณ  ์ˆ˜์ •ํ•˜๋ฉด์„œ ์‹ค์ œ ๊ฐฑ์‹ ์ด ๋ˆ„๋ฝ๋˜๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

@Test
void decrease_inventory_concurrent_requests() throws InterruptedException {
    int threadCount = 100;
    ExecutorService executorService = Executors.newFixedThreadPool(32);
    CountDownLatch latch = new CountDownLatch(threadCount);

    for (int i = 0; i < threadCount; i++) {
        executorService.submit(() -> {
            try {
                stockService.decrease(1L, 1L);
            } finally {
                latch.countDown();
            }
        });
    }

    latch.await();

    Stock stock = stockRepository.findById(1L).orElseThrow();

    assertEquals(0, stock.getQuantity());
}

Java Synchronized

synchronized ๋ฅผ ๋ฉ”์„œ๋“œ ์„ ์–ธ๋ถ€์— ๋ถ™์—ฌ์ฃผ๋ฉด ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋Š” ํ•œ ๊ฐœ์˜ ์Šค๋ ˆ๋“œ๋งŒ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

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

    stockRepository.saveAndFlush(stock);
}

.

โš ๏ธ ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

Spring Transactional ์˜ ๋™์ž‘ ๋ฐฉ์‹์€ ์•„๋ž˜์™€ ๊ฐ™์ด ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ํด๋ž˜์Šค๋ฅผ ๋ž˜ํ•‘ํ•œ ํด๋ž˜์Šค๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์„œ ์‹คํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

public class TransactionService {
   private StockService stockService;

   ...

   public void decreases(Long id, Long quantity) {
       startTransaction();

       stockService.decreases(id, quantity);

       endTransaction();
   }

   ...
}

์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ํด๋ž˜์Šค๋ฅผ ํ•„๋“œ๋กœ ๊ฐ€์ง€๊ณ  ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์ „์— ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐฑ์‹ ๋œ ์ „ ๊ฐ’์„ ์ฝ๊ฒŒ ๋˜๋ฉด ๊ฒฐ๊ตญ ์ด์ „๊ณผ ๋™์ผํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

@Transactional ๊ณผ PROXY

์—ฌ๊ธฐ์„œ ๋‹จ์ˆœํ•˜๊ฒŒ Transactional Annotation ์„ ์ œ๊ฑฐํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜๋Š” ์žˆ์Šต๋‹ˆ๋‹ค.

.

โš ๏ธ ํ•˜์ง€๋งŒ, Java Synchronized ์˜ ๋ฌธ์ œ๋Š” ๋˜ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

Java Synchronized ๋Š” ํ•˜๋‚˜์˜ ํ”„๋กœ์„ธ์Šค ์•ˆ์—์„œ๋งŒ ๋ณด์žฅ์ด ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‹ค๋ณด๋‹ˆ ์„œ๋ฒ„๊ฐ€ 1๋Œ€์ผ ๊ฒฝ์šฐ์—๋Š” ๊ดœ์ฐฎ๊ฒ ์ง€๋งŒ, ์„œ๋ฒ„๊ฐ€ 2๋Œ€ ์ด์ƒ์ผ ๊ฒฝ์šฐ ๊ฒฐ๊ตญ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ๋™์‹œ์— ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋ฉด์„œ ๋ ˆ์ด์Šค ์ปจ๋””์…˜์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋Œ€๋ถ€๋ถ„์˜ ์šด์˜ ์„œ๋น„์Šค๋Š” 2๋Œ€ ์ด์ƒ์˜ ์„œ๋ฒ„๋กœ ์šด์˜๋˜๊ธฐ ๋•Œ๋ฌธ์— Java Synchronized ๋Š” ๊ฑฐ์˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

DataBase Lock

MySQL

Pessimistic Lock

๋น„๊ด€์  ๋ฝ.

  • ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ์— Lock์„ ๊ฑธ์–ด์„œ ์ •ํ•ฉ์„ฑ์„ ๋งž์ถ”๋Š” ๋ฐฉ๋ฒ•

  • Row or Table ๋‹จ์œ„๋กœ Locking

  • Exclusive Lock์„ ๊ฑธ๊ฒŒ ๋˜๋ฉฐ, ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ๋Š” Lock ํ•ด์ œ ์ „์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์—†์Œ

  • ๋ฐ๋“œ๋ฝ(๋‘ ๊ฐœ ์ด์ƒ์˜ ์ž‘์—…์ด ์„œ๋กœ ์ƒ๋Œ€๋ฐฉ์˜ ์ž‘์—…์ด ๋๋‚˜๊ธฐ ๋งŒ์„ ๊ธฐ๋‹ค๋Š” ์ƒํƒœ) ์ฃผ์˜ ํ•„์š”

  • ์ถฉ๋Œ์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ผ์–ด๋‚˜๊ฑฐ๋‚˜ ์˜ˆ์ƒ๋œ๋‹ค๋ฉด ์ถ”์ฒœํ•˜๋Š” ๋ฐฉ์‹

์žฅ์ .

  • ์ถฉ๋Œ์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ผ์–ด๋‚œ๋‹ค๋ฉด Optimistic Lock ๋ณด๋‹ค ์„ฑ๋Šฅ์ด ์ข‹์„ ์ˆ˜ ์žˆ๋‹ค.

  • ๋ฝ์„ ํ†ตํ•ด ์—…๋ฐ์ดํ„ฐ๋ฅผ ์ œ์–ดํ•˜๋ฏ€๋กœ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์ด ๋ณด์žฅ๋œ๋‹ค.

๋‹จ์ .

  • ๋ณ„๋„์˜ ๋ฝ์„ ์žก๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ ๊ฐ์†Œ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.

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

...

/** Service **/
@Transactional
public void decrease(Long id, Long quantity) {
    final Stock stock = stockRepository.findByIdWithPessimisticLock(id);
    stock.decrease(quantity);
    stockRepository.save(stock);
}

commit

Optimistic Lock

๋‚™๊ด€์  ๋ฝ.

  • ์‹ค์ œ๋กœ Lock ์„ ์ด์šฉํ•˜์ง€ ์•Š๊ณ  ๋ฒ„์ „์„ ์ด์šฉํ•จ์œผ๋กœ์จ ์ •ํ•ฉ์„ฑ์„ ๋งž์ถ”๋Š” ๋ฐฉ๋ฒ•

  • ๋จผ์ € ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์€ ํ›„ update ์ˆ˜ํ–‰ ์‹œ, ํ˜„์žฌ ๋‚ด๊ฐ€ ์ฝ์€ ๋ฒ„์ „์ด ๋งž๋Š”์ง€ ํ™•์ธํ•˜๋ฉฐ ์—…๋ฐ์ดํŠธ ์ˆ˜ํ–‰

  • ๋‚ด๊ฐ€ ์ฝ์€ ๋ฒ„์ „์—์„œ ์ˆ˜์ •์‚ฌํ•ญ์ด ์ƒ๊ฒผ์„ ๊ฒฝ์šฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋‹ค์‹œ ์ฝ์€ ํ›„์— ์ž‘์—…์„ ์ˆ˜ํ–‰

  • ์ถฉ๋Œ์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ผ์–ด๋‚˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์ถ”์ฒœํ•˜๋Š” ๋ฐฉ์‹

์žฅ์ .

  • ๋ณ„๋„์˜ ๋ฝ์„ ์žก์ง€ ์•Š์œผ๋ฏ€๋กœ Pessimistic Lock ๋ณด๋‹ค ์„ฑ๋Šฅ์ƒ ์ด์ ์ด ์žˆ๋‹ค.

๋‹จ์ .

  • ์—…๋ฐ์ดํŠธ ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„ ๋กœ์ง์„ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

/** Entity **/
@Version
private Long version;

...

/** Repository **/
@Lock(value = LockModeType.OPTIMISTIC)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithOptimisticLock(@Param("id") Long id);

...

/** Service **/
@Transactional
public void decrease(Long id, Long quantity) {
    final Stock stock = stockRepository.findByIdWithOptimisticLock(id);
    stock.decrease(quantity);
    stockRepository.save(stock);
}

...

/** Facade **/
@Component
@RequiredArgsConstructor
public class OptimisticLockStockFacade {

    private final OptimisticLockStockService optimisticLockStockService;

    public void decrease(Long id, Long quantity) throws InterruptedException {
        while (true) { // ์—…๋ฐ์ดํŠธ ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„
            try {
                optimisticLockStockService.decrease(id, quantity);
                break;
            } catch (Exception e) {
                Thread.sleep(50);
            }
        }
    }
}

commit

Named Lock

  • ์ด๋ฆ„์„ ๊ฐ€์ง„ Metadata Locking

  • ์ด๋ฆ„์„ ๊ฐ€์ง„ Lock ํš๋“ ํ›„, ํ•ด์ œํ•  ๋•Œ๊นŒ์ง€ ๋‹ค๋ฅธ ์„ธ์…˜์€ ์ด Lock ์„ ํš๋“ํ•  ์ˆ˜ ์—†๋„๋ก ํ•จ

  • Transaction ์ข…๋ฃŒ ์‹œ Lock ์ด ์ž๋™์œผ๋กœ ํ•ด์ œ๋˜์ง€ ์•Š๋Š” ์  ์ฃผ์˜

    • ๋ณ„๋„์˜ ๋ช…๋ น์–ด๋กœ ํ•ด์ œ๋ฅผ ์ˆ˜ํ–‰ํ•ด ์ฃผ๊ฑฐ๋‚˜ ์„ ์  ์‹œ๊ฐ„์ด ๋๋‚˜์•ผ ํ•ด์ œ

  • ๋น„๊ด€์  ๋ฝ๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ ๋กœ์šฐ๋‚˜ ํ…Œ์ด๋ธ”์ด ์•„๋‹Œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์˜ ๋ฝํ‚น์„ ํ•˜๋Š” ๋ฐฉ์‹

  • ์ปค๋„ฅ์…˜ ํ’€ ๋ถ€์กฑ ํ˜„์ƒ์„ ๋ง‰๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ์‚ฌ์šฉํ•  ๊ฒƒ์„ ๊ถŒ์žฅ

  • ์ฃผ๋กœ ๋ถ„์‚ฐ๋ฝ ๊ตฌํ˜„ ์‹œ ์‚ฌ์šฉ

์žฅ์ .

  • Timeout์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ ๊ฐ€๋Šฅ

๋‹จ์ .

  • ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์‹œ ๋ฝ ํ•ด์ œ, ์„ธ์…˜ ๊ด€๋ฆฌ ํ•„์š”

  • ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์ด ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์Œ

/** Repository **/
@Query(value = "select get_lock(:key, 3000)", nativeQuery = true)
void getLock(@Param("key") String key);

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

...

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

    stockRepository.saveAndFlush(stock);
}

...

/** Facade **/
@Component
@RequiredArgsConstructor
public class NamedLockStockFacade {

    private final LockRepository lockRepository;

    private final StockService stockService;

    @Transactional
    public void decrease(Long id, Long quantity) {
        try {
            lockRepository.getLock(id.toString());
            stockService.decrease(id, quantity);
        } finally {
            lockRepository.releaseLock(id.toString());
        }
    }
}

commit

Reference.

Redis

MySQL์˜ Named Lock๊ณผ ์œ ์‚ฌํ•œ ๋ฐฉ์‹

Lettuce

  • setnx(set if not exist) ๋ช…๋ น์–ด๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ถ„์‚ฐ๋ฝ ๊ตฌํ˜„

  • Spin Lock ๋ฐฉ์‹

    • ๋ฝ์„ ํš๋“ํ•˜๋ ค๋Š” ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฝ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋ฐ˜๋ณต์ ์œผ๋กœ ํ™•์ธํ•˜๋ฉด์„œ ๋ฝ ํš๋“์„ ์‹œ๋„ํ•˜๋Š” ๋ฐฉ์‹

    • ์žฌ์‹œ๋„ ๋กœ์ง ๊ฐœ๋ฐœ ํ•„์š”

์žฅ์ .

  • ๊ตฌํ˜„์ด ๋‹จ์ˆœ

  • ๋ณ„๋„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋ถˆํ•„์š”(spring data redis ์‚ฌ์šฉ ์‹œ ๊ธฐ๋ณธ์ ์œผ๋กœ Lettuce ์ ์šฉ)

๋‹จ์ .

  • Spin Lock ๋ฐฉ์‹์ด๋ฏ€๋กœ ๋™์‹œ์— ๋งŽ์€ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฝ ํš๋“ ๋Œ€๊ธฐ ์ƒํƒœ๋ผ๋ฉด ๋ ˆ๋””์Šค์— ๋ถ€ํ•˜๊ฐ€ ๊ฐˆ ์ˆ˜ ์žˆ์Œ

    • ๋ฝ ํš๋“ ์žฌ์‹œ๋„ ๊ฐ„ ๋Œ€๊ธฐ ์‹œ๊ฐ„๋„ ํ•„์š”

# key ๋Š” 1 ์ด๊ณ , value ๋Š” lock ์ธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
> setnx 1 lock
(integer) 1

# ์ด๋ฏธ 1 ์ด๋ผ๋Š” ํ‚ค๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ์‹คํŒจ
> setnx 1 lock
(integer) 0

# ํ‚ค ์‚ญ์ œ
> del 1
(integer) 1
/* Repository */
public Boolean lock(Long key) {
    return redisTemplate
            .opsForValue()
            .setIfAbsent(generateKey(key), "lock", Duration.ofMillis(3_000));
}

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

...

/* Facade */
public void decrease(Long key, Long quantity) throws InterruptedException {
    while (!redisLockRepository.lock(key)) {
        Thread.sleep(100);
    }

    try {
        stockService.decrease(key, quantity);
    } finally {
        redisLockRepository.unlock(key);
    }
}

commit

Spin Lock

Redisson

Redisson/Spring Boot Starter

  • pub-sub ๊ธฐ๋ฐ˜์œผ๋กœ Lock ๊ตฌํ˜„ ์ œ๊ณต

    • ์ฑ„๋„์„ ๋งŒ๋“ค๊ณ  ๋ฝ์„ ์ ์œ ์ค‘์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฝ ํš๋“์„ ๋Œ€๊ธฐ์ค‘์ธ ์Šค๋ ˆ๋“œ์—๊ฒŒ ๋ฝ ํ•ด์ œ๋ผ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•˜๋ฉด, ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌ๋ฐ›์€ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฝ ํš๋“์„ ์‹œ๋„ํ•˜๋Š” ๋ฐฉ์‹

  • ๋ฝ ํš๋“์„ ์œ„ํ•ด ๋ฐ˜๋ณต์ ์œผ๋กœ ๋ฝ ํš๋“์„ ์‹œ๋„ํ•˜๋Š” Lettuce ๋Œ€๋น„ ๋ถ€ํ•˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Œ

Thread-1 ----------> Channel -------------------> Thread-2
          I'm done            Try to get a Lock
# ch1 ์ฑ„๋„ ๊ตฌ๋…(thread-2)
> subscribe ch1
1) "subscribe"
2) "ch1"
3) (integer) 1

# ๋ฉ”์‹œ์ง€ ์ „์†ก(thread-1)
> publish ch1 hello
(integer) 1

# thread-2 ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ 
...
1) "message"
2) "ch1"
3) "hello"

์žฅ์ .

  • pub-sub ๊ธฐ๋ฐ˜ ๊ตฌํ˜„์œผ๋กœ lettuce ๋Œ€๋น„ ๋ ˆ๋””์Šค์— ๋ถ€ํ•˜๊ฐ€ ๋œ ๊ฐ

  • ๋ฝ ํš๋“ ์žฌ์‹œ๋„๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณต

๋‹จ์ .

  • ๋ณต์žกํ•œ ๊ตฌํ˜„

  • ๋ณ„๋„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•„์š”

/* Facade */
public void decrease(Long key, Long quantity) {
    RLock lock = redissonClient.getLock(key.toString());

    try {
        boolean available = lock.tryLock(10, 1, TimeUnit.SECONDS); // (๋ฝ ํš๋“ ์‹œ๋„ ์‹œ๊ฐ„, ์ ์œ  ์‹œ๊ฐ„)

        if (!available) {
            System.out.println("lock ํš๋“ ์‹คํŒจ");
            return;
        }

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

์‹ค๋ฌด์—์„œ๋Š” ๋ณดํ†ต

์žฌ์‹œ๋„๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๋ฝ์€ Lettuce๋ฅผ ํ™œ์šฉํ•˜๊ณ ,

์žฌ์‹œ๋„๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ Redisson์„ ํ™œ์šฉ

commit

Finish

Java Synchronized

  • ํ•œ ๊ฐœ์˜ ์Šค๋ ˆ๋“œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ œํ•œ

  • @Transactional ์ ์šฉ ๋ถˆ๊ฐ€

  • 2๋Œ€ ์ด์ƒ์˜ ์„œ๋ฒ„๋กœ ์šด์˜๋  ๊ฒฝ์šฐ ๋ ˆ์ด์Šค ์ปจ๋””์…˜์€ ๋˜ ๋‹ค์‹œ ๋ฐœ์ƒ

DataBase Lock

  • Pessimistic Lock

    • ๋กœ์šฐ, ํ…Œ์ด๋ธ” ๋‹จ์œ„๋กœ ๋ฝํ‚น

    • ๋ฝ ํ•ด์ œ ์ „๊นŒ์ง€ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์—†์Œ(๋ฐ๋“œ๋ฝ ์ฃผ์˜)

    • ์ถฉ๋Œ์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ผ์–ด๋‚  ๊ฒฝ์šฐ ์ถ”์ฒœ

  • Optimistic Lock

    • ๋ฒ„์ „์„ ์ด์šฉ

    • ์—…๋ฐ์ดํŠธ ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„

    • ์ถฉ๋Œ์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ผ์–ด๋‚˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์ถ”์ฒœ

    • ์—…๋ฐ์ดํ„ฐ ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„ ๋กœ์ง ๊ฐœ๋ฐœ ํ•„์š”

  • Named Lock

    • Pessimistic Lock ๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ ์ด๋ฆ„๋“ค ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ์— ๋ฝํ‚น

    • ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์‹œ ๋ฝ ํ•ด์ œ, ์„ธ์…˜์€ ์ง์ ‘ ๊ด€๋ฆฌ ํ•„์š”

    • ์ฃผ๋กœ ๋ถ„์‚ฐ๋ฝ ๊ตฌํ˜„ ์‹œ ์‚ฌ์šฉ

Redis

  • Lettuce

    • Spin Lock ๋ฐฉ์‹(๋ ˆ๋””์Šค์— ๋ถ€ํ•˜ ๊ฐ€๋Šฅ์„ฑ ์กด์žฌ)

    • ์žฌ์‹œ๋„ ๋กœ์ง ๊ฐœ๋ฐœ์ด ํ•„์š”

    • ์žฌ์‹œ๋„๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ถŒ์žฅ

  • Redisson

    • pub-sub ๊ธฐ๋ฐ˜

    • ๋ณ„๋„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๊ณ  ๊ตฌํ˜„์ด ๋ณต์žก

    • ์žฌ์‹œ๋„๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๊ถŒ์žฅ

MySql vs. Redis

Reference

Repository

Last updated