λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

Spring & Spring Boot

[Redis] 항곡 예맀 μ„œλΉ„μŠ€ - ν•­κ³΅κΆŒ 예맀 λ™μ‹œμ„± 이슈 해결을 μœ„ν•œ Redisson λΆ„μ‚° 락 적용

πŸ“– λͺ©μ°¨

 

 

 


 

Spring Boot MSA 기반으둜 ν•­κ³΅κΆŒ ν‹°μΌ“νŒ… μ„œλΉ„μŠ€λ₯Ό κ΅¬ν˜„ν•˜λ˜ 쀑, ν•­κ³΅κΆŒ 예맀 μ‹œ λ°œμƒν•  수 μžˆλŠ” λ™μ‹œμ„± 이슈λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€μ— Redis의 Redisson 라이브러리λ₯Ό μ΄μš©ν•΄ λΆ„μ‚° 락을 μ μš©ν–ˆλ‹€.

 

초기 κ΅¬ν˜„ λ‹Ήμ‹œ λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€κ°€ μ‚¬μš©μž μš”μ²­ μˆœμ„œλ₯Ό 보μž₯ν•˜κ³  μžˆμ—ˆμ§€λ§Œ,

'λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€μ— 적용된 λ™μ‹œμ„± μ œμ–΄κ°€ ν•­κ³΅νŽΈ μ„œλΉ„μŠ€μ—μ„œμ˜ μ’Œμ„ μˆ˜κΉŒμ§€ μ•ˆμ „ν•˜κ²Œ 보μž₯ν•΄ 쀄 수 μžˆμ„κΉŒ?'λΌλŠ”

μ˜λ¬Έμ„ κ°€μ§€κ²Œ λ˜λ©΄μ„œ μ„œλΉ„μŠ€ κ°„ λ™μ‹œμ„± μ œμ–΄μ˜ μ±…μž„ 뢄리에 λŒ€ν•œ λ¬Έμ œμ μ„ μΈμ‹ν•˜μ—¬ κ°œμ„ ν•˜λŠ” 과정을 정리해 λ³΄μ•˜λ‹€.

   

 

 

 

 

 

 

πŸ“Œ  λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€μ—λ§Œ 적용된 λ™μ‹œμ„± μ œμ–΄μ— μ˜λ¬Έμ„ 가지계 된 계기

  ν•­κ³΅νŽΈ 예맀 μ„œλΉ„μŠ€μ˜ κΈ°λ³Έ 흐름은 λ‹€μŒκ³Ό κ°™λ‹€.

1️⃣ μ‚¬μš©μžκ°€ ν•­κ³΅νŽΈ μ˜ˆμ•½ μš”μ²­
2️⃣ λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€μ—μ„œ ν•΄λ‹Ή ν•­κ³΅νŽΈμ˜ μ’Œμ„ 수 쑰회
3️⃣ μ’Œμ„ μˆ˜κ°€ μΆ©λΆ„ν•˜λ©΄ λŒ€κΈ°μ—΄μ— μ§„μž…(μš”μ²­ μˆœμ„œ 보μž₯)
4️⃣ μˆœμ„œμ— 따라 ν•­κ³΅νŽΈ μ„œλΉ„μŠ€μ— μ’Œμ„ 차감 μš”μ²­(동기 호좜)
5️⃣ μ’Œμ„ 차감 ν›„ μ˜ˆμ•½ 생성 μš”μ²­

 

 

 

  초기 κ΅¬ν˜„ λ‹Ήμ‹œμ—λŠ” λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€κ°€ μ‚¬μš©μž μš”μ²­ μˆœμ„œλ₯Ό 보μž₯ν•˜κ³  μžˆμ—ˆκΈ° λ•Œλ¬Έμ— ν•­κ³΅νŽΈ μ„œλΉ„μŠ€ λ‚΄λΆ€μ—μ„œλŠ” λ³„λ„μ˜ λ™μ‹œμ„± μ œμ–΄ 없이 μ’Œμ„ 수λ₯Ό 차감해도 μ•ˆμ „ν•  것이라고 νŒλ‹¨ν–ˆλ‹€.

< 초기 섀계 및 κ΅¬ν˜„ 단계 - ν•­κ³΅νŽΈ 예맀 μ„œλΉ„μŠ€μ˜ 흐름 >

 

