글로벌(멀티 리전) 서버 구축 #04 - 복제본 없이 elasticache 사용하기(JavaScript Proxy 사용하기)

‍이준성·2023년 2월 20일
0

elasticache의 일반적인 구축

elasticache의 경우, 글로벌 레플리카를 지원하는데, 이 역시도 mysql/dynamodb와 크게 다르지 않은 과정으로 사용할 수 있다.
다만, 나의 경우에는 글로벌 레플리카를 사용하지 않았다.

내가 진행한 방식의 elasticache 구축

서로 동기화되지 않는, 별개의 elasticache를 사용했다. 기존에는 리전 A에 elasticache A가 올라가 있었다. 글로벌 배포를 진행하면서 리전 B에 새로 elasticache B를 올리게 되었다.

문제 상황

이 때, 키값에 따라서 다른 레디스에 접근할 필요가 생겼다. redis의 어떤 값들은 단순한 캐싱으로, 리전 간 공유할 필요가 없다. 이러한 것들은 각자의 리전에 있는 redis에 적재하면 된다.

하지만 어떤 값들은 공유할 필요가 있어서, 하나의 redis를 공유했야만 했다.

비록 위의 상황이 흔히 발생할 수 있는 문제상황은 아니지만, 어쨌든 하나의 해결책을 제시해보려한다.

두 개의 커넥션, 문제점

우선, 키값에 따라 다른 레디스에 접근해야하므로 두 개의 레디스를 만들어야한다.

const Redis = require('ioredis');
const cacheRedis = new Redis(cacheConfig);
const shareRedis = new Redis(shareConfig);

만약 일일히 키값에 따라서 커넥션을 선택적으로 사용한다고 해보자. 그러면 기존에 redis를 사용하는 모든 부분들을 찾아서 키값에 따라 일일히 바꾸어줘야할 것이다.
이는 너무 번거롭고, 하나의 값을 서로 다른 redis에 연결하는 등, 실수의 가능성도 있다.
이 때 쓸 수 있는 것이 바로 proxy이다.

Proxy를 사용해보자

JavaScript의 Proxy 기능이다.
해당 기능을 공부하기 위해

를 참고했다.

해당 포스팅은 글로벌 구축에 대한 포스팅이므로, proxy에 대한 내용은 서술하지 않는다. 필요할 경우 별도의 포스팅을 작성하여 서술한다.

proxy는 대상 객체에 대한 접근을 가로챌 수 있다. 그리고, 그 가로채는 방법은 handler가 정의한다.
우리는 redis.get, redis.set 등의 일반적인 redis 명령어 호출 함수를 가로채서, 새로운 함수를 리턴하기 위해 사용할 것이다.

const isCachedRedisKey = (key) => {
  // key가 cacheRedis를 위한 key라면 true 리턴
  // key가 shareRedis를 위한 key라면 false 리턴
  // 확인할 수 없다면 not boolean value 리턴
}
const redisProxy = new Proxy(shareRedis, {
  get(originalRedis, p) {
    if (typeof originalRedis[p] !== 'function') return originalRedis[p];
    return (key, ...args) => {
      const flag = isCacheRedisKey(key);

      if (typeof flag !== 'boolean') {
        const err = new Error(`Unknown Redis Key ${key}`);
        console.error(JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err))));
      }

      const redis = flag ? cacheRedis : originalRedis;
      return redis[p](key, ...args);
    };
  },
});

우선, isCachedRedisKey는 잘 정의되어있다고 가정하자.

그렇다면, 앞으로 redisProxy를 사용한 redis 접근은 다음과 같이 동작한다.

  1. proxy에 의해서, redisProxy에 접근할 때마다 직접 입력해준 get 핸들러가 동작한다.
  2. get 핸들러가 동작한다.
    2-1. 만약 redis[p]가 함수가 아니라면, redisProxy는 별다른 동작을 하지 않고 끝난다.
    2-2. 만약 함수라면, 원본 redis[p]와는 조금 다른 새로운 함수를 리턴한다.
  3. 새로운 함수는 실행 될 때 key값을 받아서 캐시키인지 아닌지 판별한다.
    3-1. 만약 캐시키라면, cacheRedis의 p라는 이름을 가진 함수를 실행시킨다.
    3-2. 그렇지 않다면, origianlRedis의 p라는 이름을 가진 함수를 실행시킨다. 여기서 originalRedis는 Proxy의 첫 번째 인자이다.

위 과정을 거쳐서, 키값에 따라 적절하게 cacheRedis와 shareRedis 사이에서 라우팅해준다.
따라서, 외부에서 사용할 때에는, 그냥 하나의 redis만 있다고 생각하고 redis를 사용할 수 있게된다.

장점

  • 일일히 redis를 호출하는 부분을 변경할 필요가 없다.
    • 일일히 변경할 필요가 없으니, 단순 반복 작업 중에 특정 부분에서 실수할 가능성도 없다.
  • 만약 어떠한 값이 cacheRedis에서 shareRedis로 옮겨서 저장되어야하거나, 반대의 경우가 있더라도 변경이 용이하다.
    • 어디로 라우팅될지 판단하는 isCacheRedisKey 함수만 바꿔주면 되기 때문이다.
  • 외부 사용 시에, redis 연결이 여러개인지 하나인지 알 필요 없이 작업 가능하다.
    • 하나의 추상화 계층이 추가된 셈이다.

단점

  • 매번 proxy를 타고, 함수를 만들고, key값에 따라 분기처리가 들어가기에 약간의 성능 저하가 있다.
  • 같이 일하는 사람 중 proxy를 아는 사람이 별로 없을 수 있다.
    • 다른 사람들 입장에서 처음에는 난해하게 느껴질 수 있다.

정리

Proxy를 통해서 객체로의 접근을 가로챌 수 있다.
이를 통해서 코드를 깔끔하게 유지하면서 여러 개의 redis 연결을 사용할 수 있다.
다만 성능 이슈 및 최초 코드 파악 시의 난해함이 문제가 될 수 있다.

0개의 댓글