전체 글 193

MSA와 분산 트랜잭션 — Saga 패턴

MSA와 분산 트랜잭션 — Saga 패턴하나의 서비스를 여러 마이크로서비스로 분리하면, 각 서비스가 자신만의 독립적인 데이터베이스를 갖게 된다. 이때 여러 서비스에 걸친 하나의 비즈니스 흐름(예: 주문 → 결제 → 재고 차감)을 처리하다 중간에 실패하면, 이미 처리된 앞 단계를 어떻게 되돌릴 것인가 하는 문제가 생긴다. 이것이 분산 트랜잭션(Distributed Transaction)의 딜레마이다.@Transactional이 통하지 않는 이유단일 DB 환경이라면 가장 바깥 메서드의 @Transactional 하나로 모든 작업을 묶어, 중간에 예외가 나면 전부 깔끔하게 롤백할 수 있다. 하지만 MSA에서는 각 서비스가 독립적인 데이터베이스를 가지고 있어 이 방식이 통하지 않는다.스프링의 @Transacti..

JAVA 2026.06.26

데이터베이스 동시성 — 비관적 락과 낙관적 락

데이터베이스 동시성 — 비관적 락과 낙관적 락여러 사용자나 프로세스가 동시에 같은 데이터를 수정하려고 할 때 데이터가 어긋나는 동시성 문제가 발생한다. 이를 막기 위한 대표적인 방어 전략이 비관적 락(Pessimistic Lock)과 낙관적 락(Optimistic Lock)이다.문제 — 레이스 컨디션과 로스트 업데이트통제되지 않은 레이스 컨디션(경쟁 상태)이라는 환경 때문에, 로스트 업데이트(갱신 손실)라는 데이터 유실 사고가 발생한다. 즉 레이스 컨디션이 원인이고, 로스트 업데이트는 그 결과 중 하나이다.① 레이스 컨디션 (Race Condition, 경쟁 상태)두 개 이상의 스레드나 프로세스가 공유 자원(메모리, 데이터베이스 등)에 동시에 접근하여 값을 변경하려고 할 때 발생하는 문제다. 운영체제의 ..

DATABASE 2026.06.26

동시성 문제

