System Design

λ™μ‹œμ„± 문제λ₯Ό ν•΄κ²°ν•˜λŠ” μ—¬λŸ¬κ°€μ§€ 방법

ν”„λ‘œκ·Έλž˜λ¨Έ μ˜€μ›” 2024. 11. 25.

μ†Œν”„νŠΈμ›¨μ–΄ κ°œλ°œμ—μ„œ λ™μ‹œμ„± λ¬Έμ œλŠ” 데이터 일관성과 μ‹œμŠ€ν…œ μ•ˆμ •μ„±μ„ μœ„ν•΄ λ°˜λ“œμ‹œ κ³ λ €ν•΄μ•Ό ν•˜λŠ” μš”μ†Œλ‹€. 특히, λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό λ‹€λ£¨λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œλŠ” μ—¬λŸ¬ μ‚¬μš©μžμ˜ μš”μ²­μ„ λ™μ‹œμ— μ²˜λ¦¬ν•˜λ©΄μ„œλ„ λ°μ΄ν„°μ˜ 정확성을 μœ μ§€ν•˜λŠ” 것이 핡심이닀.

 

 

μ„ μ°©μˆœ μ΄λ²€νŠΈλ‚˜ 재고 μ²˜λ¦¬λŠ” λ™μ‹œμ„±(Concurrency) λ¬Έμ œκ°€ λ°œμƒν•˜κΈ° μ‰¬μš΄ μ˜μ—­μœΌλ‘œ, λ‹€μˆ˜μ˜ μš”μ²­μ΄ λ™μ‹œμ— 처리될 λ•Œ μ •ν™•μ„±κ³Ό 일관성을 μœ μ§€ν•΄μ•Ό ν•œλ‹€. 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λ‹€μ–‘ν•œ λ™μ‹œμ„± μ œμ–΄ 방법을 μ‚¬μš©ν•  수 있으며, μ‹œμŠ€ν…œμ˜ μš”κ΅¬ 사항과 νŠΈλž˜ν”½μ— 따라 μ ν•©ν•œ 방법을 선택해야 ν•œλ‹€.

 

 

1. λ°μ΄ν„°λ² μ΄μŠ€ λ ˆλ²¨μ—μ„œ λ™μ‹œμ„± μ œμ–΄

νŠΈλžœμž­μ…˜ & 락

λ°μ΄ν„°λ² μ΄μŠ€ νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•˜μ—¬ SELECT ... FOR UPDATE둜 재고λ₯Ό μ‘°νšŒν•˜κ³  μ—…λ°μ΄νŠΈ μ‹œ 락을 κ±Έμ–΄ λ™μ‹œμ„±μ„ μ œμ–΄. 비관적 락의 일쒅이닀.

START TRANSACTION;
SELECT stock FROM inventory WHERE product_id = 1 FOR UPDATE;
UPDATE inventory SET stock = stock - 1 WHERE product_id = 1 AND stock > 0;
COMMIT;

 

λ°μ΄ν„°λ² μ΄μŠ€ μˆ˜μ€€μ—μ„œ μ •ν™•ν•œ λ™μ‹œμ„±μ„ 보μž₯ν•΄ μ€€λ‹€. ν•˜μ§€λ§Œ 락이 κ±Έλ¦° μƒνƒœμ—μ„  ν•΄λ‹Ή νŠΈλžœμž­μ…˜μ΄ μ™„λ£Œλ  λ•ŒκΉŒμ§€ λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ΄ 데이터에 μ ‘κ·Όν•˜μ§€ λͺ»ν•œλ‹€. κ·Έλž˜μ„œ 높은 νŠΈλž˜ν”½μ—μ„œ μ„±λŠ₯μ €ν•˜ 문제λ₯Ό μΌμœΌν‚¬ 수 μžˆλ‹€.

λ˜ν•œ λΆ„μ‚° λ°μ΄ν„°λ² μ΄μŠ€μ—μ„  락이 ν•œ 곳의 λ°μ΄ν„°λ§Œ μž κΈˆμ„ 걸기에 λ‹€λ₯Έ λ°μ΄ν„°λ² μ΄μŠ€μ˜ λ°μ΄ν„°λŠ” μ ‘κ·Ό κ°€λŠ₯ν•˜κΈ°μ— ν•œκ³„κ°€ μžˆλ‹€.

 

비관적 락(Pessimistic Lock)

