캐시(Cache)는 무엇이며, 어떻게 활용되나요?

김상욱·2024년 12월 22일
0

캐시(Cache)는 무엇이며, 어떻게 활용되나요?

What?

캐시(Cache)는 데이터 접근 속도를 높이기 위해 자주 사용되는 데이터를 임시로 저장해 두는 메모리 공간을 의미. 즉, 필요한 데이터를 빠르게 가져올 수 있도록 미리 저장해 두는 저장소.

Why?

  • 메인 데이터베이스나 외부 API에서 데이터를 가져오는 것보다 메모리에서 데이터를 가져오는 것이 훨씬 빠릅니다.
  • 데이터베이스나 외부 서비스에 대한 요청 횟수를 줄여 서버의 부하를 낮출 수 있습니다.
  • 사용자에게 더 빠른 응답을 사용하여 사용자 경험을 향상시킬 수 있습니다.

HOW?

  1. Spring의 캐싱 추상화 사용
  • Spring은 캐시를 쉽게 적용할 수 있는 캐싱 추상화(Caching Abstraction)을 제공합니다. 이를 통해 애플리케이션 코드에 크게 영향을 주지 않고 캐시를 도입할 수 있습니다.
  • @Cacheable : 메서드의 결과를 캐시에 저장합니다. 같은 파라미터로 호출될 경우 캐시된 값을 반환합니다.
@Service
public class UserService {

    @Cacheable("users")
    public User getUserById(Long id) {
        // 데이터베이스에서 사용자 조회
    }
}
  • @CacheEvict : 캐시에서 특정 데이터를 제거합니다.
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
    // 사용자 삭제 로직
}
  • @CachePut : 메서드 실행 결과를 캐시에 업데이트합니다.
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
    // 사용자 업데이트 로직
}
  1. 캐시 구현체 선택
    Spring 캐싱 추상화는 여러 캐시 구현체와 함께 사용할 수 있습니다. 대표적인 캐시 구현체는 다음과 같습니다.
  • Ehcache : 자바 기반의 캐시 라이브러리로, 간단한 설정으로 사용할 수 있습니다.
  • Redis : 인메모리 데이터 구조 저장소로, 분산 캐시로 많이 사용됩니다.
  • Caffeine : 고성능의 자바 기반 캐시 라이브러리로, 로컬 캐싱에 적합합니다.
    Redis 캐시 사용 예시
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379
@Service
public class ProductService {

    @Cacheable("products")
    public Product getProductById(Long id) {
        // 데이터베이스에서 제품 조회
    }
}
  1. 캐싱 전략
    캐시를 효과적으로 사용하기 위해 다양한 전략을 적용할 수 있습니다.
  • LRU(Least Recently Used) : 가장 오랫동안 사용되지 않은 데이터를 제거.
  • TTL(Time To Live) : 캐시에 저장된 데이터의 유효 시간을 설정하여 자동으로 만료되게 합니다.
  • Write-Through/Write-Back : 데이터가 변경될 때 캐시와 데이터베이스를 동시에 업데이트하는 방식입니다.

Caution

  • 캐시된 데이터와 실제 데이터 간의 일관성을 유지해야 합니다. 이를 위해 캐시 무효화(Cache Invalidation) 전략이 필요.
  • 캐시는 메모리를 사용하므로, 적절한 크기와 관리가 필요.
  • 캐시를 도입하면 시스템의 복잡성이 증가할 수 있으므로, 필요한 경우에만 도입하는 것이 좋습니다.

Example

@Service
public class ArticleService {
    
    @Autowired
    private ArticleRepository articleRepository;

    @Cacheable(value = "articles", key = "#id")
    public Article getArticleById(Long id) {
        simulateSlowService(); // 데이터베이스 조회 시뮬레이션
        return articleRepository.findById(id).orElse(null);
    }

    private void simulateSlowService() {
        try {
            Thread.sleep(3000L); // 3초 지연
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }
    }
}

취업 준비 중인 신입 Java/Spring 백엔드 개발자 분께서 캐시(Cache)를 실습하면서 이해도를 높일 수 있는 다양한 실습 과제를 소개해드리겠습니다. 이 실습 과제들은 캐시의 기본 개념부터 실제 프로젝트에 적용하는 방법까지 단계별로 학습할 수 있도록 구성되었습니다.

실습 과제 1: 기본적인 @Cacheable 사용하기

목표

Spring의 @Cacheable 어노테이션을 사용하여 메서드 결과를 캐싱해보고, 캐시의 동작을 이해합니다.

