no image
[이거아냥?] 크롤링을 분리하고 kafka 도입하기
들어가며대학교 공지사항을 자동으로 수집하여 모바일 푸시 알림으로 전달하는 서비스를 운영하고 있다. Python 크롤러가 학교 홈페이지를 주기적으로 스크래핑하고, SpringBoot가 데이터를 저장한 뒤 Expo Push API로 알림을 전송하는 구조이다. 처음에는 빠른 개발과 배포를 위해 하나의 Docker 컨테이너에 Java와 Python을 함께 넣었다.운영하면서 이 구조의 한계를 느꼈고, 크롤러를 독립 컨테이너로 분리한 뒤, HTTP 통신을 kafka 기반 이벤트 파이프라인으로 전환했다. 이 글에서는 왜 분리가 필요했는지, RabbitMQ와 Kafka 중 왜 Kafka를 선택했는지, 그리고 최종 아키텍처가 어떻게 바뀌었는지를 정리한다.1. 기존 아키텍처DockerfileFROM eclipse-temu..
2026.02.23
no image
[Spring JPA] JPA의 구조와 Entity 생명주기
들어가기 앞서Spring Boot로 백엔드 개발을 시작하면, 데이터베이스 접근 기술로 가장 먼저 접하게 되는 것이 Spring Data JPA다. CRUD 메서드를 자동으로 제공하고, SQL을 직접 작성하지 않아도 된다는 점에서 매우 편리하다. 하지만 프로젝트가 조금만 복잡해지면 자연스럽게 다음과 같은 의문이 생긴다. 왜 SQL이 이 시점에서 실행되지 않을까?엔티티를 저장했는데, 아직 DB에 반영되지 않은 것처럼 보이는 이유는 뭘까?Hibernate 예외는 왜 이런 타이밍에 발생하는 걸까?이러한 의문들은 대부분 Spring Data JPA가 내부적으로 어떻게 동작하는지, 그리고 엔티티가 어떤 생명주기를 거쳐 데이터베이스에 반영되는지를 정확히 이해하지 못했을 때 발생한다.이 글에서는 Spring Data ..
2026.01.19
no image
[잇다] 한정된 메뉴 재고를 100% 정확하게 지켜내는 동시성 제어 전략
서론https://dongyeop00.tistory.com/218 [SpringBoot] 레벨 별 락 기법서론동시성을 제어하기 위해 다양한 락 기법을 적용해보면서 레벨 별 성능이 다르다는 것을 알게 되었다. 얄팍하게나마 레벨 별 동시성 제어 기법을 알아보자. 왜 락의 레벨을 구분해야 할까?dongyeop00.tistory.com앞 글에서 레벨 별 락 기법에 대해 살펴 보았다. 이번 글에서는 잇다 프로젝트를 진행하며 발생한 메뉴 재고 동시성 문제를 해결하는 과정에서, 여러 락 전략을 비교, 실험하고 최종적인 기술적 선택에 이르기까지의 과정을 정리해보겠다. 메뉴 주문 기능은 단순한 CRUD 작업처럼 보일 수 있으나, 다수의 사용자가 동시에 하나의 자원에 접근하는 상황에서는 데이터 정합성과 시스템 안정성을 동..
2026.01.16
no image
[SpringBoot] 레벨 별 락 기법
서론동시성을 제어하기 위해 다양한 락 기법을 적용해보면서 레벨 별 성능이 다르다는 것을 알게 되었다. 얄팍하게나마 레벨 별 동시성 제어 기법을 알아보자. 왜 락의 레벨을 구분해야 할까? 백엔드 개발을 하다 보면 동시성 문제는 피할 수 없다.특히 주문, 결제, 재고, 좌석 예약과 같은 도메인에서는 "누가 먼저 처리했는가" 보다 "데이터가 정확한가"가 훨씬 중요해진다. 처음에는 synchronized나 @Transactional만으로도 해결되는 것처럼 보인다.하지만 서버가 늘어나고 트래픽이 증가하는 순간, 이 방식들이 아무 의미 없는 코드가 되는 경험을 하게 된다. 이 글에서는 락을 단순히 기술 목록이 아니라, "어디에서 동시성을 제어하느냐"라는 관점에서 정리해본다. 레벨은 JVM, Spring Data J..
2026.01.15
no image
[잇다] JPA FetchType의 설계 의도를 이해하며 N+1 문제 해결
서론https://dongyeop00.tistory.com/216 [Spring JPA] JPA N+1 문제: 왜 발생하고, 어떻게 제어해야 하는가1. 왜 N+1을 반드시 알아야 할까JPA를 쓰다 보면 쿼리는 한 번 날렸는데 왜 DB 로그에 수십, 수백 개의 쿼리가 찍히는 상황을 반드시 겪게 된다.하나의 쿼리로 데이터를 가져온 뒤 연관된 데이터를dongyeop00.tistory.com 이전 글에서 JPA의 연관 관계 매핑과 설계 방식을 다뤘다.JPA에서 연관 관계 매핑에 대해 공부가 끝났다면 이제 프로젝트에서 발생하는 N+1 문제를 마주하고 해결해보자 오래전 진행했던 프로젝트에서는 실제 요구사항을 통해 어떤 지점에서 문제가 발생했고, 왜 특정 해결 방식을 선택했는지 기록해 보겠다.본론해당 프로젝트는 아동..
2026.01.12
no image
[Spring JPA] JPA N+1 문제: 왜 발생하고, 어떻게 제어해야 하는가
1. 왜 N+1을 반드시 알아야 할까JPA를 쓰다 보면 쿼리는 한 번 날렸는데 왜 DB 로그에 수십, 수백 개의 쿼리가 찍히는 상황을 반드시 겪게 된다.하나의 쿼리로 데이터를 가져온 뒤 연관된 데이터를 조회하기 위해 추가로 n개의 쿼리가 실행되어 총 1+n개의 쿼리가 나가는 현상이게 바로, N+1 문제고, 개발 환경에서는 티가 안 나다가 프로덕션 환경에서 트래픽이 몰리는 순간 DB 부하 -> 응답 지연 -> 장애로 직결될 수 있다는 점이다. 따라서 N+1 문제는 알면 좋은 개념이 아니라, ORM을 사용하는 개발자라면 반드시 이해하고 통제해야 하는 함정이다...2. N+1 Select 문제2.1 N+1 문제란?특정 엔티티를 조회하는 쿼리 1번해당 엔티티가 가진 연관관계를 조회하기 위한 쿼리 N번1+N 문제..
2026.01.12
no image
이거아냥? - WebClient 기반 논블로킹 처리
서론https://dongyeop00.tistory.com/214 이거아냥? - @Async를 활용한 스레드 분리서론https://dongyeop00.tistory.com/213 이거아냥? - 알림 전송 서비스 구조 개선 (1)서론서비스 레이어에 중복 코드와 책임이 점점 쌓이면서, 공통 로직을 util 클래스로 분리하는 리팩토링을 진행하게 되dongyeop00.tistory.com이전 글에서는 알림 전송 서비스의 병목 지점을 AOP 기반 실행 시간 측정을 통해 수치로 확인하고,푸시 알림 전송 로직을 비동기 스레드로 분리하여 메인 트랜잭션 유지 시간을 대폭 줄이는 작업을 진행했다. 그 결과, 트랜잭션 내부에서 수행되던 푸시 알림 전송은 메인 스레드에서 분리되었고, 공지사항 및 학교 소식 저장 로직은 빠르게..
2026.01.06
no image
이거아냥? - @Async를 활용한 스레드 분리
서론https://dongyeop00.tistory.com/213 이거아냥? - 알림 전송 서비스 구조 개선 (1)서론서비스 레이어에 중복 코드와 책임이 점점 쌓이면서, 공통 로직을 util 클래스로 분리하는 리팩토링을 진행하게 되었다.현재는 DDD 패턴으로 비즈니스 로직을 도메인에서 해결하도록 리팩토dongyeop00.tistory.com앞 글에서는 저장 로직의 병목 현상을 줄여 트랜잭션 유지 시간을 줄일 계획을 세웠다.이번 글에서는 트랜잭션 유지 시간 측정 베이스 라인을 AOP를 활용하여 공지사항, 학교 소식 서비스에 적용해보겠다.본론1. AOP 구현하기모니터링 용 애노티이션 만들기코드더보기더보기더보기@Target(ElementType.METHOD)@Retention(RetentionPolicy.RU..
2026.01.04
no image
이거아냥? - 알림 전송 서비스 구조 개선 계획
서론서비스 레이어에 중복 코드와 책임이 점점 쌓이면서, 공통 로직을 util 클래스로 분리하는 리팩토링을 진행하게 되었다.현재는 DDD 패턴으로 비즈니스 로직을 도메인에서 해결하도록 리팩토링을 진행했다.리팩토링 과정에서 비즈니스 로직의 실행 흐름을 하나씩 따라가며 살펴본 결과, 불필요한 DB 조회가 반복되고 있었고, 외부 API 호출이 동기 방식으로 처리되며 트랜잭션 경계 안에 포함되어 있다는 구조적인 문제를 발견했다. 현재 핵심 비즈니스 흐름은 아래와 같다.아래 로직을 간략하게 저장 로직이라고 하겠다.면밀히 살펴본 문제점들1. 크롤링과 저장된 DB 비교 로직현재 저장 로직의 흐름은 다음과 같다.1. 스케줄러가 크롤링을 실행하여 최신 데이터 5건을 수집한다.2. 애플리케이션은 DB에 저장된 기존 데이터 ..
2025.12.30