springboot 20

동시성 제어에 캐시와 분산락 Redisson 을 도입한 이유

= 상황 =이전 게시글에서 유효성 검증과 예약을 DB에 저장하는 예약 추가 로직을 RabbitMQ 를 도입하며 이를 아래와 같이 분리하게 되었다.유효성 검증 및 메세지 큐 전송 (Sender)메세지 큐 수신 후 DB에 저장 (Listener)( 이해를 위해 임의로 두 로직을 Sender, Listener 로 명하였음 )이러한 구조 변경을 통해 예약 순서 보장 , 처리량 증가, 데이터 유실 방지라는 이점을 얻었다.하지만 이후 서버가 증설되며 동시성 문제와, RabbitMQ의 메세지 처리 방식으로 인한 문제가 발생하였다.= 문제 =해당 서비스는 동일 시간대의 예약 가능 좌석이 제한되어 있기에, 메세지를 큐에 전송하기 전 DB 에서 해당 시간대에 예약이 가능한지 확인하는 과정을 거쳤다.그러나 이 과정에서 아래..

기술적 고민 2025.04.09

예약 관리 서비스에 RabbitMQ 를 도입하게 된 이유

현재 진행 중인 프로젝트에서는 하나의 매장에서 여러 고객을 관리하고, 고객 별 예약을 관리 해야만 한다. 예약은 등록 되어 있는 고객만이 가능하고, 동일 시간에 최대 예약 가능 갯수가 제한 되어 있다.이외에도 여러 부가적인 기능이 존재하지만, 가장 중요하게 바라봐야할 포인트는고객 등록예약 등록이라고 생각을 했다.한 명의 고객에 대한 등록은 단 한 번만 진행을 하며, 해당 데이터가 유실이 되면 예약을 포함한 다른 서비스를 이용할 수 없었고, 결국 동일한 고객의 정보를 재입력 하는 과정이 부정적인 사용자 경험으로 이어질 것이라 예상됐다.예약 등록은 데이터 유실 방지는 물론, 최대 갯수가 제한되어 있는 만큼 예약 순서를 보장해야 했고 잘못된 데이터가 입력될 경우 서비스에도 장애가 발생할 것이라 예상이 되었다...

기술적 고민 2025.03.26

무한스크롤에서 Offset 방식의 문제와 Keyset Pagination 사용 이유

프로젝트를 진행하던 중, 기존 페이징 처리가 되어있던 부분이 무한스크롤 방식으로 변경이 되었다.때문에 사용자에게 페이지 번호를 전달 받을 필요가 없었고 Jpa의 Pageable을 사용할 이유가 없었기에 다른 방법을 모색하게 되었다.Jpa Pageable 의 가장 큰 문제는 페이지 번호가 증가함에 따라 반환 시간이 증가한다는 점이다.이는 Pageable이 Offset 방식으로 동작하기 때문인데, 페이지 번호가 증가할 수록 앞선 페이지의 데이터를 스캔하고 버려야하기에 일정한 반환 시간을 보장할 수 없게 된다.아래는 Mysql 8.0 환경에 저장된 1100만 개의 데이터를 활용해 페이지 별 반환 시간을 테스트 해본 결과이다.데이터는 페이지당 10개 씩 읽어오도록 하였다.// 테스트 엔티티@Entity@Gett..

기술적 고민 2025.02.20

Content-Type 'application/octet-stream' is not supported 원인 및 해결 방법

[2025-02-03 22:48:44.371] [http-nio-8080-exec-8] [8706cf15-ff91-49be-98c7-236cb49d64c6] ERROR [.error.GlobalExceptionHandler.handleException:25 ] - Content-Type 'application/octet-stream' is not supported[2025-02-03 22:48:44.372] [http-nio-8080-exec-8] [8706cf15-ff91-49be-98c7-236cb49d64c6] ERROR [.error.GlobalExceptionHandler.handleException:26 ] - Exception : org.springframework.web.HttpMediaT..

Springboot 2025.02.05

Exception Handler ( 커스텀 익셉션 ) 사용 이유 및 작성 방법

코드를 작성하다보면 에러를 발견하는 것은 너무나 당연한 수순이다.이러한 에러를 발견했을 때 이를 해결하기 위해 로그를 확인하고, 문제가 되는 부분을 추적해 해결해야만 한다.하지만, 로그를 한 번이라도 확인해봤으면 알겠지만 이러한 로그는 길이가 짧지 않아 이를 읽고 파악하는데 생각보다 많은 시간이 소요되게 된다.또한, 이러한 로그는 보안상 local 환경에서만 확인할 수 있도록 하여 별도의 처리가 없는 한 API를 가져다 쓰는 프론트 쪽에서는 오류가 발생해도 어떤 것이 문제인지 파악할 수 없는 경우가 빈번히 발생하게 된다.예시를 들어보겠다.아래 코드는 Integer a 를 전달 받고, 해당 값에 +3 을 더한 값을 반환하는 코드이다. @PostMapping("/post") public int po..

