본문 바로가기

반응형

분류 전체보기

(433)
TIL - JVM Cold Start: 왜 첫 요청은 항상 느린가 한 줄 요약JVM 애플리케이션은 시작 후 첫 요청이 느린 이유가 네트워크/DB 연결 문제가 아니라, JVM 내부의 클래스 로딩·인터프리터 실행·JIT 컴파일 지연에 있다.배경 — 무엇을 보다 알게 됐나쿠폰 서비스에 HikariCP Eager 워밍업을 적용해 DB TCP cold start(~220ms)를 제거했음에도,앱 재시작 후 첫 smoke 테스트 응답이 3,616ms로 여전히 느렸다.두 번째 실행은 592ms로 6배 빨라졌다.TCP 연결은 이미 열려 있는데 왜 느린가? → JVM 레이어의 cold start.JVM Cold Start란JVM(Java Virtual Machine)은 바이트코드(.class)를 실행하는데, 처음에는 인터프리터가 한 줄씩 읽어 실행한다.이 과정에서 세 가지 비용이 발생한..
TIL - ExecutorService와 CountDownLatch로 동시성 테스트하기 쿠폰 재고가 1개일 때 100명이 동시에 발급 요청을 보내도 쿠폰이 1개만 발급되는지 검증하고 싶었다.핵심 개념ExecutorService여러 작업을 스레드로 실행하고 관리하는 도구다. ExecutorService executor = Executors.newFixedThreadPool(100); 100개의 작업을 동시에 실행할 수 있는 스레드 풀을 만든다. executor.submit(() -> couponService.issueCoupon()); submit()을 호출하면 전달한 작업이 스레드 풀에서 실행된다.CountDownLatch내부 카운터가 0이 될 때까지 스레드를 기다리게 하는 동기화 도구다. CountDownLatch latch = new CountDownLatch(100); 카운터가 100..
TIL - HikariCP 커넥션 풀과 Cold Start 배경부하 테스트 중 Zipkin 트레이스에서 connection 스팬이 220ms 찍히는 현상을 발견. HikariCP 동작 방식과 cold start 원인을 파악함.HikariCP란Spring Boot 기본 JDBC 커넥션 풀. 애플리케이션 ↔ DB 사이의 TCP 연결을 미리 만들어두고 재사용해서 매 요청마다 연결 비용을 없애는 역할.Cold Start가 발생하는 이유HikariCP는 기본적으로 lazy 초기화를 사용함. 앱이 뜰 때 커넥션을 미리 만들지 않고, 첫 요청이 들어올 때 비로소 물리 TCP 연결을 생성함.앱 시작 → 풀 생성 (빈 상태)첫 요청 → 그때서야 PostgreSQL TCP 연결 생성 → ~220ms이후 요청 → 이미 만들어진 커넥션 재사용 → ~0mslazy가 기본값인 이유: 앱..
TIL - MSA에서 HTTP vs Kafka, Spring Cloud Gateway 면접 질문"대용량 트래픽인데 왜 Gateway에서 Kafka를 사용하지 않고 HTTP로 서비스 간 통신을 했나요?"처음에는 "대용량 = Kafka"라고 생각해서 제대로 답하지 못했다.하지만 질문의 의도는 Kafka를 써야 하는 상황과 HTTP를 써야 하는 상황을 구분할 수 있는지를 확인하는 것이었다.내가 구현했던 구조Client │HTTPS ▼Spring Cloud Gateway │HTTP ▼User Service │HTTP ▼Wallet Service 기존에는Gateway │HTTP ▼User Service │userId 조회 와 같이 Gateway가 User Service를 호출하여 userId를 조회했다.이를 개선하면서JWT Token에 userId를 포함..
TIL - Docker restart 정책 비교 (no / on-failure / always / unless-stopped) Docker restart 정책 4가지Docker의 restart 정책은 컨테이너가 종료되었을 때 Docker가 어떻게 동작할지 결정하는 옵션입니다.정책설명no자동 재시작하지 않음 (기본값)on-failure비정상 종료(exit code ≠ 0)일 때만 재시작always종료 원인과 관계없이 항상 재시작unless-stopped항상 재시작하지만 사용자가 수동으로 중지한 경우는 제외참고on-failure는 Docker 데몬이 재시작될 당시 비정상 종료 상태였던 컨테이너도 다시 시작합니다.on-failure vs unless-stopped 차이처음에는 always, on-failure, unless-stopped가 비슷해 보였지만 핵심 차이는 사용자가 docker stop으로 직접 중지한 경우입니다. dock..
TIL - Boolean.TRUE.equals()를 쓰는 이유 배운 것Boolean.TRUE.equals(result) 형태로 쓰는 이유는 result가 null일 수 있기 때문이다.상황Redis에서 Set 멤버 여부를 확인하는 코드에서 발견했다.public boolean isAlreadyIssued(String couponId, String userId) { Boolean result = redisTemplate.opsForSet().isMember(ISSUED_KEY_PREFIX + couponId, userId); return Boolean.TRUE.equals(result);}redisTemplate.opsForSet().isMember()의 반환 타입은 Boolean (래퍼 타입)이다.Java의 래퍼 타입 Boolean은 true, false, ..
TIL - G1GC vs ZGC : GC 방식 차이와 선택 기준 📌 한 줄 요약G1GC는 처리량 최적화, ZGC는 latency 최적화. 힙이 커질수록 G1GC의 STW pause가 늘어나지만 ZGC는 힙 크기에 관계없이 항상 1-10ms 이하를 유지한다.🔎 왜 GC가 중요한가 — STW(Stop-The-World)JVM GC가 실행되는 순간 모든 애플리케이션 스레드가 멈춘다(STW). 100개 VU가 요청을 처리하는 도중 GC가 터지면, 100개 요청 모두가 그 시간만큼 추가 지연된다.GC 발생 시 타임라인: VU 1 ──[요청 처리 중]── ⚡GC START ── 멈춤 450ms ── GC END ── 완료 VU 2 ──[요청 처리 중]── ⚡GC START ── 멈춤 450ms ── GC END ── 완료 ... VU 100 ──[요청 처리 중..
[내일배움캠프 TIL] - Lettuce Redis 명령이 Zipkin에 안 나타나는 이유 & 해결 배운 것Lettuce(Spring Data Redis 기본 클라이언트)는 Netty 이벤트 루프 스레드에서 Redis 명령을 실행한다. Spring의 trace context는 ThreadLocal 기반이라 Tomcat → Netty 스레드 간 전파가 안 된다. 그래서 Lettuce의 기본 tracing 설정만으로는 Redis 스팬이 Zipkin에 나타나지 않는다.문제 상황POST /api/v1/coupons/{couponId}/issue 엔드포인트의 Zipkin 트레이스에 Redis 명령이 전혀 보이지 않았다.쿠폰 발급 시 Redis를 3번 호출하는데:SISMEMBER coupon:issued:{id} {userId} ← 중복 발급 체크DECR coupon:stock:{id} ..

반응형