레이블이 Backend인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Backend인 게시물을 표시합니다. 모든 게시물 표시

수요일

Redis maxmemory 설정 — OOM 에러와 eviction policy 완벽 가이드

증상 — 에러 메시지

OOM command not allowed when used memory > 'maxmemory'.
ERR command not allowed when used memory > 'maxmemory'

캐시 서버로 Redis를 운영하다 보면 어느 날 갑자기 쓰기 명령이 전부 실패하는 상황을 만납니다. OOM(Out of Memory) 에러는 Redis가 설정된 최대 메모리 한도에 도달했을 때 발생하며, 기본 정책(noeviction)에서는 새 데이터를 추가할 수 없습니다.

원인

Redis는 기본적으로 maxmemory 제한이 없어서 서버 물리 메모리를 모두 소모할 수 있습니다. 운영 환경에서 명시적으로 제한을 걸면, 그 한도를 넘는 순간 maxmemory-policy 설정에 따라 동작이 결정됩니다. 기본값인 noeviction은 메모리가 가득 차면 쓰기 에러를 반환합니다.

해결방법

1. maxmemory 및 정책 설정

# redis.conf 파일 수정
maxmemory 2gb
maxmemory-policy allkeys-lru

# 또는 런타임에 즉시 적용 (재시작 불필요)
redis-cli CONFIG SET maxmemory 2gb
redis-cli CONFIG SET maxmemory-policy allkeys-lru

# 설정 확인
redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy

2. Eviction Policy 종류와 용도

# noeviction (기본): 메모리 초과 시 쓰기 에러 반환
# allkeys-lru  : 모든 키 중 LRU 순서로 삭제 — 순수 캐시에 권장
# volatile-lru : TTL 있는 키 중 LRU 순서로 삭제 — 혼합 환경
# allkeys-lfu  : 사용 빈도 낮은 키 삭제 — Redis 4.0+
# volatile-ttl : TTL 짧은 키부터 삭제 — 세션 저장소
# allkeys-random: 무작위 삭제

3. 메모리 사용량 모니터링

redis-cli INFO memory
# 주요 항목: used_memory_human, maxmemory_human, mem_fragmentation_ratio

redis-cli DBSIZE                  # 전체 키 개수
redis-cli MEMORY USAGE mykey      # 특정 키 메모리 크기(bytes)

4. Spring Boot에서 Redis 메모리 효율화

@Bean
public RedisCacheConfiguration cacheConfiguration() {
    return RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(30))   // TTL 필수 지정
        .disableCachingNullValues()          // null 캐싱 방지
        .serializeValuesWith(
            RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer())
        );
}

정책 선택 가이드

사용 목적권장 정책이유
순수 캐시 (세션 없음)allkeys-lru모든 키를 대상으로 LRU 교체, 가장 일반적
캐시 + 영구 데이터 혼합volatile-lruTTL 있는 캐시 키만 삭제, 영구 키 보존
세션 저장소volatile-ttl만료 임박 세션 먼저 삭제
쓰기 보장 필수noeviction메모리 부족 시 에러 반환, 데이터 손실 없음
접근 빈도 편중 심함allkeys-lfu자주 쓰는 키 보존, Redis 4.0 이상

핵심 요약

  • maxmemorymaxmemory-policy는 항상 함께 설정한다.
  • 캐시 전용 서버라면 allkeys-lru가 기본 선택지다.
  • Spring Boot에서는 entryTtl로 TTL을 반드시 지정해 키가 무한정 쌓이지 않게 한다.
  • mem_fragmentation_ratio가 1.5 이상이면 MEMORY PURGE 또는 재시작을 고려한다.

Spring Security CORS 완벽 해결 — @CrossOrigin 설정해도 403이 사라지지 않는 이유

🔍 검색 키워드: Spring Security CORS 에러, Spring Boot CORS 설정, Access-Control-Allow-Origin 403, CORS preflight 차단, Spring Security WebMvcConfigurer CORS

증상 — 에러 메시지

프론트엔드에서 API를 호출하면 다음과 같은 CORS 에러가 콘솔에 출력됩니다:

Access to XMLHttpRequest at 'https://api.example.com/api/v1/users' from origin
'https://app.example.com' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

