전체 글 123

[Troubleshooting] Outbox Relay 다중 인스턴스 환경의 중복 발행 방지

문제 정의Care Plan Service는 Outbox Relay Scheduler가 주기적으로 PostgreSQL의 Outbox 테이블에서 PENDING 상태의 이벤트를 조회한 뒤 RabbitMQ로 발행하는 구조였습니다.단일 인스턴스에서는 하나의 Scheduler만 동작하지만, 서비스가 여러 인스턴스로 확장되면 각 인스턴스에서 동일한 Scheduler가 독립적으로 실행됩니다.따라서 동일한 PENDING 이벤트를 여러 인스턴스가 거의 동시에 조회할 수 있었습니다.CarePlan Service A → PENDING 이벤트 조회CarePlan Service B → 동일 PENDING 이벤트 조회A → RabbitMQ 발행B → RabbitMQ 발행기존 구조에는 이벤트의 처리 권한을 선점하거나 동시 수정 충돌..

Todak-Todag 2026.09.20

[Troubleshooting] Docker Volume에 남은 PostgreSQL 인증 정보로 인한 DB 연결 실패 해결

문제 상황팀원들과 동일한 PostgreSQL 비밀번호를 .env에 설정했음에도 로컬 환경에서만 DB 인증 오류가 발생했습니다.특히 Spring Boot 테스트 실행 시 contextLoads() 단계에서 Hibernate 초기화가 실패하며 애플리케이션 컨텍스트가 정상적으로 올라오지 않았습니다.원인 분석PostgreSQL Docker 이미지는 POSTGRES_PASSWORD 값을 최초 데이터베이스 초기화 시점에만 적용합니다.기존 Docker Volume에는 과거에 사용하던 비밀번호로 초기화된 PostgreSQL 데이터가 남아 있었기 때문에, .env의 비밀번호를 팀과 동일하게 변경해도 실제 PostgreSQL 사용자 비밀번호는 변경되지 않았습니다.따라서 다음과 같은 불일치가 발생했습니다.애플리케이션 설정 ..

Todak-Todag 2026.09.20

[Troubleshooting] RabbitMQ Consumer의 외부 호출과 DB Transaction 분리

문제 정의CarePlanCompleted 이벤트 처리 흐름에서도 CarePlan Consumer가 Schedule Service를 Feign으로 호출한 뒤 CarePlan 상태를 변경하고 Outbox 이벤트를 저장하고 있었다.RabbitMQ ↓CarePlan Consumer ↓Feign → Schedule ↓CarePlan DB 변경 ↓Outbox 저장정상 환경에서는 처리 시간이 짧아 문제가 드러나지 않았지만, 코드 확인 결과 Consumer 처리 전체가 @Transactional 범위에 포함되어 있어 Schedule Service 응답이 느려질 경우 DB Connection이 함께 장시간 점유될 가능성이 있었다.Schedule 내부 API에 의도적으로 3초 지연을 주입하고, 10 Cons..

Todak-Todag 2026.09.20

[Troubleshooting] 외부 서비스 지연으로 인한 CarePlan Connection Pool 포화 개선

문제 정의PATCH /api/v1/care-plans/{carePlanId}/status는 CarePlan 상태 변경 과정에서 User Service를 Feign으로 호출하고 있었다.정상 환경에서 40 Threads × Loop Count 5, 총 200건의 반복 부하를 발생시켰지만 Average 20ms, p95 28ms로 처리가 매우 빨라 HikariCP Pool 포화가 발생하지 않았다.하지만 코드상 User Service 호출이 DB 트랜잭션 내부에 존재했기 때문에 외부 서비스가 느려질 경우 DB Connection까지 장시간 점유할 가능성이 있었다.이를 검증하기 위해 User Service 내부 API에 300ms 지연을 주입했다.동일한 200건 부하에서:Active = Max(10)Idle = ..

Todak-Todag 2026.09.20

[Troubleshooting] RabbitMQ 비동기 이벤트 구간의 Trace 단절 개선

문제 정의Schedule Service에서 CarePlanCompleted 이벤트를 발행하고 CarePlan Service가 이를 소비하는 흐름을 Zipkin으로 추적하던 중, 두 서비스가 하나의 Trace로 연결되지 않는 문제를 발견했다.Schedule Outbox Relay ↓RabbitMQ ↓CarePlan Consumer이벤트 전달과 비즈니스 처리는 정상적으로 완료됐지만 Schedule에는 traceId가 존재하는 반면 CarePlan Listener에서는 traceId/spanId가 존재하지 않았고, 이후 CarePlan 작업에서 새로운 Trace가 생성되고 있었다.이 때문에 이벤트 처리 지연이 발생해도 Schedule 처리, RabbitMQ 전달, CarePlan Co..

Todak-Todag 2026.09.20

[MSA] 메시지 브로커란? RabbitMQ와 Kafka

order-service, payment-service 아예 서로 다른 프로그램이다 따라서 자바 메서드를 직접 호출할 수 없다paymentService.pay(orderId); // X// paymentService 객체가 주문 서비스 JVM 안에 없기 떄문// 게다가 DB도 분리되어 있어서 하나의 트랜잭션도 일반적으로 불가능order-service payment-serviceOrderService PaymentServiceOrder DB Payment DBJVM A JVM BMSA에서 두 가지 선택지1. 동기 통신주문 서비스 → HTTP 요청 → 결제 서비스주문 서비스 ← 결과 ← 결제 서비스 주문 서비스가 결제 결과를 기다린다 paymentC..

[CQRS]읽기와 쓰기를 분리하는 이유

User나 Review같은 하나의 엔티티(DB 테이블과 매핑되는 자바 클래스)를 만들어서 그 엔티티로 등록하고 조회도 하고 수정도 해야 한다 대부분의 서비스에선 이정도로 충분하다 문제는 서비스가 커졌을 때 생긴다 예를들어 쇼핑몰에서는 주문 생성은 한 번 일어나는데 내 주문 조회는 사용자가 새로 고침할 때마다 마이페이지 들어갈 때마다 계속 일어난다 즉, 쓰기는 적고 읽기는 훨씬 많이 발생하는 게 일반적이다 그런데 하나의 모델, 하나의 DB로 쓰기와 읽기를 같이 처리하면 이런 일이 생긴다Join 폭발주문 상세 화면 하나 그리려면 주문, 상품, 유저, 결제, 배송 테이블을 다 엮어야 하는데 조회가 몰리면 DB CPU가 이 JOIN 연산으로 계속 바빠진다자원 경합읽기와 쓰기가 직접 서로 락을 걸지 않아도 같은 ..

[Docker] VM과 Docker의 차이부터 컨테이너 실행까지

Docker가 해결하는 문제부터 이해하자내 컴퓨터에서는 되는데요? — 이 문제를 근본적으로 없애기 위해 나온 기술이 도커다 개발 환경, 테스트 서버, 운영 서버의 OS 버전, 라이브러리 버전, 환경 변수가 미묘하게 다르면 똑같은 코드도 다르게 동작한다 Docker는 애플리케이션 + 그것이 필요로 하는 모든 것(런타임, 라이브러리, 설정 파일)을 하나의 이미지라는 패키지로 묶어서 어디서 실행하든 100% 동일한 환경을 보장한다 내가 이전에 진행했던 개인프로젝트인 CoreBoard에서 Testcontainers를 썼었는데 로컬 PC든 CI 서버든 항상 똑같은 버전의 MySQL 컨테이너가 뜬다 그 이유가 바로 이식성 때문이다핵심 개념 4개Dockerfile (설계도, 텍스트 파일) │ docker bu..