본문 바로가기

Java/Spring

(9)
Redis 캐싱 적용과 TTL, 캐시 전략 패턴 활용기 개요프로젝트가 1차 개발을 마치고 2차 개발에 들어가던 시점, 회원 수가 200명에서 최대 2만 명까지 늘어날 수 있다는 요구사항을 받았다. 이에 따라 사용자 경험 개선과 성능 강화를 위해 Redis 도입을 검토했지만, 고객사는 추가 비용을 우려해 쉽게 동의하지 않았다. 당시 서버 환경은 AWS ECS Fargate 기반이었고, 가장 단순한 방법은 ElastiCache를 사용하는 것이었다. 하지만 비용이 만만치 않았기 때문에, 결국 비용을 줄이기 위해 EC2에 Redis를 직접 설치하는 방법을 택했다. 이 과정에서 초기 과정이라 모니터링을 따로 붙이진 못했다.하지만 고객사의 상황에 맞춰 성능 개선과 비용 절감을 동시에 가져갈 수 있었다. 오늘은 이렇게 적용된 Redis가 어떻게 구성되어 있고, 언제 사용..
open-in-view=false 환경에서 LazyInitializationException → 403 에러 전파 사례 개요오늘은 실제 프로젝트를 프런트 분들과 함께 진행하면서, 생각지도 못했던(?) 어이없는 에러를 공유하려고 한다.서비스 운영 중 특정 조회 화면에 들어가면 자꾸 로그인이 풀려 튕겨나가는 현상이 발생했다.퇴근 직전에 AWS CloudWatch 로그를 확인해 보니, 원인은 다름 아닌 LazyInitializationException.그런데 더 황당했던 건 이 예외가 프런트 쪽에서 “로그아웃”으로 처리되어 버린 것이었다.원인을 찾아보니, 함께 개발하던 분이 @Transactional 하나를 누락했을 뿐인데… 사용자 입장에선 로그아웃된 것처럼 보였던 거다.결국 @Transactional 추가만으로 해결했지만, 이 일을 계기로 “open-in-view=false일 때 왜 이런 일이 생길까?”라는 궁금증이 생겼다...
Spring Boot 다중 서버 환경에서 SchedulerLock으로 스케줄러 동시 실행 제어 개요회사에서 프로젝트를 진행하던 중, ECS 기반 다중 서버 환경에서 동일한 스케줄러 작업이 동시에 실행되는 문제를 마주치게 된 적이 있었다. 각 스케줄러는 사용자 인스턴스2개 , 관리자 인스턴스 2개가 올라가 있었고 서버 인스턴스가 늘어나면서 같은 작업이 여러 번 실행되는 상황이 발생했다. 그 결과 불필요한 DB 접근, 자원 낭비, 성능 저하 같은 부작용이 우려되었다. 그때는 이 문제를 막기 위해 ShedLock + RDB 조합을 사용했다.DB 테이블에 락 정보를 저장해 두고, 한 번에 한 서버만 해당 스케줄러를 실행하도록 막는 방식이었다. 문제 자체는 해결됐지만, 지금 생각해 보면 아쉬움이 크다. 락 제어만을 위해 DB에 주기적으로 쓰기/삭제를 반복시키는 건 DB의 본래 역할(트랜잭션 데이터 관리)과..
Kafka Listener 예외 반복 실행되는 이유 그리고 해결 요즘 경매 기능 개발 중 Kafka로 입찰 메시지를 처리하고 있는데, 이상한 상황을 하나 마주했다.프론트에서 입찰 금액을 현재가 이하로 보내면, 백엔드에서는 당연히 예외를 던지게 돼 있다. 문제는 이때 Kafka 컨슈머가 해당 메시지를 계속해서 반복 실행하면서 로그가 쌓이고, 결국 장애처럼 느껴질 정도로 실행하면서 터져버렸다.로그를 보면 아래처럼 계속 같은 메시지를 읽고 실패하고, 다시 읽고 실패하는 로그가 찍힌다. 분명 예외가 발생했는데도, Kafka가 계속 같은 메시지를 소비하고 있었다.Kafka 리스너가 예외를 던졌는데, 이걸 “정상 처리되지 않았다”고 인식하고 계속 재시도하고 있었던 거다.Kafka는 메시지를 읽고, 예외 없이 끝나야 성공적으로 처리했다고 판단한다.그런데 RuntimeExcepti..
전략 패턴에서 Object vs 제네릭, 왜 나는 제네릭을 선택했을까? 최근 개인 프로젝트(실시간 채팅 시스템)를 진행하면서 전략 패턴을 적용하는 과정에서 재미있는 고민을 하게 되었다.바로 공통 인터페이스의 파라미터를 Object로 받을지, 아니면 제네릭으로 추상화할지에 대한 문제였다. 처음에는 단순한 문제처럼 보였지만, 실제로 구조를 짜면서 생각보다 깊은 고민을 하게 되었고, 결국 제네릭 기반 설계를 선택했다. 이 글에서는 그 과정을 회고해보고자 한다.문제 배경내가 만든 프로젝트에는 여러 종류의 채팅방 생성 방식이 필요했다.AuctionChatRoomCreator : Long productId 기반 경매 채팅방 생성ProductChatRoomCreator : ProductChatRequest 기반 일반 상품 채팅방 생성GroupChatRoomCreator : GroupCh..
Spring에서 확장성을 고려한 채팅방 생성 로직 전략 패턴 적용 개선 전: 단일 메서드로 직접 처리/** 1:1 상품 채팅방 생성 or 조회 */@Transactionalpublic ChatRoomResponse createOrGetProductChat(Long productId, User buyer) { ChatRoom room = chatRoomRepository .findByTypeAndProductIdAndBuyer(ChatRoomType.DIRECT, productId, buyer.getId()) .orElseGet(() -> { Product product = productService.findById(productId); ChatRoom newRoom = ChatRoom.builder(..
Spring STOMP + Kafka 구조에서 Kafka만 사용하는 설정 변경 후 404 에러 개인 프로젝트에서 기존 STOMP 기반 WebSocket 채팅을 제거하고, Kafka 기반 실시간 채팅 시스템으로 마이그레이션을 진행했습니다. 이 과정에서 WebSocket 연결 시도 시 404 오류가 발생하는 문제가 있어서 저와 같이 30분 시간 낭비 안하시길 바라면서 포스팅 합니다. 변경 배경: 왜 STOMP에서 Kafka로?기존 STOMP 방식은 Spring에서 빠르게 WebSocket 채팅을 구현할 수 있었지만, 다음과 같은 한계가 있었습니다.단일 WAS 한정 메시지 브로커(SimpleBroker) 사용서버 인스턴스가 여러 대일 때 세션 동기화 어려움대량 트래픽 처리 시 확장성 부족메시지 전달 경로가 김 (WebSocket -> SimpleBroker -> Conusumer -> 다시 WebSoc..
Spring Boot에서 application.yml 설정 분리하기 Spring Boot로 프로젝트를 진행하다 보면 개발(dev) 환경과 운영(prod) 환경의 설정을 분리해야 할 때가 많습니다.예를 들어, 로컬 DB는 MySQL, 운영은 PostgreSQL 또는 로컬에서만 테스트 데이터 초기화 등을 하고 싶을 때죠. Spring Boot는 기본적으로 application.yml 외에 application-{profile}.yml 형태로 환경별 설정을 지원합니다.이번 글에서는 application-dev.yml 설정과 profile 관리 방법을 정리합니다. application.yml: 공통 설정application-dev.yml: 개발 환경 설정application-prod.yml: 운영 환경 설정application.yml (공통)spring.profiles.acti..