본문 바로가기

728x90

전체 글 135

MariaDB ANALYZE FORMAT=JSON 실행 계획 읽기: 추정 rows와 실제 r_rows 비교

MariaDB ANALYZE FORMAT=JSON 실행 계획 읽기: 추정 rows와 실제 r_rows 비교인덱스가 있다는 사실만으로 쿼리가 빠르지는 않습니다. EXPLAIN에 type=ref가 보여도 예상보다 많은 행을 반복해서 읽거나, 조인 순서의 첫 추정이 틀리면 전체 실행량이 커집니다. 이때 필요한 질문은 “인덱스를 탔는가?”가 아니라 옵티마이저가 예상한 행 수와 실제로 읽은 행 수가 얼마나 다른가입니다.MariaDB의 EXPLAIN은 쿼리를 실행하지 않고 계획을 보여 줍니다. ANALYZE는 쿼리를 실제로 실행해 r_rows, r_filtered 같은 실행 통계를 추가합니다. 더 자세한 트리와 시간 정보가 필요하면 ANALYZE FORMAT=JSON을 사용합니다.EXPLAIN과 ANALYZE의 역..

Spring 트랜잭션 전파: REQUIRED·REQUIRES_NEW·NESTED 차이와 커넥션 풀 함정

Spring 트랜잭션 전파: REQUIRED·REQUIRES_NEW·NESTED 차이와 커넥션 풀 함정주문 저장과 감사 로그 저장을 한 요청에서 처리한다고 가정해 보겠습니다. 주문이 실패하면 둘 다 취소되어야 할까요? 감사 로그만은 남아야 할까요? 이 질문에 답하지 않은 채 @Transactional(propagation = ...)을 붙이면 데이터 정합성이나 커넥션 풀 고갈 문제가 생깁니다.트랜잭션 전파는 “메서드마다 새 트랜잭션을 만들 것인가”가 아니라 호출 경계의 논리 트랜잭션을 실제 DB 트랜잭션에 어떻게 매핑할 것인가를 정하는 규칙입니다.세 전파 방식의 차이전파 방식물리 트랜잭션내부 실패 영향추가 커넥션대표 용도REQUIRED기존 트랜잭션에 참여전체가 rollback-only가 될 수 있음없음하..

Programming/Spring 2026.08.12

Spring Boot Micrometer Timer로 외부 API 지연시간 측정하기

Spring Boot Micrometer Timer로 외부 API 지연시간 측정하기외부 결제 API가 느려졌는데 애플리케이션 로그에는 타임아웃만 남는 경우가 있습니다. 평균 응답시간만 보면 평소와 비슷하지만 일부 요청의 지연이 급증했을 수도 있고, 실패한 요청만 유독 오래 걸렸을 수도 있습니다. 이때 필요한 것은 단순 생존 확인이 아니라 처리 횟수와 지연시간의 분포입니다.Spring Boot Actuator는 Spring이 관리하는 MeterRegistry를 자동 구성합니다. Micrometer Timer를 등록하면 호출 횟수와 총 소요시간을 함께 기록하고, 모니터링 시스템이 지원하면 최대값·히스토그램·백분위 지연도 계산할 수 있습니다.Counter·Gauge·Timer 선택 기준타입측정 대상예시Coun..

Spring Data JPA 벌크 UPDATE 후 조회값이 바뀌지 않는 이유

Spring Data JPA 벌크 UPDATE 후 조회값이 바뀌지 않는 이유휴면 회원 5만 명의 상태를 한 번에 변경하려고 JPQL 벌크 UPDATE를 실행했습니다. DB에서는 status='DORMANT'로 바뀌었지만, 같은 트랜잭션에서 조금 전에 조회한 엔티티는 계속 ACTIVE를 반환할 수 있습니다. UPDATE가 실패한 것이 아니라 DB와 영속성 컨텍스트의 상태가 달라진 것입니다.JPA의 일반 변경 감지는 관리 중인 엔티티를 기준으로 SQL을 만듭니다. 반면 벌크 UPDATE·DELETE는 엔티티를 하나씩 로딩하지 않고 DB에 직접 쿼리를 보냅니다. 이미 1차 캐시에 들어온 객체의 필드는 자동으로 고쳐지지 않습니다.일반 수정과 벌크 수정 비교항목엔티티별 변경벌크 UPDATE실행 방식조회 후 변경 ..

Spring @Async 예외 처리: CompletableFuture와 전용 Executor 설정

Spring @Async 예외 처리: CompletableFuture와 전용 Executor 설정메일 발송이나 외부 API 호출을 @Async로 넘긴 뒤 HTTP 응답은 빨라졌는데, 실제 작업 실패를 아무도 모르는 경우가 있습니다. 특히 반환형이 void이면 호출자는 결과를 받을 통로가 없습니다. 로그 한 줄만 남고 주문 상태는 “처리 중”에 머물 수 있습니다.이 글에서는 Spring Framework 7 기준으로 @Async의 두 실패 경로를 나누고, CompletableFuture와 전용 ThreadPoolTaskExecutor로 실패·포화·타임아웃을 관측하는 방법을 구현합니다.반환형에 따라 예외 전달 방식이 달라진다반환형예외가 도착하는 곳권장 용도voidAsyncUncaughtExceptionHan..

