Q. 성능 최적화를 위한 캐싱을 할 때, 캐싱 에러대비는 어떻게 해야 좋을까요?

기본 아바타 racefl 2019. 02. 01. 조회수 482


안녕하세요, 최근에 좋은 글을 보며 스스로에 대한 로직을 점검하고 수정하는 개발자입니다.

캐싱을 트래픽이 급증하기 전에 미리 해두는 것이 좋다는 글을 보다가 문득 궁금한 점이 생겼습니다.

로직은 다음과 같습니다.

특정 사용자가 회원가입을 하면, 필요한 정보를 레디스와 같은 곳에 캐싱합니다.

이후에 로그인을 할 때는 RDB에 접근하지 않고 레디스로 접근해서 캐싱된 값을 통해 인증을 하게 되구요!

이에 대해 들었던 의문점 및 궁금한 점은 아래와 같습니다.

  1. 캐싱을 할 때, 변경 가능한 정보 ( 이메일, 전화번호 ) 등은 캐싱하지 않는것이 좋을까요?
    개인적인 생각으로는 정보 변경하면 기존 캐싱된 정보를 엎어 써버리는 것도 방법이 될거 같은데
    조언을 구하고 싶습니다!

  2. 레디스를 쓴다고 가정할 때, 레디스에서 읽기 작업이 실패한 경우 다시 RDB로 접근 해서 정보를 가져오는 방식으로 로직을 구현하는게 서비스 제공 관점에서 좋을거 같은데 다른 대안이 있는지 궁금합니다.
    레디스에 대해 깊게 알지 못하지만, 레디스는 휘발성이 있다고 알고 있어서요. 또 일정 데이터가 쌓이면 자동으로 데이터가 삭제되는 것으로 알고 있어 질문 드립니다.

짧은 지식과 정보로 질문을 드려, 답변에 어려움이 있으실 것 같아 죄송합니다.
참고한 글은 아래와 같습니다.

즐거운 명절 보내세요!

  • URL

https://medium.com/aha-official/%EC%95%84%ED%95%98-rest-api-%EC%84%9C%EB%B2%84-%EA%B0%9C%EB%B0%9C-10-c09764e116f9

공유하고 보상받기 ♥︎
댓글 0

1개의 답변이 있습니다.

질문자 & 큐레이터 채택
이희승 답변자인증
2019. 02. 01 100%의 채택

안녕하세요 racefl 님,

아하 CTO 이희승입니다.

먼저 제 강의를 봐주시고 여기에다가 질문까지 해주셔서 너무 감사드립니다.

REST API 의 캐싱 문제 때문에 많은 고민을 하시고 계시죠?

사실 Cache 를 잘 설계해야 하는 이유가 DB 데이터가 이원화되어 저장되기 때문에 둘의 sync 를 잘 맞출 수 있는 방법을 강구해야 하고

이 방법이 하나의 실수도 생겨선 안된다는 점 때문이겠지요.

  1. 캐싱을 할 때, 변경 가능한 정보 ( 이메일, 전화번호 ) 등은 캐싱하지 않는것이 좋을까요?
    개인적인 생각으로는 정보 변경하면 기존 캐싱된 정보를 엎어 써버리는 것도 방법이 될거 같은데
    조언을 구하고 싶습니다!

-> Redis 를 사용하시는 경우에는 I/O 의 성능이 굉장히 빠르기 update 사항이 생기는 대로 redis 에 바로바로 적용하셔도 별 문제는 없습니다. 실제로 실시간으로 많은 데이터를 읽고 써야 하는 프로그램에서는 1차 DB 로 redis 를 활용한다고 알고 있습니다.

또한 제 강좌의 소스코드를 보시면 아시겠지만 실제 모델의 구현체인 user.model.js 에서 afterSave() 훅을 잡아서 redis 에 적용하는 것을 확인하실 수 있으실 겁니다.

// src/models/user.model.js

// 생성, 변경 후 캐시에 저장
User.afterSave((user, options) => userCache.store(user))

이런식으로 모델 구현체에 hook 을 잡으시게 되면 매번 소스코드에서 이 모델이 update 되었는지, 만약 update 되었다면 redis 에 객체가 있는지, 있으면 update 해야 하는지를 고민하실 필요가 없게 됩니다.

  1. 레디스를 쓴다고 가정할 때, 레디스에서 읽기 작업이 실패한 경우 다시 RDB로 접근 해서 정보를 가져오는 방식으로 로직을 구현하는게 서비스 제공 관점에서 좋을거 같은데 다른 대안이 있는지 궁금합니다.
    레디스에 대해 깊게 알지 못하지만, 레디스는 휘발성이 있다고 알고 있어서요. 또 일정 데이터가 쌓이면 자동으로 데이터가 삭제되는 것으로 알고 있어 질문 드립니다.

-> 레디스는 휘발성 영역에서 작동하는 in memory database 이어서 휘발성 db 가 맞습니다만, production level 에서 피치 못할 사정에 의해 수백 MB ~ 수 GB 에 달하는 캐시를 날리지 않기 위하여 snapshot 이라고 하는 persistance 모듈을 지원합니다. 따라서 redis 가 휘발성이라고 걱정하시지는 않으셔도 되며, 실제 활발한 서비스들은 캐시를 여러 cluster 로 관리하여 그 중 하나의 서버에 문제가 생긴다고 하더라도 장애가 생기지 않게끔 운영하고 있습니다.

또한, redis 실패 시 RDB 에 쿼리를 날리는 부분은 다음 강좌에서 보완하여 소스코드를 좀 더 견고하게 작성하려고 했습니다만, 말씀 나온김에 대충의 예제를 보여드리도록 하겠습니다.

// user.cache.js
import userRepo from '../repositories/user.repository'

const find = async (uuid) => {
  if (uuid) {
    let user
    
    try {
      user = await client.hgetAsync('users:uuid', `${uuid}`)

      if (!user) { // redis 에 값 자체가 없을 경우
        user = await userRepo.find(uuid)
        store(user)
      }
    } catch (e) {
      // logger.error(e) 등을 통해 로깅 진행 
      // db 에 직접 접근
      user = await userRepo.find(uuid)
    }

    return JSON.parse(user)
  }

  return null
}

이런식으로 대응할 수 있습니다.

어떻게 하면 조금 더 견고한 캐시 설계를 할 수 있는지, 그리고 이쁘지 않은 코드들을 어떻게 잘 이쁘게 랩핑해서 재사용성을 올릴 수 있는지는 금일 작성할 11편의 강좌에서 더 자세히 다뤄볼 수 있도록 하겠습니다.

오늘도 좋은 하루 보내시고 즐거운 코딩하세요 감사합니다^^

이희승 드림.

댓글 0