동시성 문제 — 한정판 선착순 판매 시 발생 가능한 문제시스템에서 NFT를 발행하는데 이때 총 발행량을 200개로 고정되어 있었고, 특정 시각에 선착순으로 판매하는 드롭(Drop) 이벤트 형식이였다.이때에 실제 토큰 발행은 블록체인 상에서 이루어졌지만 매 요청마다 온라인 트랜잭션을 보내고, 컴펌을 기다리면 너무 시간이 많이 걸리고, 가스비도 감당하기 어려울 것이라고 판단되어, 선착순에 대한 판정을 백엔드에서 먼저 처리 후 확정된 경우에 민팅 트랜잭션을 수행하도록 했다.예시 코드)@Service@RequiredArgsConstructorpublic class NftDropService { private final NftDropRepository nftDropRepository; @Transact..

JAVA 2026.06.26

ThreadLocal과 OOM (Out of Memory) 발생

ThreadLocal과 OOM (Out of Memory) 발생시스템의 API 서버(Spring Boot 3.x, 내장 Tomcat 사용, Java 17) 환경에서 트래픽이 몰리는 피크 타임에 간헐적으로 서버 응답이 극도로 느려지다가, 결국 java.lang.OutOfMemoryError: Java heap space를 뱉고 컨테이너가 재시작되는 장애가 발생하였다.메모리 누수(Memory Leak)의 주범은 java.lang.ThreadLocal 내부에 쌓여 있는 UserContext 객체들이었다. 해당 UserContext는 사용자의 세션 정보, 권한, 장바구니 메타데이터 등 꽤 무거운 데이터를 담고 있는 객체인데, 이것들이 참조를 잃지 않고 힙 메모리의 70~80%를 점유하고 있었다.메모리 공간 차이..

JAVA 2026.06.26

트랜잭션 격리 수준 4단계

트랜잭션에서 격리 수준이란 여러 트랜잭션이 동시에 같은 데이터를 다룰 때, 한 트랜잭션이 다른 트랜잭션의 작업을 얼마나 볼 수 있는지(=얼마나 격리되는지)를 정하는 단계 설정을 의미한다. 격리 수준은 4단계로 나뉘며높일수록 안전하지만 느리고, 낮출수록 빠르지만 위험하다.트랜잭션 격리 수준이 존재하는 이유여러 트랜잭션을 완벽하게 격리하면(서로 영향을 0으로) 가장 안전하지만 동시 처리량이 바닥난다. 반대로 격리를 풀면 빠르지만 데이터가 꼬인다. 격리 수준은 이 안전성과 성능 사이의 트레이드오프를 조절하는 장치다.즉 격리 수준은 어떤 동시성 문제(이상 현상)를 허용하고 어떤 걸 막을 것인가를 정하는 것이라 할 수 있다. 격리 수준과 관련하여 3가지 타입의 이상 현상이 존재한다.이상 현상Dirty Read (..

DATABASE 2026.06.01

@Transactional 동작 원리 (feat. 롤백이 되지 않는 경우)

@Transactional은 프록시로 동작한다Spring의 @Transactional은 AOP(관점 지향 프로그래밍)를 기반으로 프록시(Proxy) 객체를 통해 동작한다. 해당 어노테이션이 붙은 메서드를 호출하면 원본 객체가 아닌 프록시 객체가 개입하여 트랜잭션을 시작한다. 비즈니스 로직이 정상적으로 완료되면 커밋을 수행하고, 로직 수행 중 RuntimeException 같은 언체크 예외가 발생하면 즉시 롤백 처리를 하여 데이터베이스의 정합성을 보장한다.프록시를 "사장님(본체) 앞에 앉은 비서"라고 생각하면 쉽다. 외부 요청은 일단 비서(프록시)를 거치고, 비서가 "이 일은 트랜잭션으로 묶어야겠네" 하고 앞뒤로 부가 작업(begin / commit·rollback)을 처리한 뒤 사장님에게 실제 일을 넘긴..

JAVA 2026.06.01

CI/CD + 모니터링 스택 구축

사설 이미지 저장소 — Harbor회사 내부에서 이미지를 관리하려면 사설 레지스트리가 필요하다. Harbor는 사용자별 권한 제어, 취약점 스캔 등을 제공하는 CNCF 프로젝트다. Helm으로 설치하며, LoadBalancer 타입으로 노출하고 스토리지는 openebs-hostpath를 연결한다.helm repo add harbor https://helm.goharbor.iohelm pull harbor/harbortar xvfz harbor-1.17.1.tgz && mv harbor harbor-1.17.1 && cd harbor-1.17.1cp values.yaml my-values.yaml# my-values.yaml 주요 수정 항목# expose.type: loadBalancer# expose.l..

기타/Kubernetes 2026.05.31

Helm으로 애플리케이션 배포하기

Deployment, Service, ConfigMap, PVC를 매번 따로 apply 하는 건 번거롭다. Helm은 이 리소스들을 하나의 패키지(차트)로 묶고, 환경별로 달라지는 값만 values.yaml로 관리하게 해주는 쿠버네티스 패키지 매니저다. Helm이 해결하는 문제애플리케이션 하나를 배포하려면 보통 여러 리소스가 한 세트로 필요하다. Helm 차트는 이 리소스들의 YAML 템플릿과, 거기에 끼워 넣을 변수를 담은 values.yaml의 집합이다. 템플릿은 그대로 두고 values만 바꾸면 dev/prod 환경의 DB 주소 같은 차이를 깔끔하게 관리할 수 있다.차트 구조Chart.yaml # 차트 메타 정보values.yaml # 기본 템플릿 변수charts/ #..

기타/Kubernetes 2026.05.31

쿠버네티스 스토리지 — PV/PVC/StorageClass, OpenEBS

파드를 재시작했더니 데이터가 다 날아가 있다. 컨테이너 데이터는 기본적으로 임시 디스크에 있어 파드가 사라지면 같이 사라지기 때문이다. 영속적인 데이터를 가지기 위해서 PV·PVC·StorageClass에 대한 설정이 필요하다.세 가지 핵심 — PV, PVC, StorageClass스토리지는 세 리소스가 협업한다. PV (PersistentVolume) — 실제 데이터가 저장되는 공간. AWS EBS, 로컬 디스크 같은 진짜 스토리지 인프라. 파드 생명주기와 독립적이라 파드가 죽어도 데이터는 남는다. PVC (PersistentVolumeClaim) — 스토리지 요청서. "1Gi짜리 RWO 볼륨 주세요"라는 주문서. StorageClass — 어떤 방식으로 볼륨을 만들지 정의한 규칙. 운영 환경에선 이걸..

카테고리 없음 2026.05.31

쿠버네티스 네트워킹 — Service, DNS, MetalLB, Ingress

Pod IP는 계속 바뀐다 — 그래서 Service가 필요하다Pod는 죽고 다시 뜰 때마다 IP가 바뀐다. 그래서 Pod IP를 직접 가리키면 안 된다. Service는 변하지 않는 가상 IP(와 이름)를 제공하고, 뒤에 있는 Pod들로 트래픽을 분산한다. Service 타입타입용도ClusterIP기본값. 클러스터 내부에서만 접근 가능한 가상 IP. 내부 Pod 간 통신용.HeadlessClusterIP 없이 각 Pod의 DNS를 직접 제공. StatefulSet과 함께 쓴다.NodePort노드의 특정 포트를 열어 외부에서 접근. 내부적으로 ClusterIP를 포함한다.LoadBalancer외부 로드밸런서를 통해 접근. NodePort를 포함한다. 클라우드에선 자동 제공, 온프레미스에선 MetalLB 등..

기타/Kubernetes 2026.05.31