...
	if(seatCount <= remainingSeats) {
	    // ν•­κ³΅νŽΈ μ’Œμ„ 차감
	    flightClient.decreaseSeats(flightId, seatCount);
	    log.info("λŒ€κΈ°μ—΄ 선점에 성곡 ν–ˆμŠ΅λ‹ˆλ‹€. 남은 μ’Œμ„ 수: {}", remainingSeats - seatCount);
	    rankOps.remove(key, topUser);
	
	    producerService.sendReserveSuccess(flightId, userId, seatCount);
	    return QueueResponseDto.of(EventStatusEnum.SUCCESS, "λŒ€κΈ°μ—΄ 선점에 μ„±κ³΅ν–ˆμŠ΅λ‹ˆλ‹€.");
	} else {
	    log.info("남은 μ’Œμ„μ΄ μ—†μŠ΅λ‹ˆλ‹€. 남은 μ’Œμ„ 수: {}", remainingSeats);
	    rankOps.remove(key, topUser);
	    deleteExistReserve(flightId, userId);
	    return QueueResponseDto.of(EventStatusEnum.FAILED, "남은 μ’Œμ„μ΄ μ—†μŠ΅λ‹ˆλ‹€.");
	}

 

 

 

 

 


 

 

 

 

 

  ν…ŒμŠ€νŠΈ 쀑 λ°œμƒν•œ λ¬Έμ œλ‚˜ νŠΉμ •ν•œ 였λ₯˜λŠ” μ—†μ—ˆμ§€λ§Œ, μ’Œμ„ 수의 κ°μ†Œμ™€ 증가에 λŒ€ν•œ 비동기 처리둜의 μ „ν™˜ κ³Όμ •μ—μ„œ λ‘œμ§μ„ μ κ²€ν•˜λ˜ 쀑 λ‹€μŒκ³Ό 같은 의문이 λ“€μ—ˆλ‹€.

 

'λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€μ—μ„œ μ‚¬μš©μž μš”μ²­ μˆœμ„œλ₯Ό 보μž₯ν•΄ μ€€λ‹€κ³  해도, ν•­κ³΅νŽΈ μ„œλΉ„μŠ€ λ‚΄λΆ€μ˜ μ’Œμ„ 수 κ°μ†Œ μž‘μ—…κΉŒμ§€ μ•ˆμ „ν•˜κ²Œ 보μž₯ν•  수 μžˆμ„κΉŒ?'

 

 

μ΄λŸ¬ν•œ μ˜λ¬Έμ„ λ°”νƒ•μœΌλ‘œ μ„œλΉ„μŠ€ κ°„ 역할을 λ‹€μ‹œ μ κ²€ν•œ κ²°κ³Ό, μ •ν•΄μ§„ μˆœμ„œλŒ€λ‘œ μ‚¬μš©μž μš”μ²­μ„ κ΄€λ¦¬ν•˜λŠ” 것은 λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€μ˜ μ±…μž„μ΄μ§€λ§Œ μ’Œμ„ 수의 변경은 항곡 μ„œλΉ„μŠ€μ—μ„œ 직접 μ œμ–΄ν•΄μ•Ό ν•  μ±…μž„μ΄κΈ° λ•Œλ¬Έμ— μ’Œμ„ 수의 μ›μžμ„±μ„ 보μž₯ν•˜λŠ” λͺ©μ μ˜ λ™μ‹œμ„± μ œμ–΄κ°€ λ³„λ„λ‘œ ν•„μš”ν•˜λ‹€λŠ” 결둠을 λ„μΆœν•˜κ²Œ λ˜μ—ˆλ‹€.

 

 

 

 

 

 

 


 

 

 

 

 

 

 

