Redis Cache - Write Back(Write Behind) 전

2023. 9. 9. 00:20· Spring, Spring Boot/Cache - Redis
목차
  1. 개요
  2. 장점과 단점
  3. 실행 흐름
  4. 코드
  5. 3. Service 파일 생성 
  6. 실행 결과
  7. 마무리

Spring Boot와 Redis

 

 

개요

이번에는 Write Back 혹은 Write Behind 라고도 불리는 지연 쓰기 전략에 대해 적어보겠습니다. 

Write Back은 Write Through 처럼 데이터의 변경(수정, 삭제 등)이 일어날 때 캐시를 최신화, 일관성을 유지하는 전략입니다. 

 

다만 다른 점은 Write Through는 캐시 데이터를 변경하면서 동시에 DB에 변경 사항을 반영하는데 Write Back 전략은 변경 사항을 모았다가 한 번에 DB에 반영합니다. 

 

이 방법은 높은 트래픽 상황에서 응답 시간을 최적화 하는데 유용하지만 데이터 손실 위험이 있습니다. 

 

 

장점과 단점

장점

  1. 빠른 응답 시간: 쓰기 요청에 대한 응답은 캐시 업데이트 후 즉시 반환되므로, 사용자에게는 빠른 응답 시간이 제공됩니다.
  2. 배치 처리의 효율성: 여러 쓰기 작업을 배치로 묶어서 한 번에 처리하므로, I/O 작업의 오버헤드가 줄어듭니다.

 

단점

  1. 데이터 일관성의 위험: 캐시와 백엔드 저장소 사이에 일시적인 데이터 불일치가 발생할 수 있습니다. 이는 배치 업데이트가 이루어질 때까지 지속됩니다.
  2. 데이터 손실의 위험: 시스템 장애가 발생하면, 아직 백엔드 저장소에 기록되지 않은 캐시의 데이터가 손실될 수 있습니다.

 

 

하지만 단점을 잘 매꾸면 높은 트래픽 상황에서도 응답 시간을 최적화하는 성능 향상을 이룰 수 있습니다. 

이 Write Back 전략은 주로 바로 반영되서 보여주지 않아도 되는 좋아요수, 조회수 같은 서비스에 주로 사용된다고 합니다. 

(Youtube의 조회수는 들어갈 때 마다 바로 반영되는게 아니라 일정 시간 이후 반영됩니다.)

 

 

 

실행 흐름

  1. 데이터 변경 요청(수정, 삭제)이 들어온다.
  2. Redis에 Caching된 데이터만 변경하고 DB에 반영하지 않는다. 대신 지연 정보를 큐에 담아 Redis에 저장한다.
  3. 원하는 시간대에 Redis에 저장된 큐에서 하나씩 꺼내면 작업을 처리한다.

 

 

 

코드

 

build.gradle에 의존성 추가

// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// 직렬화, 역직렬화
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'

// cache
implementation 'org.springframework.boot:spring-boot-starter-cache'

 

 

application.yml에 Redis 연결 정보 추가

spring:
  cache:
    type: redis
  redis:
    host: localhost
    port: 6379
    password: ssafy

 

 

3. Service 파일 생성 

 

Operation 클래스 생성

// Redis에 지연 정보를 저장할 Operation 클래스
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Operation {
    private String type;
    private JsonNode data;
}

 

Redis에 지연 정보를 저장할 형태의 Operation 클래스 입니다.

Redis에 이 Operation의 List를 넣어 변경 사항 정보, 즉 지연 정보들을 저장합니다.

 

JsonNode

이 때 data로 JsonNode를 사용했습니다.

Jackson 라이브러리는 Java 객체와 JSON 간의 직렬화, 역직렬화를 지원하는 라이브러리 입니다.

JsonNode는 Jackson 라이브러리에서 제공하는 JSON 트리 모델의 핵심 클래스 입니다.

 

저의 로직은 수정, 삭제일 때 data에 들어가는 자료형이 다릅니다.

수정일 떄는 Product, 삭제일 때는 Integer id가 들어갑니다.

JsonNode를 사용함으로 객체의 트리 구조로 변환해 쉽게 다룰 수 있고 직렬화, 역직렬화를 쉽게 할 수 있었습니다.

 

 

 

 

Operation 직렬화, 역직렬화

