๐ญ ๋ค์ด๊ฐ๋ฉฐ

์งํํ๋ ํ๋ก์ ํธ์์ ์์ ๊ฐ์ด ์์ ํ์๋ฅผ ์ฌ๋ฌ๋ฒ ํด๋ฆญํ๋ฉด ์ด์ ๋ง๊ฒ ํ์๊ฐ ์ฆ๊ฐ๋๋ ๋ก์ง์ ๊ตฌํํด์ผ ํ๋ค.
ํ ๋ช ์ ์ฌ์ฉ์๋ ์ฌ๋ฌ๋ฒ ์์ ํ์๋ฅผ ์ฆ๊ฐ์ํฌ ์ ์๋ค.
์ด์ ๊ด๋ จํด ์ด๋ป๊ฒ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์์ง์ ๋ํ ๊ณ ๋ฏผ์ ์์ํ๋ค.
โ ๋์์ฑ ๋ฌธ์ ๋?
- ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋ ์ํฉ
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 ๋ฌธ์ ๊ฐ ์์ฐ์ค๋ ํด๊ฒฐ๋์ด ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์๋ค.
๐ ์์ฌ์ ๋ ์
์ฌ์ค ์์ ๋ฐฉ์์, ํจ๊ป ํ๋ก์ ํธ์ ์ฐธ์ฌํ๊ณ ์๋ ๋๋ฃ๋ถ๊ป ์ถ์ฒ์ ๋ฐ์ ๋ฐ์ํ๋ ๋ฐฉ๋ฒ์ด์๋ค.
๋ฐ๋ผ์ ๋์์ฑ ๋ฌธ์ ์ ๋ํ ํด๊ฒฐ๋ฐฉ์์ด ์ด๋ค ๊ฒ๋ค์ด ๋ ์์์ง์ ๋ํด ๊ณต๋ถํ๊ณ , ํ์ตํ์ฌ ์ถํ์ ์ด๋ป๊ฒ ๊ฐ์ ํด๋ณด๋ฉด ์ข์์ง์ ๋ํด ๊ณ ๋ฏผํด๋ณด๊ณ ์ถ์๋ค.
๐ ๋์์ฑ ๋ฌธ์ ํด๊ฒฐ ๋ฐฉ์
๋ค์ ๊ฐ์๋ฅผ ์ฐธ๊ณ ํด์ ์์ฑํ์ต๋๋ค :)
์ฌ๊ณ ์์คํ ์ผ๋ก ์์๋ณด๋ ๋์์ฑ์ด์ ํด๊ฒฐ๋ฐฉ๋ฒ ๊ฐ์ - ์ธํ๋ฐ
๋์์ฑ ์ด์๋ ๋ฌด์์ธ์ง ์์๋ณด๊ณ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ๋ค์ ํ์ตํฉ๋๋ค., ๋์์ฑ ์ด์ ์ฒ๋ฆฌ๋ ์์ ์๊ฒ! ๊ฐ๋จํ ์ฌ๊ณ ์์คํ ์ผ๋ก ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐฐ์๋ณด์ธ์. ๋ฐฑ์๋ ๊ฐ๋ฐ์๋ผ๋ฉด ๊ผญ ์์์ผ ํ ๋์์ฑ ์ด์
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 ๋ฐฉ์์ด๋?

์์ ๊ฐ์ด, ์ฐ๋ ๋ 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 ๋ฐฉ์์ด๋?

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