특히 @CrossOrigin 어노테이션이나 WebMvcConfigurer로 CORS를 설정했는데도 에러가 사라지지 않는 경우, Spring Security가 원인일 가능성이 높습니다.

원인

Spring Boot 프로젝트에 Spring Security가 추가되면 CORS 처리 순서가 달라집니다. Spring Security의 필터 체인이 Spring MVC의 CORS 처리보다 먼저 실행되기 때문에, @CrossOrigin 이나 WebMvcConfigurer를 통한 CORS 설정이 Security 레이어에서 막혀버립니다.

Preflight 요청(OPTIONS)은 인증 헤더가 없기 때문에 Spring Security가 401/403으로 거부하고, 브라우저는 CORS 에러로 인식합니다. 즉, CORS 설정이 잘못된 게 아니라 Security 필터가 CORS 처리 전에 먼저 요청을 차단하는 것이 핵심 원인입니다.

해결방법

방법 1: SecurityFilterChain에 CORS 직접 등록 (Spring Boot 3.x 권장)

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                http
                            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                                        .csrf(csrf -> csrf.disable())
                                                    .authorizeHttpRequests(auth -> auth
                                                                    .requestMatchers("/api/public/**").permitAll()
                                                                                    .anyRequest().authenticated()
                                                                                                );
                                                                                                        return http.build();
                                                                                                            }
                                                                                                            
                                                                                                                @Bean
                                                                                                                    public CorsConfigurationSource corsConfigurationSource() {
                                                                                                                            CorsConfiguration config = new CorsConfiguration();
                                                                                                                                    config.setAllowedOrigins(List.of("https://app.example.com"));
                                                                                                                                            config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
                                                                                                                                                    config.setAllowedHeaders(List.of("*"));
                                                                                                                                                            config.setAllowCredentials(true);
                                                                                                                                                                    config.setMaxAge(3600L);
                                                                                                                                                                    
                                                                                                                                                                            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
                                                                                                                                                                                    source.registerCorsConfiguration("/**", config);
                                                                                                                                                                                            return source;
                                                                                                                                                                                                }
                                                                                                                                                                                                }

방법 2: WebMvcConfigurer와 Security 통합

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
        public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                            .allowedOrigins("https://app.example.com")
                                        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                                                    .allowedHeaders("*")
                                                                .allowCredentials(true)
                                                                            .maxAge(3600);
                                                                                }
                                                                                }
                                                                                
                                                                                // Security 설정에서 반드시 cors() 활성화
                                                                                @Bean
                                                                                public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                                                                                    http
                                                                                            .cors(Customizer.withDefaults())  // WebMvcConfigurer 설정 사용
                                                                                                    .csrf(csrf -> csrf.disable())
                                                                                                            // ...
                                                                                                                return http.build();
                                                                                                                }

자주 하는 실수 3가지

1. Security 설정에 .cors() 없이 WebMvcConfigurer만 설정 — Security 필터가 OPTIONS 요청을 차단함

2. allowedOrigins에 trailing slash 포함 — "https://app.example.com/" 처럼 끝에 슬래시 금지

3. allowedMethods에 OPTIONS 누락 — Preflight 통과를 위해 OPTIONS 반드시 포함

원인별 정리표

증상원인해결
OPTIONS 요청 401/403Security가 Preflight 차단cors() 활성화 + permitAll
@CrossOrigin 무시됨Security 필터 우선 실행SecurityFilterChain에 cors() 추가
쿠키/세션 전송 안 됨allowCredentials 미설정setAllowCredentials(true)
특정 헤더만 에러allowedHeaders 미포함setAllowedHeaders(List.of("*"))
로컬에서만 동작origin 목록에 배포 URL 없음allowedOrigins에 배포 도메인 추가

핵심 정리

Spring Security + CORS 문제의 90%는 SecurityFilterChain에 .cors()를 추가하지 않아서 발생합니다. @CrossOrigin이나 WebMvcConfigurer만으로는 Security 레이어를 통과할 수 없습니다. Spring Boot 3.x 환경에서는 corsConfigurationSource Bean을 별도로 등록하는 방법 1이 가장 명확하고 안정적입니다.