// Operation 직렬화
  public String operationSerialize(Operation operation) {
      try {
          return objectMapper.writeValueAsString(operation);
      } catch (Exception e) {
          throw new RuntimeException("직렬화 실패", e);
      }
  }

  // Operation 역직렬화
  public Operation operationDeserialize(String serializedData) {
      try {
          return objectMapper.readValue(serializedData, Operation.class);
      } catch (Exception e) {
          throw new RuntimeException("역직렬화 실패", e);
      }
  }

Operation의 직렬화를 통해 Redis 에 저장하고 역직렬화를 통해 Redis에 가져온 값을 다시 Java의 객체로 돌립니다.

 

 

 

Product 수정 메소드 

// 상품 수정
public ProductResponseDto updateProductWriteBack(ProductUpdateDto dto) {

    // MySQL Product 조회
    Product product = productRepository.findById(dto.getId()).orElseThrow(
            ()-> new RuntimeException("Product 조회 실패")
    );

    // Product 수정 전 detach -> DB에 반영 안됨
    entityManager.detach(product);

    // Product 수정
    product.setName(dto.getName());
    product.setDescription(dto.getDescription());
    product.setPrice(dto.getPrice());


    // 캐시 수정
    redisTemplate.opsForValue().set("product" + product.getId(), product);

    // 지연 정보 추가
    JsonNode productJsonNode = objectMapper.valueToTree(product);
    Operation operation = new Operation("UPDATE", productJsonNode);
    String serializedData = operationSerialize(operation);
    stringRedisTemplate.opsForList().rightPush("productOperationList", serializedData);



    // 응답
    ProductResponseDto result = productMapper.productToProductResponseDto(product);

    return result;
}

 

entitiyManager.detach(product);

수정 요청이 들어오면 product 객체를 불러와서 수정 사항들을 반영한 후 Redis에 저장합니다.

이 때, 저는 JPA를 사용하고 있기에 set 메소드로 객체의 필드를 수정하면 자동으로 DB에 반영됩니다.

이것을 방지하고자 entityManager.detach(); 를 사용해서 객체의 영속성을 포기하여 자동 반영을 막았습니다.

 

변경된 product를 redis에 업데이트하고 productOperationList에 지연 정보를 넣었습니다.

 

 

 

 

Product 삭제

// 상품 삭제
public void deleteProductWriteBack(Integer id) {
    // Redis에서 Product 삭제
    redisTemplate.delete("product" + id);

    // 지연 정보 추가
    JsonNode idJsonNode = objectMapper.valueToTree(id);
    Operation operation = new Operation("DELETE", idJsonNode);
    String serializedData = operationSerialize(operation);
    stringRedisTemplate.opsForList().rightPush("productOperationList", serializedData);
}

redis에 캐싱된 데이터를 삭제하고 productOperationList에 삭제 지연 정보를 넣었습니다.

 

 

 

 

productOperationList 실행

 

@EnableScheduling

변경된 정보들을 저장한 productOperationList 5분 마다 실행하게 했습니다.

이를 위해 Spring Boot Application의 main 메소드에 @EnableScheduling 어노테이션을 추가했습니다.

@SpringBootApplication
@EnableScheduling
public class ProductApplication {

	public static void main(String[] args) {
		SpringApplication.run(ProductApplication.class, args);
	}

}

 

 

productOperationList 안에 있는 Operation을 실행시키는 메소드

// 지연 정보 처리 메소드 - 5분 마다 실행
@Scheduled(fixedRate = 300000)
public void processOperationList() throws JsonProcessingException {
    while (true) {
        String serializedData = stringRedisTemplate.opsForList().leftPop("productOperationList");

        if(serializedData == null) {
            break;
        }

        Operation operation = operationDeserialize(serializedData);

        if (operation.getType().equals("UPDATE")) {
            Product data = objectMapper.treeToValue(operation.getData(), Product.class);
            Product product = productRepository.findById(data.getId()).orElse(null);
            product.setName(data.getName());
            product.setDescription(data.getDescription());
            product.setPrice(data.getPrice());
            productRepository.save(product);
        }
        else if (operation.getType().equals("DELETE")) {
            Integer data =  operation.getData().asInt();
            productRepository.deleteById(data);
        }
    }
}

 

@Scheduled(fixedRate = 300000)

이 어노테이션을 통해 메소드의 스케줄링을 추가할 수 있습니다.

시간은 5 minutes * 60 seconds * 1000 milliseconds 로 계산합니다.

 

이 메소드는 productOperationList에서 Operation을 하나씩 가져와서 DB에 반영합니다.

이 때 Operation이 null이면 비어있다는 뜻이므로 동작을 멈추게 했습니다.

 

 

 

실행 결과

 

