서드 파티 데이터 캐시 불일치 이슈

boms·2024년 7월 23일
0

인턴

목록 보기
3/3

Issue

이미 승인된 알림톡 템플릿은 수정 불가한데 클라이언트측에서 수정하여 오류가 발생했다.

Problem

슈어엠 api로 조회한 미승인 템플릿을 redis에 저장했는데 얼마 지나지 않아 승인처리되어 슈어엠 db와 캐시간의 데이터 불일치가 발생한 것이다.

현재 개발 방식은

1) 병원이 소유중인 모든 알림톡 template에 대한 summary를 슈어엠 api로 요청한다

2) 특정 template의 상세 정보를 가져오는 경우 cache를 사용한다. 특정 template을 가져올 때 cache를 확인하고 없는 경우 슈어엠 api로 fetch하고 cache에 저장한다.

ttl이 24시간이기 때문에 만약 슈어엠내에서 데이터가 수정된다면 cache내 데이터와 불일치하게 된다

Solution

슈어엠측에서 언제든지 데이터 상태를 변경할 수 있기 때문에 ttl만 수정하는 것은 의미가 없다. 따라서 캐시 무효화를 통해 슈어엠과 redis의 싱크를 맞출 수 있도록 했다.

클라이언트에게 특정 template을 가져오는 화면은 모든 알림톡 템플릿 서머리 조회 화면 이후에 노출된다. 따라서 서머리를 가져왔을 때 최신화된 상탤르 cache 데이터의 상태와 비교하고 다르면 delete하여 무효화하도록 했다.

    // 가져온 템플릿 필터링
    const filteredTemplates = templates.filter(
      (e) =>
        (partialName === undefined || e.name.includes(partialName)) &&
        e.dormant === false &&
        e.inspectionStatus !== InfoTalkTemplateInspectionStatus.DELETED,
    );
    
    // 캐시 데이터와 비교
    await Promise.all(
      filteredTemplates.map(async (template) => {
        if (template.inspectionStatus !== 'REJECTED') {
          const cacheTemplate =
            await this.infoTalkTemplateRedisRepository.get<InfoTalkTemplate>(
              hospitalIds[0],
              template.templateCode,
            );
          if (
            cacheTemplate &&
            cacheTemplate.inspectionStatus !== template.inspectionStatus
          ) {
            await this.infoTalkTemplateRedisRepository.delete(
              hospitalIds[0],
              template.templateCode,
            );
          }
        }
      }),
    );

병원 보유 템플릿이 평균 10~15개 정도 밖에 안되기 때문에 Promise.all을 사용하여 병렬적으로 cache 데이터를 get()하여 inspectionStatus를 비교하고 delete()하도록 했다. in memory 캐시 작업을 병렬적으로 처리했더니 실행시간에 큰 영향을 주지않았다.

  • Call Stack에 들어가는 코드:

    • filteredTemplates.map은 Call Stack에 들어갑니다.
    • async (template) => {...} 함수도 Call Stack에 들어갑니다.
    • if (template.inspectionStatus !== 'REJECTED') { ... } 조건문과 그 내부의 코드도 Call Stack에 들어갑니다.
  • 비동기 작업 (getdelete):

    • this.infoTalkTemplateRedisRepository.getthis.infoTalkTemplateRedisRepository.delete는 비동기 함수 호출입니다.
    • 이들은 Call Stack에 잠시 들어갔다가, 비동기 작업으로 전환되면서 Call Stack에서 빠져나와 Event Loop와 libuv를 통해 처리됩니다.
    • await 키워드로 인해, 이 작업들이 완료될 때까지 해당 함수는 일시 중단됩니다. Event Loop는 다른 작업을 계속 처리할 수 있습니다.
  • filteredTemplates.map과 내부의 동기 코드는 Call Stack에서 실행됩니다.

  • this.infoTalkTemplateRedisRepository.getthis.infoTalkTemplateRedisRepository.delete는 비동기 함수로, Call Stack에서 빠져나와 Event Loop와 libuv에 의해 처리됩니다.

  • 비동기 작업이 완료되면, Event Loop가 이를 다시 Call Stack으로 가져와 후속 작업을 처리합니다.

Takeaway

  • 모든 템플릿을 가져올 때는 데이터 일관성을 위해 캐시를 사용 못하지만 특정 템플릿을 가져올 때는 캐시를 사용하여 실행시간을 단축할 수 있었다.
  • 슈어엠 api fetch시 캐시 무효화를 하여, 캐시 일관성을 유지할 수 있어 안정적이다.
  • 데이터 변화를 전혀 제어하지 못하는 서드 파티 api 데이터를 캐싱하는 것은 어렵지만 상황에 맞춰 대응하면 충분히 가능하다
profile
2023.08.21~

0개의 댓글