[์นํ์] 11/13 ๊ฐ๋ฐ์ผ์ง ์ฟ ํฐ ๋ฐ๊ธ ์ ๋์์ฑ ๋ฌธ์ ํด๊ฒฐ 1 : ๋์์ฑ ๋ฌธ์ ๋ฐ์ ๋ฐ ๋๊ด์ , ๋น๊ด์ , ๋ถ์ฐ ๋ฝ
๐ก ๋ชฉํ
์ฟ ํฐ 300 ๊ฐ๋ฅผ ์ ์ 1000 ๋ช ์ด 5์ด๋์ ๋ฐ๊ธ์ ๋ฐ๊ธฐ ์ํด ํ ์คํธ๋ฅผ ์งํํ๋๋ฐ, ์ค๋ณต๋ ์ฟ ํฐ์ด ๋ฐ๊ธ๋๋ค๋ ์ง, ์๋ 300 ๊ฐ๋ฅผ ๋์ด์์ ๋ฐ๊ธ์ด ๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ ๋ฝ์ ๊ฑธ์ด ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ํ์๊ฐ ์์๋ค.
๐ Test : ๋ฝ์ ์ ์ฉํ์ง ์์ ์ํ์ ๋์์ฑ ๋ฌธ์ ํ์ธ
- Number of Threads (users) : 1000
- Ramp-up period (seconds) : 5
- Loop Count : 1
- ๋ฐ๊ธํ ์ ํจ ์ฟ ํฐ ์ : 300
- ์ค์ ์ฟ ํฐ์ ๋ฐ๊ธ๋ฐ์ ์ ์ ์ : 595
์ ์ด๋ฏธ์ง์ ๊ฐ์ด ๋จ์ ์ฟ ํฐ ์๋ ์ ์ค์ง ์๊ณ ์ค๋ณต์ผ๋ก ๋ฐ๊ธ๋๋ ์ฟ ํฐ์ด ๋ง์ด ๋ฐ์.
์ค์ ๋ก ๊ฐ์ ์ฝ๋์ ์ฟ ํฐ์ ๋ฐ๊ธ๋ฐ์ ์ ์ ๋ ๋ง๋ค.
์๋ต ์ฒ๋ฆฌ์๋๋ ํ๊ท ์ฝ 171 ms ๊ฐ ๋์๋ค.
๊ฒฐ๋ก
๋ง์ ํธ๋ํฝ์ด ๋ชฐ๋ ธ์ ๋ ์ฟ ํฐ ๋ฐ๊ธ ์์ฒญ์์ ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
์ด์ ๋ง๋ ํด๊ฒฐ์ฑ ์ ์ฐพ๊ธฐ ์ํด์ ๋๊ด์ ๋ฝ, ๋น๊ด์ ๋ฝ, ๋ถ์ฐ ๋ฝ์ ๋ํด์ ์์๋ณด์๋ค.
๐ฑ ๋๊ด์ ๋ฝ ( Optimistic Lock )
@Entity
public class Product {
@Id
private Long id;
@Version
private int version;
private String name;
private int quantity;
// getters and setters
}
๐ ์ํฉ ์์
A ์ B ๊ฐ ๋์์ ๊ฐ์ Product ๋ฅผ ์กฐํํด์ ์์ ํ๋ค๊ณ ๊ฐ์
์กฐํ ์์ ์ version ์ 1 ์ด๋ผ๊ณ ํ์ ๋,
A ๊ฐ Product ๋ฅผ ์์ ํด์ version ์ 2 ๋ก ์ฌ๋ฆฐ๋ค.
๊ทธ ๋ B ๊ฐ ์์ ์ ์์ฒญํ๋ฉด ์ฌ์ ํ B ์์ version ์ 1 ์ด๊ธฐ ๋๋ฌธ์ ์ถฉ๋์ ๊ฐ์งํ๊ณ ์์ธ๋ฅผ ๋ฐ์.
-> B ๋ ์์ ์ ๋กค๋ฐฑํ๊ณ ๋ค์ ์์ ์ ์๋ํด์ผํ๋ค.
๐ ์ฅ์
- ๋ฝ์ด ๊ฑธ๋ฆฌ์ง ์์: ๋ฐ์ดํฐ์ ๋ฝ์ ๊ฑธ์ง ์๊ณ ์์ ํ๋ฏ๋ก ๋์์ฑ ์ฑ๋ฅ์ด ์ข๋ค. ์ฆ, ๋ค๋ฅธ ํธ๋์ญ์ ์ด ๋ฝ์ ๋งํ์ง ์๊ณ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฑฐ๋ ์์ ํ ์ ์๋ค.
- ๋ฎ์ ๋ฆฌ์์ค ์ฌ์ฉ: ๋น๊ด์ ๋ฝ๊ณผ ๋ฌ๋ฆฌ, ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ธฐ ์ ๊น์ง๋ ๋ฝ์ ๊ฑธ์ง ์์ผ๋ฏ๋ก ๋ฆฌ์์ค ์ฌ์ฉ์ด ์ค์ด๋ ๋ค.
๐ ๋จ์
- ์ถฉ๋ ๋ฐ์ ์ ์ฌ์๋ ํ์: ๋ง์ฝ ์ถฉ๋์ด ๋ฐ์ํ๋ฉด ์์ธ๊ฐ ๋ฐ์ํ๊ณ ํธ๋์ญ์ ์ ๋ค์ ์๋ํด์ผ ํ๋ค. ์ถฉ๋์ด ๋น๋ฒํ๋ค๋ฉด ๋น๊ด์ ๋ฝ์ด ๋ ์ ํฉํ ์ ์๋ค.
- ์ถฉ๋ ๊ฐ์ง ๋ฐ ๊ด๋ฆฌ ๋ณต์ก์ฑ: ์ถฉ๋์ ๊ฐ์งํ๊ณ ์ฌ์๋ํ๋ ๋ก์ง์ ๊ด๋ฆฌํด์ผ ํ๋ ๋ณต์ก์ฑ์ด ์๊ธด๋ค.
๐ ์์ฝ
๋๊ด์ ๋ฝ์ ์ถฉ๋ ๊ฐ๋ฅ์ฑ์ด ๋ฎ์ ๊ฒฝ์ฐ์ ์ ์ฉํ๋ฉฐ, ์ถฉ๋ ์์๋ ์์ธ๋ฅผ ๋ฐ์์์ผ ์ฌ์๋๊ฐ ํ์ํ๋ค. ๋ฐ๋ผ์ ๋ฐ์ดํฐ ์ถฉ๋์ด ์์ฃผ ์ผ์ด๋์ง ์๋ ํ๊ฒฝ์์ ์ฑ๋ฅ ์ต์ ํ์ ์ข๋ค.
๐ฑ ๋น๊ด์ ๋ฝ ( Pessimistic Lock )
import javax.persistence.LockModeType;
@Transactional
public Product updateProductQuantity(Long productId, int newQuantity) {
Product product = entityManager.find(Product.class, productId, LockModeType.PESSIMISTIC_WRITE);
product.setQuantity(newQuantity);
return product;
}
์ ์ฝ๋์์ LockModeType.PESSIMISTIC_WRITE ์ต์ ์ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋น๊ด์ ๋ฝ์ ์ค์ ํ๊ณ , ํด๋น ๋ฐ์ดํฐ์ ๋ค๋ฅธ ํธ๋์ญ์ ์ด ์ ๊ทผํ์ง ๋ชปํ๋๋ก ํ๋ค.
๐ ์ํฉ ์์
A ๊ฐ Product ์ quantity ๋ฅผ ์์ ํ๋ค๊ณ ๊ฐ์ .
A ๋ ๋จผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ SELECT ... FOR UPDATE ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ฌ ๋น๊ด์ ๋ฝ์ ์ค์
A ์ ํธ๋์ญ์ ์ด ๋๋๊ธฐ ์ ๊น์ง ๋ค๋ฅธ ์ฌ์ฉ์๋ ํด๋น Product ์ฟผ๋ฆฌ์ ์ ๊ทผํ ์ ์๋ค.
์ดํ A ๊ฐ quantity ๋ฅผ ์์ ํ๊ณ ์ปค๋ฐํ๋ฉด ๋ฝ์ด ํด์ ๋๋ค.
๐ ์ฅ์
- ๊ฐ๋ ฅํ ์ถฉ๋ ๋ฐฉ์ง: ๋ค๋ฅธ ํธ๋์ญ์ ์ด ๋ฐ์ดํฐ๋ฅผ ์์ ํ์ง ๋ชปํ๋๋ก ๊ฐ๋ ฅํ ์ ์ดํ ์ ์์ด ๋ฐ์ดํฐ ์ ํฉ์ฑ์ด ๋ณด์ฅ๋๋ค.
- ์ฌ์๋๊ฐ ํ์ ์์: ๋ฐ์ดํฐ ์์ ์ ๋ถํฐ ๋ฝ์ด ๊ฑธ๋ ค ์์ด ์ถฉ๋์ด ๋ฐ์ํ์ง ์์ผ๋ฏ๋ก, ์ถฉ๋๋ก ์ธํ ์์ธ ์ฒ๋ฆฌ๋ ์ฌ์๋ ๋ก์ง์ด ํ์ ์๋ค.
๐ ๋จ์
- ์ฑ๋ฅ ์ ํ: ๋น๊ด์ ๋ฝ์ ํธ๋์ญ์ ์ด ์ข ๋ฃ๋ ๋๊น์ง ๋ฝ์ ์ ์งํ๋ฏ๋ก, ๋๊ธฐ ์๊ฐ์ด ๋ฐ์ํด ์ฑ๋ฅ ์ ํ๋ฅผ ์ผ์ผํฌ ์ ์๋ค.
- ๋ฐ๋๋ฝ ์ํ: ์ฌ๋ฌ ํธ๋์ญ์ ์ด ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ๋ฅผ ๋์์ ๋ฝ์ ๊ฑธ๋ ค๊ณ ํ ๋, ๋ฐ๋๋ฝ์ด ๋ฐ์ํ ์ ์๋ค.
๐ ์์ฝ
๋น๊ด์ ๋ฝ์ ๋ฐ์ดํฐ ์ถฉ๋ ๊ฐ๋ฅ์ฑ์ด ๋๊ฑฐ๋, ๊ฐ๋ ฅํ ๋ฐ์ดํฐ ์ ํฉ์ฑ์ด ํ์ํ ๊ฒฝ์ฐ์ ์ ํฉํ๋ค.
ํ์ง๋ง ์ฑ๋ฅ ์ ํ์ ๋ฐ๋๋ฝ ์ํ์ด ์์ด, ์ถฉ๋ ๊ฐ๋ฅ์ฑ์ด ์ ์ ๊ฒฝ์ฐ๋ผ๋ฉด ๋๊ด์ ๋ฝ์ด ๋ ๋์ ์ ํ์ผ ์ ์๋ค.
๐ฑ ๋ถ์ฐ ๋ฝ ( Distributed Lock ) - Redisson
import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;
// ๋ฝ์ ์ค์ ํ๊ณ ์ฌ์ฉํ ํค ์ด๋ฆ ์ ์
String lockKey = "product:lock";
// ๋ฝ์ ๊ฐ์ ธ์ค๊ธฐ
RLock lock = redissonClient.getLock(lockKey);
try {
// ๋ฝ์ ํ๋ํ๋ ค๊ณ ์๋, 10์ด ๋์ ๊ธฐ๋ค๋ฆฌ๊ณ 5์ด ๋์ ๋ฝ ์ ์ง
if (lock.tryLock(10, 5, TimeUnit.SECONDS)) {
// ๋ฝ์ ํ๋ํ๋ค๋ฉด ์คํํ ์ฝ๋
System.out.println("Lock acquired, executing critical section.");
// ์ค์ํ ์์
์ํ (์: ์ฌ๊ณ ์๋ ๊ฐ์ ๋ฑ)
} else {
// ๋ฝ์ ์ป์ง ๋ชปํ๋ฉด ์คํํ ์ฝ๋
System.out.println("Could not acquire lock, try again later.");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// ๋ฝ ํด์
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("Lock released.");
}
}
๐ ์ํฉ ์์
A ๊ฐ Product ์ quantity ๋ฅผ ์์ ํ๋ค๊ณ ๊ฐ์ .
ํ ๋ฒ์ ํ๋์ ํ๋ก์ธ์ค๋ง Product ๋ฅผ ํ์ธํ๊ณ ๋ณ๊ฒฝํ ์ ์๊ธฐ ๋๋ฌธ์
๋จผ์ ์์ฒญ์ด ๋ค์ด์จ A ๊ฐ ๋ฝ์ ํ๋ํ๊ณ Product ๋ฅผ ํ์ธ, ๋ณ๊ฒฝ.
B ์์ฒญ์ด ๋ค์ด์ค๋ฉด ๊ณ ๊ฐ A ์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋ ๋ ๊น์ง ๊ธฐ๋ค๋ฆฌ๊ฑฐ๋ "์ฌ์๋" ์ ๊ฐ์ ๋ฉ์ธ์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ฒ ํจ.
๊ณ ๊ฐ A์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋๋ฉด ๋ฝ์ด ํด์ ๋๊ณ , ๊ณ ๊ฐ B๊ฐ ๋ฝ์ ํ๋ํ์ฌ ๋์ผํ ํ๋ก์ธ์ค๋ฅผ ์ํ
- lock.tryLock(10, 5, TimeUnit.SECONDS): ๋ฝ์ ํ๋ํ ๋ 10์ด ๋์ ๊ธฐ๋ค๋ฆฌ๋ฉฐ, ๋ฝ์ ํ๋ํ๋ฉด 5์ด ๋์ ์ ์ง, ์ด๋ก์จ ๋ฐ๋๋ฝ์ ๋ฐฉ์งํ ์ ์๋ค.
- isHeldByCurrentThread(): ๋ฝ์ ํ์ฌ ์ค๋ ๋๊ฐ ๋ณด์ ํ๊ณ ์๋์ง ํ์ธํ์ฌ, ํ์ฌ ์ค๋ ๋๋ง ๋ฝ์ ํด์ ํ ์ ์๊ฒ ํ๋ค.
- unlock(): ์์ ์ด ๋๋๋ฉด ๋ฝ์ ํด์ ํ์ฌ ๋ค๋ฅธ ํ๋ก์ธ์ค๊ฐ ์ ๊ทผํ ์ ์๋๋ก ํ๋ค.
๐ ์ฅ์
- ๊ฐํธํ ์ฌ์ฉ: Redisson์ ์ฌ์ฉํ๋ฉด ๋ฝ ์ค์ ๊ณผ ํด์ ๋ก์ง์ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋ค.
- TTL ๊ด๋ฆฌ ์ฉ์ด: ์๋์ผ๋ก ๋ง๋ฃ ์๊ฐ์ด ์ค์ ๋๋ฏ๋ก, ๋ฐ๋๋ฝ ๋ฐฉ์ง๊ฐ ์ฝ๋ค.
- ๋์ ์ฑ๋ฅ๊ณผ ํ์ฅ์ฑ: Redis๋ฅผ ํ์ฉํด ๋์ ์ฑ๋ฅ๊ณผ ํ์ฅ์ฑ์ ์ ๊ณตํ๋ฉฐ, ๋ค์ค ํ๋ก์ธ์ค ๊ฐ ๋ฝ์ ์ฝ๊ฒ ๊ด๋ฆฌํ ์ ์๋ค.
๐ ๋จ์
- Redis ์์กด์ฑ: Redis ์๋ฒ์ ์ฅ์ ๊ฐ ๋ฐ์ํ๋ฉด ๋ฝ ๊ด๋ฆฌ๊ฐ ๋ถ๊ฐ๋ฅํด์ง ์ ์๋ค.
- TTL ์ค์ ์ฃผ์ ํ์: ๋๋ฌด ์งง๊ฒ ์ค์ ํ ๊ฒฝ์ฐ, ์์ ์ด ๋๋๊ธฐ ์ ์ ๋ฝ์ด ํด์ ๋ ์ํ์ด ์๋ค.
๐ ์์ฝ
๋น๊ด์ ๋ฝ์ ๋ฐ์ดํฐ ์ถฉ๋ ๊ฐ๋ฅ์ฑ์ด ๋๊ฑฐ๋, ๊ฐ๋ ฅํ ๋ฐ์ดํฐ ์ ํฉ์ฑ์ด ํ์ํ ๊ฒฝ์ฐ์ ์ ํฉํ๋ค.
ํ์ง๋ง ์ฑ๋ฅ ์ ํ์ ๋ฐ๋๋ฝ ์ํ์ด ์์ด, ์ถฉ๋ ๊ฐ๋ฅ์ฑ์ด ์ ์ ๊ฒฝ์ฐ๋ผ๋ฉด ๋๊ด์ ๋ฝ์ด ๋ ๋์ ์ ํ์ผ ์ ์๋ค.
Redisson์ ์ด์ฉํ Redis ๊ธฐ๋ฐ์ ๋ถ์ฐ ๋ฝ์ ๋ค์ค ์๋ฒ ํ๊ฒฝ์์ ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ ์ ์งํ๋ฉด์ ์์ ์ ๊ทผ์ ๊ด๋ฆฌํ ์ ์๋ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ด๋ค.
Redisson์ด ์ ๊ณตํ๋ ๋ค์ํ ๋ฝ ๊ธฐ๋ฅ์ ํ์ฉํ๋ฉด ๋ถ์ฐ ์์คํ ์์ ๋ฐ๋๋ฝ ๋ฐฉ์ง์ ์ฑ๋ฅ ์ต์ ํ๊ฐ ๊ฐ๋ฅํ๋ค.
๐ก ๊ฒฐ๋ก
์ฟ ํฐ์ด ๋ฐ๊ธ ๋ ๋ ์ฟ ํฐ ๋ฐ์ดํฐ์ ์ ๊ทผํด์ ์ฟ ํฐ ๋ฐ๊ธ ์ํ๋ฅผ ์์ฃผ ์์ ํด์ค์ผ ํ๊ธฐ ๋๋ฌธ์ ๋๊ด์ ๋ฝ์ ์ถฉ๋ ์ฒ๋ฆฌ๋ ์ํ ๊ด๋ฆฌ๊ฐ ๋ณต์กํด ์ง ์ ์๊ณ ,
๋น๊ด์ ๋ฝ์ ์ฟ ํฐ ๋ฐ๊ธ๊ณผ ๊ฐ์ ๊ณ ์ ์ฒ๋ฆฌ๊ฐ ํ์ํ ์ํฉ์์ ์ฑ๋ฅ ์ ํ๋ฅผ ์๊ฐํ๋ฉด ๋ฐ๋์งํ์ง ์๋ค.
์ดํ ํ์ฅ์ฑ์ ๊ณ ๋ คํด ๋ถ์ฐ ์บ์ ์์คํ ์ธ Redisson ์ ์ด์ฉํ ๋ถ์ฐ ๋ฝ์ ์ฌ์ฉํด๋ณด๊ธฐ๋ก ๊ฒฐ์ ํ๋ค.