RedisInsight 를 통해 데이터 변경과 삭제 지연 정보가 저장된 것을 확인할 수 있습니다.

Redis에 저장된 지연 정보들

 

 

5분 후

지연 정보들이 처리된 모습

 

 

 

 

마무리

이번에는 wrtie back 을 구현해 보았습니다.

이번 Redis를 하면서 직렬화, 역직렬화에 애를 먹는 거 같습니다.

또한 Operation의 data가 수정, 삭제일 때가 다르기 때문에 이 부분을 같은 List에 넣기 위해 고민을 많이 했습니다.

다행히 동작이 되게 만들었지만 보수해야할 부분이 많습니다.

Operation을 실행하면서 도중에 실패하면 처리할 코드, Redis가 내려가서 List가 사라졌을 때 복구하는 방법 등 안정성을 더 높여야 합니다. 

이 부분도 학습하여 적용해보도록 하겠습니다.

 

감사합니다.

 

  1. 개요
  2. 장점과 단점
  3. 실행 흐름
  4. 코드
  5. 3. Service 파일 생성 
  6. 실행 결과
  7. 마무리
'Spring, Spring Boot/Cache - Redis' 카테고리의 다른 글
  • Redis Cache - Write Through 전략
  • Redis Cahce - Read Through 전략
  • Redis와 Spring Boot 연결하기 - RedisTemplate, Cache-Aside 전략
  • Redis와 Spring Boot 연결하기 - RedisHash
너지살
너지살
너지살
너지살개발자
너지살
전체
오늘
어제
  • 분류 전체보기 (375)
    • 잡식 (2)
      • 티스토리 (2)
    • 개발 일지 (0)
      • OMS 프로젝트 (4)
      • 우테코 6기 프리코스 (1)
    • Git (2)
    • JAVA (15)
      • Java 공부 (6)
      • 자료구조 (4)
      • 도움되는 메모 (4)
    • DevOps (18)
      • AWS (6)
      • Docker (2)
      • Jenkins (1)
      • Nginx (1)
      • Kafka (6)
      • RabbitMQ (2)
    • Spring, Spring Boot (16)
      • Test Code (1)
      • AOP (2)
      • Batch (3)
      • Cache - Redis (5)
      • Cloud Config - 설정 파일 관리 (3)
      • 성능 측정 (1)
      • 예외 처리 (1)
    • BackEnd (1)
      • Spring 공부 (1)
      • Thymeleaft (0)
    • DB (17)
      • JPA (2)
      • DB 공부 (3)
      • DB 포스팅 (4)
      • DB 답장 (1)
      • MySQL (2)
      • Redis (5)
      • MongoDB (0)
    • CS (8)
      • Spring (4)
      • DataBase (3)
      • Java (1)
    • Algorithm (203)
      • 알고리즘 개념 (5)
      • 정렬 알고리즘 (11)
      • 프로그래머스 문제풀이 (18)
      • 백준 문제풀이 (165)
      • 소프티어 문제풀이 (3)
      • 알고리즘 시험 정리 (1)
    • SQL (0)
      • 문법 (1)
      • 프로그래머스 문제풀이 (52)
      • 리트코드 문제풀이 (19)
    • IT (1)
      • IT 공부 (1)
    • 정리 (10)
      • 질문 정리 (10)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 데이터베이스
  • 외판원 순회 문제
  • Union-Find
  • dynamiceprogramming
  • 최소 스패닝 트리
  • 질문 정리
  • 설정
  • 최소 신장 트리
  • Spring Batch
  • 우선수위큐
  • 깊이/너비 우선탐색
  • Bitmast
  • Spring Boot
  • Test code
  • 경로표현식
  • cache
  • Java
  • 다음 순열 찾기
  • 유니온파인드
  • dynamic programing
  • redis
  • Algorithm
  • DFS
  • 병렬 처리
  • 그래프 탐색
  • two pointer
  • 다이나믹 프로그래밍
  • 투포인트
  • 다이나믹프로그래밍
  • 알고리즘
  • 두 포인터
  • 부분탐색
  • DP
  • 자료구조
  • db
  • MST
  • 분리 집합
  • docker
  • Next permutation
  • 크루스칼 알고리즘
  • Sorting algorithm
  • 비트마스킹
  • Spring Boot Redis 연결
  • 투 포인터
  • Java 정리
  • JPA
  • 그래프 이론
  • 소프티어
  • 백준
  • git

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
너지살
Redis Cache - Write Back(Write Behind) 전
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.