좩돌이 λ°œμƒν•  κ°€λŠ₯성이 높은 경우 μ‚¬μš©λ˜λŠ” λ™μ‹œμ„± μ œμ–΄ 기법이닀. μœ„μ— νŠΈλžœμž­μ…˜ & 락과 같이 λ°μ΄ν„°λ² μ΄μŠ€μ— 락을 κ±°λŠ” 방식이닀.  μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ½”λ“œμ—μ„œ 섀정이 κ°€λŠ₯ν•˜λ‹€.

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Product findProductForUpdate(@Param("id") Long id);

 

데이터λ₯Ό μ½κ±°λ‚˜ μˆ˜μ •ν•˜κΈ° 전에 LOCK 을 νšλ“ν•˜μ—¬ λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ˜ μ•‘μ„ΈμŠ€λ₯Ό μ°¨λ‹¨ν•œλ‹€. 

 

λ™μ‹œμ„± λ¬Έμ œκ°€ λΉˆλ²ˆν•˜κ²Œ μΌμ–΄λ‚œλ‹€λ©΄ rollback의 횟수λ₯Ό 쀄일 수 있기 λ•Œλ¬Έμ— μ„±λŠ₯적으둜 μ’‹λ‹€. λͺ¨λ“  νŠΈλžœμ μ…˜μ— lock을 μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έμ—, lock이 ν•„μš”ν•˜μ§€ μ•Šμ€ 상황이더라도 무쑰건 lock을 κ±Έμ–΄μ„œ μ„±λŠ₯상 λ¬Έμ œκ°€ 될 수 μžˆλ‹€. 특히 read μž‘μ—…μ΄ 많이 μΌμ–΄λ‚˜λŠ” 경우 단점이 될 수 μžˆλ‹€. λ˜ν•œ, μ„ μ°©μˆœ μ΄λ²€νŠΈκ°™μ΄ λ§Žμ€ νŠΈλž˜ν”½μ΄ λͺ°λ¦¬λŠ” μƒν™©μ΄λ‚˜ μ—¬λŸ¬ ν…Œμ΄λΈ”μ— lock을 κ±Έλ©΄μ„œ μ„œλ‘œ μžμ›μ΄ ν•„μš”ν•œ 경우, λ°λ“œλ½μ΄ λ°œμƒν•  수 있고 μ΄λŠ” 비관적 락으둜 ν•΄κ²°ν•  수 μ—†λŠ” 뢀뢄이닀.

λ˜ν•œ λ˜‘κ°™μ΄ λΆ„μ‚° λ°μ΄ν„°λ² μ΄μŠ€ ν™˜κ²½μ—μ„œλ„ μ œν•œμ μ΄λ‹€.

 

낙관적 락(Optimistic Lock)

낙관적 락은 데이터 λ³€κ²½ μ‹œμ μ—λ§Œ μΆ©λŒμ„ κ²€μ‚¬ν•˜λŠ” 방식이닀. μ™œλƒν•˜λ©΄ 낙관적 락은 데이터 좩돌이 자주 λ°œμƒν•˜μ§€ μ•ŠλŠ” μƒν™©μ—μ„œ μ„±λŠ₯을 μ΅œμ ν™”ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜κΈ° λ•Œλ¬Έμ΄λ‹€.

λ ˆμ½”λ“œμ— version ν•„λ“œλ₯Ό μΆ”κ°€ν•˜κ³ , μ—…λ°μ΄νŠΈ μ‹œ μ²΄ν¬ν•œλ‹€. λ§Œμ•½ version이 λ³€κ²½λ˜μ—ˆμœΌλ©΄ μž¬μ‹œλ„(좩돌 감지) 을 ν†΅ν•΄ μΆ©λŒμ„ ν•΄κ²°ν•œλ‹€. μžμ›μ— Lock을 직접 κ±Έμ–΄μ„œ μ„ μ ν•˜μ§€ μ•Šκ³ , λ™μ‹œμ„± λ¬Έμ œκ°€ μ‹€μ œλ‘œ λ°œμƒν•˜λ©΄ κ·Έλ•Œκ°€μ„œ μ²˜λ¦¬ν•˜λŠ” 방식이닀. μžμ›μ„ μ½μ—ˆμ„ λ•Œ 버전이 7μ΄μ—ˆλŠ”λ° μˆ˜μ •ν•˜λ €κ³  ν•˜λ‹ˆ 버전이 8 이면 κ·Έ 사이에 λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ΄ μ™€μ„œ μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈ ν–ˆλ‹€λŠ” λœ»μ΄λœλ‹€. 그럼 νŠΈλžœμž­μ„ μ„ λ‘€λ°±ν•΄μ„œ λ‹€μ‹œ μ²˜λ¦¬ν•œλ‹€. 

