Cache-Aside

Cache-aside 패턴은 마이크로서비스 아키텍처(MSA)에서 시스템의 신뢰성(Reliability)을 향상시키기 위해 사용되는 중요한 캐싱 전략이다.

Cache-aside 패턴은 애플리케이션이 데이터를 읽을 때 먼저 캐시를 확인하고, 캐시에 데이터가 없을 경우 데이터베이스에서 데이터를 가져와 캐시에 저장하는 방식이다.
이 패턴은 “Lazy Loading” 또는 “Look Aside” 패턴으로도 알려져 있다.

Cache-aside 패턴은 MSA 환경에서 시스템의 성능과 신뢰성을 향상시키는 효과적인 방법이다. 하지만 적절한 구현과 관리가 필요하며, 시스템의 요구사항에 맞게 신중하게 설계해야 한다.

Cache-aside
https://learn.microsoft.com/ko-kr/azure/architecture/patterns/cache-aside

동작 방식

  1. 애플리케이션이 데이터를 요청한다.
  2. 캐시를 먼저 확인한다.
  3. 캐시에 데이터가 있으면(캐시 히트) 즉시 반환한다.
  4. 캐시에 데이터가 없으면(캐시 미스) 데이터베이스에서 데이터를 조회한다.
  5. 데이터베이스에서 가져온 데이터를 캐시에 저장한다.
  6. 데이터를 애플리케이션에 반환한다.

구현 시 고려사항

  1. 캐시 일관성: 데이터베이스의 데이터가 변경될 때 캐시를 업데이트하거나 무효화해야 한다.
  2. TTL(Time To Live) 설정: 캐시된 데이터의 유효 기간을 설정하여 오래된 데이터 문제를 방지한다.
  3. 캐시 크기 관리: 메모리 사용량을 고려하여 적절한 캐시 크기를 설정해야 한다.
  4. 동시성 제어: 여러 요청이 동시에 같은 데이터를 요청할 때의 처리 방법을 고려해야 한다.

장점

  1. 성능 향상: 자주 접근하는 데이터를 빠르게 제공할 수 있다.
  2. 데이터베이스 부하 감소: 캐시를 통해 데이터베이스 쿼리 수를 줄일 수 있다.
  3. 유연성: 캐시와 데이터베이스를 독립적으로 확장할 수 있다.
  4. 장애 대응: 캐시 서버에 문제가 생겨도 데이터베이스를 통해 서비스를 계속할 수 있다.

단점

  1. 초기 지연: 캐시 미스 시 데이터베이스 조회로 인한 지연이 발생할 수 있다.
  2. 데이터 일관성 관리: 캐시와 데이터베이스 간의 일관성을 유지하는 것이 복잡할 수 있다.
  3. 추가적인 복잡성: 캐시 관리 로직이 애플리케이션에 추가되어 복잡성이 증가할 수 있다.

사용 예시

동시성 처리와 오류 복구를 포함한 버전

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class RobustCacheAside {
    constructor(cacheClient, dbClient) {
        this.cache = cacheClient;
        this.db = dbClient;
        this.lockTTL = 10; // 10  타임아웃
    }

    async getData(key) {
        try {
            // 1. 캐시 확인
            const cachedValue = await this.cache.get(key);
            if (cachedValue) {
                return JSON.parse(cachedValue);
            }

            // 2. 캐시 미스: 분산  획득
            const lockKey = `lock:${key}`;
            const locked = await this.acquireLock(lockKey);

            if (!locked) {
                // 다른 프로세스가 데이터를 가져오는 
                await this.sleep(100);
                return this.getData(key);
            }

            try {
                // 3. DB에서 데이터 조회
                const data = await this.db.query(key);
                
                if (data) {
                    // 4. 캐시에 데이터 저장
                    await this.cache.setex(
                        key,
                        3600,
                        JSON.stringify(data)
                    );
                }

                return data;
            } finally {
                // 5.  해제
                await this.releaseLock(lockKey);
            }

        } catch (error) {
            // 6. 오류 처리
            console.error(`Cache-aside error: ${error}`);
            // 캐시 오류  DB에서 직접 조회
            return await this.db.query(key);
        }
    }

    async acquireLock(lockKey) {
        return await this.cache.setnx(lockKey, 1);
    }

    async releaseLock(lockKey) {
        await this.cache.del(lockKey);
    }
}

최적화 팁

  1. 캐시 워밍: 시스템 시작 시 자주 사용되는 데이터를 미리 캐시에 로드한다.
  2. 캐시 갱신 전략: Write-through나 Write-behind 등의 전략을 고려하여 데이터 일관성을 유지한다.
  3. 분산 캐시: 대규모 시스템에서는 Redis Cluster 등을 활용하여 캐시를 분산 구성한다.

참고 및 출처