πŸ“Œ  λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€μ™€ ν•­κ³΅νŽΈ μ„œλΉ„μŠ€κ°„ μ±…μž„μ˜ 뢄리

  λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€μ™€λŠ” 달리 항곡 μ„œλΉ„μŠ€μ˜ ν•­κ³΅νŽΈ μ’Œμ„ μˆ˜λŠ” DB λ‚΄μ—μ„œ λ³€κ²½λ˜λŠ” 곡유 μžμ›μœΌλ‘œ μ—¬λŸ¬ μŠ€λ ˆλ“œλ‚˜ ν”„λ‘œμ„ΈμŠ€κ°€ λ™μ‹œμ— μ ‘κ·Όν•  경우, λ™μ‹œμ„± μ œμ–΄κ°€ λ˜μ§€ μ•ŠλŠ”λ‹€λ©΄ μ’Œμ„ μˆ˜κ°€ 잘λͺ» μ°¨κ°λ˜μ–΄ 데이터 μ •ν•©μ„± λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλ‹€

* μ’Œμ„ 수의 차감뿐만 μ•„λ‹ˆλΌ, μ’Œμ„ 수의 볡ꡬ λ‘œμ§λ„ λ§ˆμ°¬κ°€μ§€μ΄λ‹€.

 

이에 따라 λ°μ΄ν„°μ˜ 일관성을 μœ μ§€ν•˜κΈ° μœ„ν•΄ ν•­κ³΅νŽΈ μ„œλΉ„μŠ€ λ‚΄λΆ€μ—μ„œ μ’Œμ„ 수λ₯Ό λ³€κ²½ν•˜λŠ” λ‘œμ§μ— Redisson을 μ‚¬μš©ν•˜μ—¬ λΆ„μ‚° 락을 μ μš©ν•˜κΈ°λ‘œ κ²°μ •ν–ˆλ‹€.

public class DistributedLockAop {
...
        // μ˜ˆμ™Έ 처리 및 락 νšλ“μ„ μž¬μ‹œλ„ν•˜λŠ” 둜직
        int retryCount = 0;

        while (retryCount < distributedLock.maxRetryCount()) {
            try {
                boolean available = rLock.tryLock(distributedLock.waitTime(),
                        distributedLock.leaseTime(), distributedLock.timeUnit());

                if (available) {
                    log.info("[DistributedLock]: Lock acquired with key: {}", key);
                    try {
                        return aopForTransaction.proceed(joinPoint);
                    } finally {
                        if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                            log.info("[DistributedLock]: Lock released with key: {}", key);
                            rLock.unlock();
                        }
                    }
                } else {
                    log.info("[DistributedLock]: Lock acquisition failed with key: {}", key);
                    retryCount++;
                    Thread.sleep(distributedLock.retryDelay());
                }
            } catch (InterruptedException e) {
                log.error("[DistributedLock]: Lock interrupted", e);
                Thread.currentThread().interrupt();
                throw e;
            }
        }
        log.warn("[DistributedLock]: Lock acquisition failed after {} retries with key: {}", retryCount, key);
        throw new LockFailedAfterRetryException();
    }
}

 

// μ’Œμ„ 수 차감
@DistributedLock(key = "#flightId.toString()")
public void decreaseSeats(UUID flightId, Integer requiredSeats) {
    ...
}

// μ’Œμ„ 수 볡ꡬ
@DistributedLock(key = "#flightId.toString()")
public void increaseSeats(UUID flightId, Integer requiredSeats) {
    ...
}

 

AOP 기반으둜 @DistibutedLock μ–΄λ…Έν…Œμ΄μ…˜μ„ μ„ μ–Έν•˜μ—¬ ν•­κ³΅νŽΈ ID에 락을 κ±Έμ–΄ λ™μ‹œμ„± μ œμ–΄λ₯Ό ν•  수 μžˆλ„λ‘ κ΅¬ν˜„ν–ˆλ‹€. AOP 기반으둜 λΆ„μ‚° 락을 κ΅¬ν˜„ν•˜λŠ” 과정은 μ•„λž˜ ν¬μŠ€νŒ…μ—μ„œ 확인할 수 μžˆλ‹€.

 

 

[Redis] 이컀머슀 μ„œλΉ„μŠ€ - Redisson을 μ΄μš©ν•œ λΆ„μ‚° 락 κ΅¬ν˜„μœΌλ‘œ λ™μ‹œμ„± 이슈 ν•΄κ²°