단계별 가이드

  1. Spring Boot 프로젝트 생성

    • Spring Initializr를 사용하여 새로운 Spring Boot 프로젝트를 생성합니다.
    • 필요한 의존성: Spring Web, Spring Boot Starter Cache, Spring Boot DevTools.
  2. 캐시 설정 활성화

    @SpringBootApplication
    @EnableCaching
    public class CacheDemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(CacheDemoApplication.class, args);
        }
    }
  3. 간단한 서비스 생성

    @Service
    public class UserService {
        
        @Cacheable("users")
        public String getUserById(Long id) {
            simulateSlowService();
            return "User" + id;
        }
    
        private void simulateSlowService() {
            try {
                Thread.sleep(3000L); // 3초 지연
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
        }
    }
  4. 컨트롤러 생성

    @RestController
    @RequestMapping("/users")
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @GetMapping("/{id}")
        public String getUser(@PathVariable Long id) {
            return userService.getUserById(id);
        }
    }
  5. 테스트

    • 애플리케이션을 실행하고 /users/1 엔드포인트를 호출합니다.
    • 첫 호출 시 3초 지연이 발생하지만, 두 번째 호출부터는 즉시 응답이 옵니다.
    • 로그를 확인하여 캐시가 작동하는지 확인합니다.

학습 포인트

  • @Cacheable의 기본 동작 이해
  • 캐시의 효과를 간단히 체험

실습 과제 2: 캐시 삭제와 업데이트

목표

@CacheEvict@CachePut 어노테이션을 사용하여 캐시를 삭제하고 업데이트하는 방법을 학습합니다.

단계별 가이드

  1. UserService 수정

    @Service
    public class UserService {
        
        @Cacheable("users")
        public String getUserById(Long id) {
            simulateSlowService();
            return "User" + id;
        }
    
        @CacheEvict(value = "users", key = "#id")
        public void deleteUser(Long id) {
            // 사용자 삭제 로직 (예: 데이터베이스에서 삭제)
            System.out.println("User " + id + " deleted");
        }
    
        @CachePut(value = "users", key = "#id")
        public String updateUser(Long id, String name) {
            // 사용자 업데이트 로직 (예: 데이터베이스에서 업데이트)
            return name;
        }
    
        private void simulateSlowService() {
            try {
                Thread.sleep(3000L); // 3초 지연
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
        }
    }
  2. 컨트롤러 수정

    @RestController
    @RequestMapping("/users")
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @GetMapping("/{id}")
        public String getUser(@PathVariable Long id) {
            return userService.getUserById(id);
        }
    
        @DeleteMapping("/{id}")
        public void deleteUser(@PathVariable Long id) {
            userService.deleteUser(id);
        }
    
        @PutMapping("/{id}")
        public String updateUser(@PathVariable Long id, @RequestParam String name) {
            return userService.updateUser(id, name);
        }
    }
  3. 테스트

    • /users/1을 호출하여 캐시에 저장된 값을 확인합니다.
    • /users/1DELETE 요청하여 캐시에서 삭제합니다.
    • 다시 /users/1을 호출하여 캐시가 삭제되었는지 확인합니다.
    • /users/1PUT 요청하여 캐시를 업데이트하고, 다시 GET 요청 시 업데이트된 값을 확인합니다.

학습 포인트

  • @CacheEvict를 사용한 캐시 삭제
  • @CachePut을 사용한 캐시 업데이트

실습 과제 3: Redis 캐시 도입하기

목표

Redis를 캐시 저장소로 사용하여 분산 캐시 환경을 구축하고 활용합니다.

단계별 가이드

  1. Redis 설치 및 실행

    • 로컬에 Redis를 설치하거나 Docker를 사용하여 Redis 컨테이너를 실행합니다.
      docker run -d -p 6379:6379 --name redis-cache redis
  2. 의존성 추가
    pom.xml에 Redis 관련 의존성을 추가합니다.

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
  3. 설정 파일 수정
    application.properties에 Redis 설정을 추가합니다.

    spring.cache.type=redis
    spring.redis.host=localhost
    spring.redis.port=6379
  4. UserService 및 UserController는 이전과 동일하게 유지

  5. 테스트

    • 애플리케이션을 실행하고 /users/1을 호출하여 Redis에 캐시가 저장되는지 확인합니다.
    • Redis 클라이언트를 사용하여 캐시된 데이터를 직접 확인할 수 있습니다.
      docker exec -it redis-cache redis-cli
      > keys *
      > get users::1

학습 포인트

  • Redis를 캐시 저장소로 사용하는 방법
  • 분산 캐시 환경 이해

실습 과제 4: 캐싱 전략 적용하기

목표

TTL(Time To Live)과 LRU(Least Recently Used) 전략을 적용하여 캐시의 만료와 갱신을 관리합니다.