Springboot 2024.11.25

메타데이터를 이용한 객체 값 자동 생성 ( DatabaseMetaData )

테스트 코드 작성 중 객체 내부의 값을 랜덤으로 생성해주는 과정이 반복되어 이를 해결하고자 FixtureMonkey를 사용하게 되었다.다만 FixtureMonkey를 사용하기 위해서는 엔티티 내부에 어노테이션으로 @Size 등의 제약사항을 작성해주어야 했는데, 작성 중인 프로젝트에서는 Mybatis를 사용해 코드를 작성하는 만큼 @Size와 같은 어노테이션을 오직 FixtureMonkey 만을 위해 엔티티에 작성해야할 이유가 없다고 느껴졌다.또한 테스트 코드를 작성을 위해 기존 코드에 영향을 주는 것은 좋지 않다 판단하였기에, Mybatis에서 별도의 설정 없이 랜덤한 값을 생성할 수 있도록 직접 코드를 작성하기로 하였다.코드를 작성하기 전 중요하게 생각한 포인트는 아래와 같았다.기존 코드에 영향이 가지..

기술적 고민 2024.10.08

fixtureMonkey, JakartaValidationPlugin 제약 조건 설정

공식 문서https://naver.github.io/fixture-monkey/v1-0-0-kor/docs/plugins/jakarta-validation-plugin/features/fixtureMonkey를 사용하여 실제 테스트 코드를 작성하다보면 무조건 한 번씩 발생하는 오류 중 하나가 필드별 제약 조건이다.분명 닉네임 값은 필수 값이며, 최대 20자 까지 밖에 입력이 되지 않아야하지만, fixtureMonkey는 이를 무시하고 null 값을 반환하거나 최대 자리수를 초과하는 값을 생성하곤 한다.이러한 문제는 JakartaValidationPlugin 를 사용하여 해결할 수 있다.사용 가능한 어노테이션은 해당 글 하단에 작성해두었다. 공식 문서에서 가져온 내용이니 시간이 여유롭다면 공식 문서를 참고..

Springboot 2024.10.06

fixtureMonkey 객체 값 자동 생성 / springboot 테스트 코드

공식 문서https://naver.github.io/fixture-monkey/v1-0-0-kor/테스트 코드를 작성하다보면 객체, 보통 엔티티에 무작위한 값을 넣고 이를 테스트해야하는 경우가 많다.다만 이러한 과정을 위해 시간을 소요하고 동일한 과정을 반복하는 것이 번거롭기에 이를 간단하게 해결 하기 위해 나온 것이 Fixture Monkey이다.사용법은 정말 간단하다.의존성// GradletestRuntimeOnly("org.junit.platform:junit-platform-launcher:{version}")testImplementation("com.navercorp.fixturemonkey:fixture-monkey-starter:1.0.23")// Maven com.navercorp.f..

Springboot 2024.10.04

스프링부트 Repository TestCode ( 테스트 코드 / mybatis )

service , controller 등에서는 mock 을 이용해 로직이 잘 동작하는지를 주목적으로 테스트 코드를 작성 하지만, Repository Test는 실제로 데이터가 DB에 잘 이동하는지 확인하기 위해서 하는 목적이 큰 만큼 mock을 사용하는 것이 아닌 실제 DB를 이용하는 것을 추천한다.실제 DB는 사용 중인 개발/운영 서버의 DB에 테스트 후에 Transaction을 하는 방법도 있겠지만, 데이터가 많거나 실제 운영 중인 서버라면 속도 지연의 원인이 될 수 있기에 테스트 DB는 따로 설정하는 것이 좋다.이를 위해 휘발성 DB인 H2를 이용하도록 하겠다.testRuntimeOnly 'com.h2database:h2'테스트를 진행할 테이블은 Members 테이블이며, 아래와 같은 구조를 가지고..

Springboot 2024.09.25

스프링부트 Repository 커스텀 (분리)

프로젝트의 규모가 크지 않거나, 시작 단계일 경우 하나의 Repository에서 모든 쿼리를 관리하는 경우를 종종 볼 수 있을 것이다.public interface MemberRepository extends JpaRepository { Optional findByEmailAndNickname(String email, String nickname); List findByRole(Role role); ... 등등}위의 코드는 아직 정의된 메소드가 두 가지 밖에 없기에 잘 와닿지 않겠지만, 관련 쿼리가 몇 십,백개가 넘어가거나현재는 Jpa만 사용하지만 jpsql 혹은 querydsl 등으로 직접 쿼리문을 작성하는 경우 하나에 파일에서 관리하게 되면 코드가 복잡해져 유지보수에 있어 어려움을 느낄 ..

Springboot 2024.09.23