πŸ“– λͺ©μ°¨ SpringBootλ₯Ό 기반으둜 이컀머슀 μ„œλΉ„μŠ€λ₯Ό κ΅¬ν˜„ν•˜λ©΄μ„œ λ™μ‹œμ„± μ΄μŠˆκ°€ λ°œμƒν–ˆλ‹€.Redis의 Redisson라이브러리λ₯Ό μ΄μš©ν•΄ λ™μ‹œμ„± 이슈λ₯Ό ν•΄κ²°ν•˜λŠ” 방법에 λŒ€ν•΄ μ•Œμ•„λ³΄μž. πŸ“Œ λ™μ‹œμ„± μ œμ–΄λž€? λ™μ‹œ

zzudev.tistory.com

 

 

 

 

 

 

 


 

 

 

 

 

 

 

πŸ“Œ  Redisson λΆ„μ‚° 락의 적용

  μœ„μ—μ„œ μ–ΈκΈ‰ν–ˆλ“―μ΄ λŒ€κΈ°μ—΄ μ„œλΉ„μŠ€μ™€ ν•­κ³΅νŽΈ μ„œλΉ„μŠ€μ˜ μ±…μž„μ„ λΆ„λ¦¬ν•˜μ—¬ ν•­κ³΅νŽΈ μ„œλΉ„μŠ€ λ‚΄λΆ€μ˜ μ’Œμ„ 차감 및 볡ꡬ λ‘œμ§μ— λΆ„μ‚° 락을 μ μš©ν•˜μ˜€λ‹€. 그리고 λΆ„μ‚° 락의 적용 μ „κ³Ό ν›„, λ™μ‹œμ„± μ œμ–΄κ°€ μ •μƒμ μœΌλ‘œ λ™μž‘ν•˜λŠ”μ§€ ν™•μΈν•˜κΈ° μœ„ν•΄ λ‹€μŒκ³Ό 같은 쑰건으둜 ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν–ˆλ‹€. 

πŸ’‘ ν…ŒμŠ€νŠΈ μ§„ν–‰ 쑰건 πŸ’‘
βœ”οΈ ν•­κ³΅νŽΈ μ’Œμ„ 수: 10,000개
βœ”οΈ λ™μ‹œ μ‚¬μš©μž: 100λͺ…
βœ”οΈ μš”μ²­ 수: 10초 λ™μ•ˆ λ™μ‹œ μš”μ²­

< ν•΄λ‹Ή ν•­κ³΅νŽΈμ˜ μ’Œμ„ 수λ₯Ό 10,000개둜 μ„€μ • >

 

 

μ•„λž˜μ™€ 같이 μ’Œμ„ 차감 슀크립트λ₯Ό μž‘μ„±ν•˜μ—¬ k6둜 ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν–ˆλ‹€.

import http from 'k6/http';
import { check, fail } from 'k6';

export let options = {
    vus: 100,
    duration: '10s',
};

const token = 'Bearer '; // token μž…λ ₯ ν•„μš”
const flightId = '2129373a-1741-45a4-a2b0-56914adcc906';

export default function () {
    const url = `http://localhost:19091/internal/v1/flights/${flightId}/seats/decrease?requiredSeats=1`;

    const headers = {
        Authorization: token,
    };

    let res = http.put(url, null, { headers });

    const success = check(res, {
        'status is 200': (r) => r.status === 200,
    });

    if (!success) {
        console.error(`❌ μš”μ²­ μ‹€νŒ¨ | μƒνƒœμ½”λ“œ: ${res.status} | 응닡: ${res.body}`);
    }
}