@Entity
public class {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private int stock;
    
    @Version
    private Long version;
}

 

μ‹€μ œλ‘œ lock을 μ‚¬μš©ν•˜μ§€ μ•Šκ³  version을 μ΄μš©ν•¨μœΌλ‘œμ„œ 정합성을 λ§žμΆ”κΈ°μ— μ„±λŠ₯λ©΄μ—μ„œ μœ λ¦¬ν•˜λ‹€. ν•˜μ§€λ§Œ λ™μ‹œμ„± λ¬Έμ œκ°€ λΉˆλ²ˆν•˜κ²Œ μΌμ–΄λ‚˜λ©΄ 계속 rollback 처리λ₯Ό ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

λΆ„μ‚° λ°μ΄ν„°λ² μ΄μŠ€ ν™˜κ²½μ—μ„œλ„ μ œν•œμ μ΄λ‹€.

 

 

 

 

2. μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ ˆλ²¨μ—μ„œ λ™μ‹œμ„± μ œμ–΄

Synchronized ν‚€μ›Œλ“œ μ‚¬μš©

Java의 synchronized ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ νŠΉμ • μ½”λ“œ λΈ”λ‘μ˜ λ™μ‹œ 싀행을 μ°¨λ‹¨ν•œλ‹€. 

public synchronized void updateStock(int productId, int quantity) {
    // 재고 처리 둜직
}

 

κ°„λ‹¨νžˆ κ΅¬ν˜„μ΄ κ°€λŠ₯ν•˜μ§€λ§Œ, 단일 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œλ§Œ μž‘λ™ν•˜κΈ°μ— μ„œλ²„κ°€ μ—¬λŸ¬ 개인 λΆ„μ‚° μ‹œμŠ€ν…œ ν™˜κ²½μ—μ„œλŠ” μ‚¬μš©ν•  수 μ—†λ‹€. 

 

뢄산락 μ‚¬μš©

뢄산락은 μ—¬λŸ¬ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ˜λŠ” ν”„λ‘œμ„ΈμŠ€ 간에 곡유된 μžμ›μ— λŒ€ν•œ λ™μ‹œ μ•‘μ„ΈμŠ€λ₯Ό μ œμ–΄ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœλ‹€.

μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„  λŒ€λΆ€λΆ„ Redis, Zookeeper 등을 ν™œμš©ν•˜μ—¬ λΆ„μ‚° ν™˜κ²½μ—μ„œ 락을 κ΅¬ν˜„ν•œλ‹€. λΆ„μ‚° μ‹œμŠ€ν…œμ—μ„œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ 곡유 μžμ›μ— μ ‘κ·Όν•˜κΈ° 전에 락을 μš”μ²­λ‹€. 락이 μ„±κ³΅μ μœΌλ‘œ νšλ“λ˜λ©΄ μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ³ , μ™„λ£Œ ν›„ 락을 ν•΄μ œλ‹€. 락이 이미 점유 쀑이라면, μš”μ²­μ€ μ‹€νŒ¨ν•˜κ±°λ‚˜ λŒ€κΈ° μƒνƒœλ‘œ μ²˜λ¦¬λœλ‹€.

