Backend/Redis

프로젝트에서 레디스를 사용하며 한 고민들

olsohee 2024. 5. 24. 15:04

RDB 

레디스는 인메모리 기반의 데이터베이스이다. 따라서 서버가 꺼지면 데이터는 모두 휘발된다. 만약 레디스의 데이터를 영구 저장하고 싶으면 레디스가 제공하는 AOF와 RDB 기능을 사용하면 된다. 두 기능에 대한 설명은 https://olsohee.tistory.com/115 이 글에 나와있다. 간략히 두 방식을 비교하면 데이터 손실이 없어야 하면 AOF를, 어느정도의 데이터 손실이 발생해도 괜찮으면 RDB를 사용하면 될 것 같다.

 

나의 경우, 레디스에 있는 인기글과 사용자 정보를 백업해야 하는 상황인데, 이는 어느정도의 데이터 손실이 발생해도 괜찮기 때문에 그 대신 적은 데이터 크기와 빠른 복구의 이점을 얻을 수 있는 RDB를 사용했다.

 

RDB를 사용하면 일정 시간마다 스냅샷을 찍어 데이터 백업이 진행된다. 그리고 이 데이터들은 dump.rdb라는 파일에 저장된다. 그리고 레디스 서버를 껐다가 다시 키면 레디스가 자동으로 dump.rdb 파일을 읽어서 백업된 데이터들을 메모리에 올린다.

maxmemory + maxmemory-policy

그런데 이때 "그럼 백업 데이터가 모두 메모리에 올라가면 메모리 사용량이 과도하게 많아지는 것이 아닌가?"라는 생각을 했다. 그리고 그 해결책으로 maxmemory 설정을 떠올렸다. maxmemory를 설정하면 레디스가 설정된 값만큼의 메모리 양만 사용할 수 있다. 따라서 그 이상의 데이터가 백업되어 있다면, maxmemory-policy에 따라 동작한다.

 

maxmemory-policy는 레디스가 설정된 maxmemory에 도달했을 때 데이터를 삭제하는 정책이다.

  • noeviction(기본 값): 데이터를 삭제하지 않는다. 따라서 maxmemory에 도달하면 쓰기 작업에 실패하고 오류를 반환한다.
  • volatile-lru: ttl이 설정된 키 중에서 LRU 알고리즘을 기반으로 삭제(LRU: 가장 오랫동안 사용(참조)되지 않은 값)
  • volatile-ttl: ttl이 설정된 키 중에서 ttl 만료 시간이 가장 가까운 것을 삭제
  • volatile-random: ttl이 설정된 키 중에서 랜덤으로 삭제
  • allkeys-lru: 모든 키 중에서 LRU 알고리즘을 기반으로 삭제
  • allkeys-random: 모든 키 중에서 랜덤으로 삭제

설정을 하지 않았을 때 기본 값은 noeviction이다. config get maxmemory-policy 명령어를 통해 확인할 수 있다.

설정

처음에는 redis.conf 파일을 생성해서 설정하려고 했는데 왜인지 redis.conf 파일이 제대로 적용되지 않아서.. 수동으로 명령어를 쳐서 설정해줬다.

  • config set save "3600 1"
  • config set maxmemory 1gb
  • config set maxmemory-policy allkeys-lru

그리고 config get 명령어를 통해 정상적으로 반영된 것을 확인할 수 있다.

레디스의 복제 + 센티널과 서킷 브레이커 패턴을 통한 레디스 장애 대응

장애 대응 전

게시글 등록 후 조회시 정상적으로 조회된다.

 

도커에서 동작중인 레디스를 중단시키면

 

레디스와의 연결이 끊겼고, 다시 게시글 조회 요청을 보내면 500 Interner Server Error가 발생한다.

rsilience4j을 통한 서킷 브레이커 패턴 적용

프로젝트에서 각 마이크로 서비스는 레디스를 사용한다. 만약 레디스에 장애가 생기면 레디스와 관련된 모든 동작이 정상적으로 처리되지 않는다. 따라서 이러한 문제를 해결하기 위해 서킷 브레이커 패턴을 적용했다.

import funfit.community.rabbitMq.dto.MicroServiceName;
import funfit.community.rabbitMq.dto.RequestUserByEmail;
import funfit.community.rabbitMq.dto.User;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {

    private final RedisTemplate<String, User> redisTemplate;
    private final RabbitMqService rabbitMqService;

    @CircuitBreaker(name = "redis", fallbackMethod = "fallback")
    public User getUserDto(String email) {
        User user = redisTemplate.opsForValue().get(email); // 레디스에서 사용자명 조회
        if (user != null) {
            return user;
        }
        return rabbitMqService.requestUserByEmail(new RequestUserByEmail(email, MicroServiceName.COMMUNITY));
    }

    private User fallback(String email, Throwable e) {
        log.error("레디스 장애로 인한 fallback 메소드 호출, {}", e.getMessage());
        return rabbitMqService.requestUserByEmailWithoutRedis(new RequestUserByEmail(email, MicroServiceName.COMMUNITY)); // RabbitMQ를 통해 사용자명 받아옴
    }
}

 

  • 게시글 조회시 사용자명을 레디스를 통해 조회한다. 
  • 그러나 레디스 장애로 레디스 조회 실패 시 서킷 브레이커가 작동하며 fallback 메소드가 실행된다.
  • fallback 메소드는 RabbitMQ로 사용자를 관리하는 마이크로 서비스로부터 사용자명을 받아온다.

레디스 복제 + 센티널 적용

게시글 조회 기능은 레디스 장애 시 RabbitMQ를 통해 사용자명을 받아오면 된다. 그러나 인기글 조회나 게시글 조회 시 조회 수를 카운팅하는 기능 등 레디스를 사용하는 또 다른 기능들은 레디스 장애 시 앞선 방법으로 해결할 수 없다. 따라서 레디스 복제 환경을 구축하여, 레디스 장애 시 슬레이브를 마스터로 승격하는 방법을 채택했다.

 

그런데 레디스 장애 시 개발자가 직접 기존 슬레이브를 마스터로 변경하고, 애플리케이션이 새로 마스터로 승격된 슬레이브를 바라보도록 변경하는 것은 매우 번거롭다. 따라서 마스터를 감시하여 자동 페일오버를 진행하는 센티널을 적용했다.

 

레디스 복제와 센티널을 적용한 자세한 내용은 다음 글을 확인하면 된다. ➡️ https://olsohee.tistory.com/183

 

센티널 로컬과 도커에 적용해보기

로컬에 적용이전 글에서 레디스의 복제와 센티널에 대해 알아봤다. 이번 글에서는 이들을 로컬 환경과 도커 환경에서 직접 적용해보자. 우선 로컬에 적용해보자. 레디스 서버는 다음과 같다.

olsohee.tistory.com