< k6 ν…ŒμŠ€νŠΈ κ²°κ³Ό - μš”μ²­ 건수: 428건 / μš”μ²­ 성곡 ν™•λ₯ : 100% >
< μ˜ˆμƒν•œ 결과와 λ‹€λ₯Έ μ’Œμ„ 수 / μ˜ˆμƒ μ’Œμ„ 수: 9,572개 >

  • ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€ : μ΄ 428건의 μš”μ²­μ΄ λͺ¨λ‘ μ„±κ³΅ν•˜μ˜€μœΌλ‹ˆ μ’Œμ„μ˜ μˆ˜λŠ” 10,000개 → 9,572κ°œκ°€ λ˜μ–΄μ•Ό ν•œλ‹€.
  • ν…ŒμŠ€νŠΈ κ²°κ³Ό : μ’Œμ„ 수 10,000개 → 9,949개, μš”μ²­ μˆ˜μ™€ λ‹€λ₯Έ 51개의 μ’Œμ„ κ°μ†Œ *

 

 

 

  λΆ„μ‚° 락을 μ μš©ν•˜κΈ° μ „μ—λŠ” 총 428건의 μš”μ²­μ΄ ν™•μΈλ˜μ—ˆκ³ , μš”μ²­μ΄ λͺ¨λ‘ μ„±κ³΅ν–ˆμœΌλ‹ˆ μ’Œμ„μ˜ μˆ˜λŠ” 428κ°œκ°€ κ°μ†Œν•œ 9,572κ°œκ°€ λ˜μ–΄μ•Ό ν•œλ‹€. ν•˜μ§€λ§Œ μ˜ˆμƒκ³Ό λ‹€λ₯΄κ²Œ μ’Œμ„μ˜ μˆ˜λŠ” 10,000개 → 9,949개둜 51개의 μ’Œμ„λ§Œ κ°μ†Œν•˜λ©° λ°μ΄ν„°μ˜ μ •ν•©μ„±κ³Ό μ›μžμ„±μ΄ κΉ¨μ§„ 것을 확인할 수 μžˆμ—ˆλ‹€.

 

 

 

 

 


 

 

 

 

 

‼️ μ΄λ²ˆμ—λŠ”, λΆ„μ‚° 락을 적용 ν•œ ν›„, λ™μΌν•œ 쑰건으둜 ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•œ λ’€ κ²°κ³Όλ₯Ό 비ꡐ해 보자.

< k6 ν…ŒμŠ€νŠΈ κ²°κ³Ό - μš”μ²­ 건수: 2,971건 / μš”μ²­ 성곡 ν™•λ₯ : 100% >
< 둜그λ₯Ό 톡해 락 ν•΄μ œ 및 νšλ“ μ—¬λΆ€ 확인 >
< μ˜ˆμƒν•œ 결과와 같은 μ’Œμ„ 수 / μ˜ˆμƒ μ’Œμ„ 수: 7,029개 >

  • ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€ : μ΄ 2,971건의 μš”μ²­μ΄ λͺ¨λ‘ μ„±κ³΅ν•˜μ˜€μœΌλ‹ˆ μ’Œμ„μ˜ μˆ˜λŠ” 10,000개 → 7,029κ°œκ°€ λ˜μ–΄μ•Ό ν•œλ‹€.
  • ν…ŒμŠ€νŠΈ κ²°κ³Ό : μ’Œμ„ 수 10,000개 → 7,029개, μš”μ²­μ˜ 수만큼 μ’Œμ„ κ°μ†Œ *

 

 

 

  λΆ„μ‚° 락을 μ μš©ν•œ ν›„μ—λŠ” 총 2,971건의 μš”μ²­μ΄ ν™•μΈλ˜μ—ˆκ³ , μš”μ²­μ΄ λͺ¨λ‘ μ„±κ³΅ν–ˆμœΌλ‹ˆ μ’Œμ„μ˜ μˆ˜λŠ” 2,971κ°œκ°€ κ°μ†Œν•œ 7,029κ°œκ°€ λ˜μ–΄μ•Ό ν•œλ‹€. 둜그λ₯Ό 톡해 락 νšλ“ 및 ν•΄μ œκ°€ μ •μƒμ μœΌλ‘œ λ˜λŠ” 것을 ν™•μΈν–ˆκ³ , μ’Œμ„μ˜ μˆ˜λŠ” μ˜ˆμƒν•œ 결과와 같이 10,000개 → 7,029개둜 μ •ν™•νžˆ μš”μ²­μ˜ 수만큼 κ°μ†Œν•œ 것을 확인할 수 μžˆμ—ˆλ‹€. 

 

 

 

 


 

 

 

 

πŸ’‘ μœ„μ—μ„œ μ§„ν–‰ν•œ ν…ŒμŠ€νŠΈ κ²°κ³Όλ₯Ό μ •λ¦¬ν•˜μžλ©΄ λ‹€μŒκ³Ό κ°™λ‹€. πŸ’‘ 

βœ”οΈ 초기 μ’Œμ„ 수: 10,000개 / ν…ŒμŠ€νŠΈ 쑰건: λ™μ‹œ 100λͺ…, 10μ΄ˆκ°„ μš”μ²­ / 도ꡬ: k6 βœ”οΈ 