단계별 가이드

  1. Caffeine 캐시 라이브러리 추가

    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
        <version>3.1.2</version>
    </dependency>
  2. 캐시 설정 클래스 작성

    @Configuration
    public class CacheConfig {
    
        @Bean
        public CaffeineCacheManager cacheManager() {
            CaffeineCacheManager cacheManager = new CaffeineCacheManager("users");
            cacheManager.setCaffeine(Caffeine.newBuilder()
                    .expireAfterWrite(10, TimeUnit.MINUTES)
                    .maximumSize(100));
            return cacheManager;
        }
    }
  3. UserService와 UserController는 동일하게 유지

  4. 테스트

    • 캐시가 설정한 TTL에 따라 만료되는지 확인합니다.
    • 캐시가 최대 크기를 초과할 때 LRU 정책이 적용되는지 확인합니다.

학습 포인트

  • 캐시 만료 정책 설정
  • 캐시 용량 관리 및 LRU 전략 이해

실습 과제 5: 캐시 성능 비교 및 분석

목표

캐시를 사용하지 않은 경우와 사용한 경우의 성능을 비교하여 캐시의 효과를 분석합니다.

단계별 가이드

  1. UserService에 캐시 없는 메서드 추가

    @Service
    public class UserService {
        
        @Cacheable("users")
        public String getUserById(Long id) {
            simulateSlowService();
            return "User" + id;
        }
    
        public String getUserByIdNoCache(Long id) {
            simulateSlowService();
            return "User" + id;
        }
    
        private void simulateSlowService() {
            try {
                Thread.sleep(3000L); // 3초 지연
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
        }
    }
  2. 컨트롤러에 캐시 없는 엔드포인트 추가

    @RestController
    @RequestMapping("/users")
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @GetMapping("/{id}")
        public String getUser(@PathVariable Long id) {
            return userService.getUserById(id);
        }
    
        @GetMapping("/nocache/{id}")
        public String getUserNoCache(@PathVariable Long id) {
            return userService.getUserByIdNoCache(id);
        }
    }
  3. 성능 테스트

    • /users/1/users/nocache/1을 각각 여러 번 호출하여 응답 시간을 비교합니다.
    • 예를 들어, 처음 호출 시 두 엔드포인트 모두 3초 지연이 발생하지만, 이후 캐시된 /users/1은 즉시 응답되고 /users/nocache/1은 매번 3초 지연됩니다.
  4. 결과 분석

    • 캐시 사용 시 응답 시간이 현저히 줄어드는 것을 확인하고, 캐시의 장점을 체감합니다.

학습 포인트

  • 캐시의 성능 향상 효과 직접 체험
  • 캐시 도입의 필요성 이해

추가 실습 과제: 실전 프로젝트에 캐시 적용하기

목표

작은 규모의 실제 애플리케이션에 캐시를 적용하여 실무에서의 캐시 활용 경험을 쌓습니다.

단계별 가이드

  1. 간단한 블로그 애플리케이션 개발

    • 기능: 게시글 조회, 작성, 수정, 삭제
    • 기술 스택: Spring Boot, Spring Data JPA, H2 Database
  2. 게시글 조회에 캐시 적용

    • @Cacheable을 사용하여 자주 조회되는 게시글을 캐싱
    • @CacheEvict@CachePut을 사용하여 게시글 수정 및 삭제 시 캐시를 업데이트
  3. 캐시 성능 모니터링

    • Actuator를 사용하여 캐시 통계를 모니터링
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
    • application.properties에 Actuator 설정 추가
      management.endpoints.web.exposure.include=cache
  4. 캐시 미세 조정

    • TTL, 최대 크기 등 캐시 설정을 조정하여 최적의 성능을 도출
  5. 문서화 및 배포

    • 애플리케이션의 캐시 적용 과정을 문서화하고, 로컬 또는 클라우드 환경에 배포하여 실제 운영 환경에서의 캐시 동작을 확인

학습 포인트

  • 실제 프로젝트에 캐시를 통합하는 경험
  • 캐시 설정 및 최적화 방법 학습
  • 캐시 관련 문제 해결 능력 향상

마무리

위의 실습 과제를 통해 캐시의 기본 개념부터 실제 프로젝트에 적용하는 방법까지 단계별로 학습할 수 있습니다. 각 실습 과제를 진행하면서 다음과 같은 점들을 유념하세요.

  • 문서화: 각 실습의 과정과 결과를 잘 기록해두면, 면접 시에 자신있게 설명할 수 있습니다.
  • 문제 해결: 실습 중 발생하는 문제를 스스로 해결해보는 경험이 중요합니다. 구글링, 공식 문서 참고 등을 통해 문제를 해결해보세요.
  • 코드 정리: 깨끗하고 유지보수 가능한 코드를 작성하는 습관을 기르세요.

이러한 실습을 통해 캐시의 중요성을 깊이 이해하고, 실제 업무에서도 효과적으로 활용할 수 있는 능력을 키우시길 바랍니다. 취업 준비에 많은 도움이 되길 바라며, 화이팅입니다!

0개의 댓글