Programming/Spring 2026.08.10

Java Stream Gatherers로 목록을 고정 크기 배치로 나누는 방법

Java Stream Gatherers로 목록을 고정 크기 배치로 나누는 방법외부 API가 요청당 100건만 허용하거나, 저장 대상 10만 건을 500건씩 나눠 처리해야 할 때가 있습니다. 기존 Stream API는 map, filter, reduce에는 강하지만 “앞의 여러 원소를 모아 하나의 결과를 내보내기”는 직접 인덱스를 계산하거나 별도 유틸리티를 만들어야 했습니다.Java 24에서 최종 기능이 된 Stream Gatherers는 이런 상태 기반 중간 연산을 표준 API로 제공합니다. Java 25에서도 Stream.gather()와 Gatherers.windowFixed()를 그대로 사용할 수 있습니다. 이 글의 검색 의도는 버전 비교가 아니라 목록을 고정 크기 배치로 안전하게 분할하는 구현법입..

Programming/JAVA 2026.08.10

MariaDB 트랜잭션 격리수준과 데드락 원인 진단법

MariaDB 트랜잭션 격리수준과 데드락 원인 진단법재고 차감이나 포인트 적립처럼 여러 트랜잭션이 같은 행을 동시에 건드리는 로직에서 Deadlock found when trying to get lock 예외를 만난 적이 있을 겁니다. 인덱스도 걸려 있고 코드도 틀린 곳이 없어 보이는데 왜 이런 일이 생길까요. 원인은 대부분 트랜잭션 격리수준과 잠금 순서에 있습니다.트랜잭션 격리수준 4단계SQL 표준은 네 가지 격리수준을 정의합니다. 격리수준이 낮을수록 동시성은 좋아지지만 아래 세 가지 이상 현상이 발생할 여지가 커집니다.격리수준Dirty ReadNon-repeatable ReadPhantom ReadREAD UNCOMMITTED발생발생발생READ COMMITTED방지발생발생REPEATABLE READ방..

빌더 패턴 vs Lombok @Builder, 언제 직접 구현할까

빌더 패턴 vs Lombok @Builder, 언제 직접 구현할까생성자 파라미터가 6개, 7개로 늘어나면 어떤 값이 몇 번째 자리인지 헷갈리기 시작합니다. 선택적 필드가 몇 개만 더 늘어도 오버로딩된 생성자가 줄줄이 생기는 텔레스코핑 생성자(telescoping constructor) 문제입니다. 빌더 패턴은 이 문제를 해결하지만, 요즘은 대부분 Lombok @Builder로 대체해 씁니다. 둘의 차이를 알아야 언제 직접 구현이 필요한지 판단할 수 있습니다.텔레스코핑 생성자 문제public class Order { public Order(Long userId, String productName, int quantity) { ... } public Order(Long userId, String ..

Spring Boot Actuator readiness·liveness 설정: 배포 트래픽을 안전하게 제어하는 방법

Spring Boot Actuator readiness·liveness 설정: 배포 트래픽을 안전하게 제어하는 방법메타 설명: Spring Boot Actuator의 liveness와 readiness 차이를 이해하고, Kubernetes probe와 운영 설정을 구성해 장애 전파와 배포 중 요청 실패를 줄이는 방법을 정리합니다.새 버전을 배포했는데 컨테이너가 실행되자마자 요청이 들어와 503이 발생하거나, 데이터베이스 장애 순간 모든 인스턴스가 연달아 재시작되는 일이 있습니다. 두 상황은 모두 “애플리케이션이 건강한가?”라는 질문을 하나의 체크로 처리할 때 생기기 쉽습니다.Spring Boot Actuator는 생존 여부를 판단하는 liveness와 트래픽 수신 가능 여부를 판단하는 readiness를..

JPA 대량 INSERT 성능 개선: Hibernate JDBC 배치와 flush·clear 설정

JPA 대량 INSERT 성능 개선: Hibernate JDBC 배치와 flush·clear 설정메타 설명: Spring Boot와 MariaDB에서 JPA 대량 INSERT를 JDBC 배치로 묶고, IDENTITY 제약과 영속성 컨텍스트 메모리 문제를 해결하는 설정·코드·검증 방법을 설명합니다.CSV에서 회원 10만 건을 가져오거나 야간 집계 결과를 저장할 때 repository.saveAll()만 호출하면 충분하다고 생각하기 쉽습니다. 하지만 Hibernate의 JDBC 배치가 꺼져 있거나 기본 키가 IDENTITY라면 INSERT가 한 건씩 전송될 수 있습니다. 한 트랜잭션에서 엔티티를 계속 관리하면 1차 캐시도 커집니다.대량 저장 성능은 세 가지를 함께 봐야 합니다.JDBC 드라이버에 INSERT..

728x90