βœ… λΆ„μ‚° 락 적용 μ „
• μš”μ²­ 수: 428건
• μš”μ²­ 성곡λ₯ : 100%
• κΈ°λŒ€ μ’Œμ„ 수: 10,000 - 428 = 9,572개
• μ‹€μ œ μ’Œμ„ 수: 9,949개 (51개만 차감됨)
응닡은 성곡이라 ν–ˆμ§€λ§Œ, μ‹€μ œ DB에 λ°˜μ˜λ˜μ§€ μ•ŠμŒ

데이터 μ •ν•©μ„± 문제 λ°œμƒ

βœ… λΆ„μ‚° 락 적용 ν›„
• μš”μ²­ 수: 2,971건
• μš”μ²­ 성곡λ₯ : 100%
• κΈ°λŒ€ μ’Œμ„ 수: 10,000 - 2,971 = 7,029개
• μ‹€μ œ μ’Œμ„ 수: 7,029개 (정상 차감)
λ‘œκ·Έμ—μ„œ 락 νšλ“ 및 ν•΄μ œ 정상 확인, μ‹€μ œ DB에 μ„±κ³΅μ μœΌλ‘œ 반영
데이터 μ •ν•©μ„±κ³Ό μ›μžμ„± 보μž₯ 확인

 

βœ… κ²°κ³Ό 정리
• μ„œλΉ„μŠ€ 경계λ₯Ό λͺ…ν™•νžˆ 이해해야 함
• μˆœμ„œ 보μž₯κ³Ό μ›μžμ„± 보μž₯은 λ³„κ°œμ˜ 문제
• 곡유 μžμ›μ— λŒ€ν•œ μ •ν•©μ„± 보μž₯ μ±…μž„μ€ ν•΄λ‹Ή μžμ›μ„ 직접 λ‹€λ£¨λŠ” μ„œλΉ„μŠ€μ— μžˆμ–΄μ•Ό 함
• λΆ„μ‚° 락을 ν†΅ν•œ λ™μ‹œμ„± μ œμ–΄λŠ” ν•„μˆ˜μ 

*
μ’Œμ„ 수의 증가에 λŒ€ν•΄μ„œλ„ 같은 λ°©μ‹μœΌλ‘œ ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•˜μ˜€κ³ , μ •μƒμ μœΌλ‘œ λ™μž‘ν•˜λŠ” 것을 확인할 수 μžˆμ—ˆλ‹€.

 

 

 

 

  μ΄λ ‡κ²Œ 문제점 κ°œμ„  ν›„, ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•˜μ—¬ κ²°κ³Όλ₯Ό ν™•μΈν•˜κ³  정상 μž‘λ™ν•˜λŠ” κ²ƒκΉŒμ§€ 확인해 λ³΄μ•˜λ‹€. 이λ₯Ό 톡해 μ„œλΉ„μŠ€ κ°„ 경계λ₯Ό λͺ…ν™•νžˆ μ΄ν•΄ν•˜κ³ , λ‹¨μˆœνžˆ μš”μ²­ μˆœμ„œλ₯Ό μ œμ–΄ν•œλ‹€κ³  ν•΄μ„œ 데이터 정합성이 보μž₯λ˜μ§€ μ•ŠλŠ”λ‹€λŠ” 점과 μ‹€μ œ μžμ›μ„ μ œμ–΄ν•˜λŠ” μͺ½μ—μ„œ λͺ…μ‹œμ μœΌλ‘œ λ™μ‹œμ„± μ œμ–΄λ₯Ό ν•΄μ•Ό ν•œλ‹€λŠ” 점에 λŒ€ν•΄ 쑰금 더 깊이 μ΄ν•΄ν•˜κ²Œ λ˜μ—ˆλ‹€.

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

μ—¬κΈ°κΉŒμ§€ μ„œλΉ„μŠ€ κ°„ λ™μ‹œμ„± μ œμ–΄μ˜ μ±…μž„ 뢄리에 λŒ€ν•œ 문제점의 인식과 해결과정에 λŒ€ν•΄ 정리해 λ³΄μ•˜λ‹€.