private final RedissonClient redissonClient;

    public DistributedLockExample(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public void executeCriticalTask() {
        RLock lock = redissonClient.getLock("criticalTaskLock");
        try {
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) { // 락 μ‹œλ„ (μ΅œλŒ€ 10초 λŒ€κΈ°, 30초 μœ μ§€)
                // 곡유 μžμ› μž‘μ—…
                System.out.println("Lock acquired, executing critical task...");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock(); // 락 ν•΄μ œ
            System.out.println("Lock released.");
        }
    }

 

λΆ„μ‚°ν™˜κ²½μ—μ„œλ„ 락 κ΅¬ν˜„μ΄ κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ— 널리 μ‚¬μš©λ˜λŠ” 방식이닀. λΆ„μ‚° 락은 μ™ΈλΆ€ μ‹œμŠ€ν…œμ΄ κ΄€λ¦¬ν•˜μ—¬ 락의 μƒνƒœλ₯Ό μ€‘μ•™μ—μ„œ κ΄€λ¦¬ν•œλ‹€. λ”°λΌμ„œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄λ‚˜ DB에 락을 κ±°λŠ” 것이 μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— 높은 μ„±λŠ₯κ³Ό μ•ˆμ •μ„±μ„ μ œκ³΅ν•œλ‹€. 

Redisλ₯Ό ν™œμš©ν•˜κΈ°μ— Redis ꡬ좕 이해λ₯Ό ν•„μš”λ‘œ ν•˜κ³ , 락 νšλ“ 및 ν•΄μ œ μ‹€νŒ¨ μ‹œ 볡ꡬ 둜직 ν•„μš”ν•˜λ‹€. λ˜ν•œ λ„€νŠΈμ›Œν¬ μ§€μ—°μ΄λ‚˜ μž₯μ• λ‘œ μΈν•œ λ¬Έμ œμ— λŒ€λΉ„ν•  수 μžˆμ–΄μ•Ό ν•œλ‹€.

 

3. 미듀웨어λ₯Ό ν™œμš©ν•œ λ™μ‹œμ„± μ œμ–΄

λ©”μ‹œμ§€ 큐 같은 미듀웨어λ₯Ό ν™œμš©ν•œ 방법이닀. λͺ¨λ“  재고 처리 μš”μ²­μ„ λ©”μ‹œμ§€ 큐(Kafka, RabbitMQ λ“±)에 λ„£κ³  순차적으둜 μ²˜λ¦¬ν•œλ‹€. μƒμ‚°μž(Producer)κ°€ μš”μ²­μ„ 큐에 보내고, μ†ŒλΉ„μž(Consumer)κ°€ 이λ₯Ό ν•˜λ‚˜μ”© μ²˜λ¦¬ν•œλ‹€. 비동기 처리둜 μš”μ²­μ„ μ¦‰μ‹œ μ²˜λ¦¬ν•˜μ§€ μ•Šκ³  νλ‚˜ 이벀트 λ‘œκ·Έμ— μ €μž₯ν•œ λ’€ 순차적으둜 μ²˜λ¦¬ν•œλ‹€.

νŠΈλž˜ν”½ 급증에도 μ•ˆμ •μ μΈ 처리 κ°€λŠ₯ν•˜λ‹€. 큐에 μžˆλŠ” μš”μ²­μ„ μ†ŒλΉ„ν•˜λ©° λ™μž‘ν•˜κΈ°μ— λ™μ‹œμ„± λ¬Έμ œκ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ”λ‹€. 둜그 기반의 Kafkaλ₯Ό μ‚¬μš©ν•œλ‹€λ©΄ λͺ¨λ“  μ΄λ²€νŠΈλ“€μ„ 둜그 ν˜•μ‹μœΌλ‘œ 남기기 λ•Œλ¬Έμ— λ³€κ²½ 사항을 좔적할 수 μžˆλ‹€.

μš”μ²­μ— λŒ€ν•΄ λ™κΈ°μ μœΌλ‘œ 처리 ν•˜μ§€ μ•Šκ³  λ„€νŠΈμ›Œν¬ 톡신을 ν•˜κΈ°μ— μ•½κ°„μ˜ 지연이 λ°œμƒν•  수 μžˆλ‹€.

쀑볡 처리, μˆœμ„œ 문제, 병λͺ© ν˜„μƒ 등이 λ°œμƒν•  κ°€λŠ₯성은 μ‘΄μž¬ν•˜κΈ°μ—, λ©±λ“±μ„± 섀계, μˆœμ„œ 보μž₯ λ©”μ»€λ‹ˆμ¦˜, Consumer ν™•μž₯ λ“±μ˜ μ „λž΅μ„ μ‚¬μš©ν•΄ ν•œλ‹€. λ˜ν•œ 좔가적인 미듀웨어λ₯Ό ν™œμš©ν•˜κΈ°μ— μ΄μ—λŒ€ν•œ 이해와 ꡬ좕 및 관리가 ν•„μš”ν•˜λ‹€.

 

'System Design' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

μ‹€λ¬΄μ—μ„œ 자주 μ ‘ν•˜κ²Œ λ˜λŠ” μ•ˆν‹° νŒ¨ν„΄  (0) 2024.12.26

λŒ“κΈ€