잡다한 프로그래밍 :: 잡다한 프로그래밍
반응형

로그 기반 CDC vs 배치 처리 — 데이터 변경 감지 방식의 본질적 차이

대표적인 접근은 배치(Polling)로그 기반 CDC(Change Data Capture) 두 가지다.
이번 글에서는 두 방식의 구조, 장단점, 그리고 실무적 선택 기준을 살펴보자

 

 

배치 기반의 CDC

- 폴링 처리로 5분마다 데이터 조회 = 실시간에 적합하지 않음

로그기반의 CDC

- 모든 트랜잭션을 로깅 하기 때문에 바로바로 이벤트를 받고 처리할 수 있음

 

 

지연시간과 CPU 부하의 상관관계로그기반의 CDC

폴링 = 짧은 주기를 가지면 지연시간이 줄어들지만 CPU 사용량이 늘어난다.

반대로 CPU 사용량이 줄어들면 실시간성이 떨어진다.

로그기반 CDC

변경된 로그를 자동으로 받아, 이벤트 처럼 처리할 수 있고

변경이 없으면 CPU 사용이 거의 없기 때문에 지연시간도 낮고 DB영향이 최소화된다.

 

삭제처리에 유용함

배치처리 CDC의 경우 로우가 삭제되는경우 레코드가 삭제 > 5분뒤 배치처리에서 조회해도 데이터가 삭제되어 조회가 불가능

 

로그기반 DELETE 트랜잭션도 로깅되기 때문에 처리가능

반응형
반응형

복잡한 비즈니스 로직, Temporal 워크플로우로 다루기

현대 시스템은 단순히 함수를 호출하고 끝나는 구조가 아닙니다.
결제 → 승인 → 재고 차감 → 알림 발송처럼,
여러 개의 트랜잭션이 순차적 혹은 병렬적으로 동작하는 복잡한 프로세스가 많습니다.

이런 구조를 단순 코드나 큐 기반으로 관리하면 점점 복잡성이 증가 합니다.

기존 워크플로우 관리 방식의 한계

  • 상태 관리 복잡: 각 단계의 상태 (진행중, 실패, 완료)를 직접 추적해야한다
  • 장애 복구 어려움: 중간 단계 실패 시, 어디서 멈췄는지 파악하기 어려움
  • 확장성 부족: 새로운 로직이 추가되면 기존 코드 수정 필요
  • 네트워크 불안정에 취약: 외부 API 장애나 타임아웃 시 재처리 로직 직접 구현 필요

구현이 불가능 하진 않지만 비즈니스로직 구현이 어려워지거나 불편해질 수 있습니다.

 

Temporal 워크플로우

앞선 문제들에 공감하고, 더 안정적으로 비즈니스로직을 운영 및 구현하고싶다면 워크플로우 오케스트레이션을 도입해볼 수 있습니다.

Temporal 적용 시 내부적으로 일어나는 일

1. Client가 Workflow 시작 요청 → Temporal Cluster 저장
→ 모든 실행 상태(Event History)가 DB에 기록됨
2. Worker가 Task Queue에서 Activity 실행
→ 네트워크 장애나 예외 발생 시 자동 리트라이
3. Activity 성공 시 다음 단계로 진행
→ 실패한 단계만 재시도 가능 (partial rollback 지원)
4. 전체 완료 시 Workflow 성공으로 마킹

이작업은 여러작업을 순서대로 or 병렬 실행하면서 복잡한 비즈니스 프로세스를 자동화 하는것 을 의미함

 

반응형
반응형

앞으로 CDC 패턴에 대해서 공부해 볼것이다.

CDC(Change Data Capture)란 무엇인가?

**CDC(Change Data Capture)**는 말 그대로

“데이터베이스에서 일어나는 변경(Insert / Update / Delete) 을 실시간으로 감지하여, 이를 다른 시스템으로 전달하는 기술”

즉, 기존의 DB가 ‘변했다’는 사실을 캡처(Capture) 해서 이를 이벤트(Event) 로 만들어 보내주는 구조입니다.

왜 CDC가 필요한가?

오늘날 기업 시스템은 단일 DB에 머무르지 않습니다.
서비스가 분리되고, 분석·알림·검색·캐시 등 다양한 하위 시스템이 함께 움직입니다.

그런데 만약 주문 데이터 하나가 생성될 때마다,

  • 검색엔진(Elasticsearch)에 반영해야 하고,
  • 캐시(Redis)를 갱신해야 하고,
  • 분석용 데이터 웨어하우스(BigQuery 등)에 전달해야 한다면?
  • 이를 매번 배치(batch) 로 돌린다면 지연(latency)이 발생하고 DB 부하가 커지고 실시간성이 확보되지 않습니다.

즉 CDC 하나의 데이터로 파생되는 수많은 데이터를 서비스마다 독립적으로 유연하게 처리할 수 있다는 장점이 있습니다.

 

예시 흐름

  1. DB에서 주문이 생성됨 (INSERT)
  2. CDC 시스템이 트랜잭션 로그에서 해당 변경사항을 캡처
  3. 이를 이벤트(Event) 로 Kafka에 발행
  4. 다른 시스템(Elasticsearch, Redis, 알림 시스템 등)이 이 이벤트를 구독(consume) 하여 데이터 반영

반응형
반응형

먼저 우리가 배울 실시간성 아키텍쳐가 왜 중요한지 예시를 통해 알아보자

 

실시간성의 중요성과 아키텍처 진화

1. 전통적인 모놀리식 아키텍처

이미지 처럼 하나의 서비스에서 주문, 결제, 배송 기능이 있고, 이는 하나의 DB를 참조하는 구조

단점

  • 서비스간 높은 결합도: 한 서비스의 변경이 다른 서비스에 즉각적인 영향을 준다. (장애도 마찬가지)
  • 확장성 한계 : 서비스에 다른 기능 추가시 기존 주문 서비스 코드를 수정해야해서 확장성이 좋지 않다.
  • 주문 > 결제 > 제고 > 배송 같은 동기 API 호출 체인에서 응답이 느려지고 장애 전파가 쉽다.

2. 배치 처리 중심의 아키텍처

단점

  • 높은 지연 시간: 배치 주기를 기다려야함 (실시간 데이터 확인 어려움)

“결제가 완료돼도 재고 정보가 다음 배치 때까지 안 맞는” 상황이 자주 발생.

  • 배치 실패: 전체 재처리 과정 필요
  • 특정 시간대 부하 집중 (DB 부하 핸들링 필요)

3. MSA (Microservice Architecture)

현대 시스템은 주문, 결제, 재고, 배송 서비스가 각각의 독립된 DB 를 가집니다.
이를 코어 MSA 구조라고 합니다.

 

단점

  • 데이터 불일치: 각 서비스마다 DB를 따로 관리하기때문에 모든 서비스에 반영해야하는 불편함이 있음
  • 시스템간 의존성 증가: 결제 완료 이벤트를 재고 서비스가 "즉시" 알아야 함

4. 공유 DB 기반의 MSA (Microservice Architecture)

단점

  • DB 성능 병목: 모든 서비스가 한 DB에 접근 하다보니 성능이 좋지않음
  • 확장성 한계: 수평 스케일링이 어려움 (하드웨어 용량 모자람 등)
  • 단일 장애지점 (SPOF)
  • DB 락 경쟁 증가

4. 이벤트 기반 EDA (Event-Driven Architecture)

EDA의 핵심

- 메시지 전달이 아니라 "상태의 히스토리 자체를 이벤트로 관리"

  • 서비스간에 공유되는 자원은 카프카 메세지 뿐 (느슨한 결합)
  • 이벤트 소싱이 존재함
    • 모든 상태 변경 (주문 생성, 결제 완료, 배송시작)을 이벤트로 기록할 수 있음
    • 시스템이 재시작 되더라도 이벤트 로그를 적용해 복구 가능
    • 이벤트 활용해 감사 추적가능 (어떤 이벤트가 어떤 시간에...등)
    • CQRS 패턴 구현 가능 (쓰기, 읽기를 분리한다)

 

참고 블로그

https://toss.tech/article/22563

반응형
반응형

페이징 처리

JPA는 페이징을 다음과 같이 추상화합니다:

String jpql = "SELECT m FROM Member m ORDER BY m.name DESC";
List<Member> result = em.createQuery(jpql, Member.class)
                        .setFirstResult(10)
                        .setMaxResults(20)
                        .getResultList();

→ 실제 SQL로 번역되어 효율적인 페이징 처리 가능 (LIMIT, OFFSET 등)

 

 

JOIN

1. 내부 조인 (INNER JOIN)

 
// jpql
SELECT m FROM Member m INNER JOIN m.team t

// sql
SELECT m.* FROM Member m JOIN Team t ON m.team_id = t.id

 

2. 외부 조인 (LEFT JOIN)

// jpql
SELECT m FROM Member m LEFT JOIN m.team t

//sql
SELECT m.* FROM Member m LEFT OUTER JOIN Team t ON m.team_id = t.id

 

3. 세타 조인 (연관관계 없는 조인)

SELECT COUNT(m) FROM Member m, Team t WHERE m.username = t.name

 

ON절 활용 (JOIN 필터링)

1. 조건 필터링 조인

SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = '팀A'

SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.team_id = t.id AND t.name = '팀A'
 

 

2. 연관 관계가 없는 엔티티 외부 조인

SELECT m, t FROM Member m LEFT JOIN Team t ON m.username = t.name

 

서브쿼리

JPQL에서는 WHERE / HAVING / SELECT(하이버네이트) 절에서 서브쿼리를 지원합니다 (단, FROM 절 서브쿼리는 불가).

예시 1: 평균보다 나이 많은 회원

SELECT m FROM Member m 
WHERE m.age > (SELECT AVG(m2.age) FROM Member m2)

 

2: 주문한 적 있는 회원

SELECT m FROM Member m 
WHERE (SELECT COUNT(o) FROM Order o WHERE o.member = m) > 0

 

 

서브쿼리 지원 함수

  • [NOT] EXIST: 서브쿼리에 결과가 존재하면 참
  • ALL: 모두만족하면 참
  • ANY, SOME: 하나라도 만족하면참
  • [NOT] IN: 서브쿼리 결과중 하나라도 같은것이 있으면 참
 

 

JPQL 타입 표현과 기타

 

조건식 CASE

기본 CASE

SELECT CASE 
  WHEN m.age <= 10 THEN '학생요금' 
  WHEN m.age >= 60 THEN '경로요금' 
  ELSE '일반요금' 
END FROM Member m

단순 CASE

SELECT CASE t.name 
  WHEN '팀A' THEN '인센티브110%' 
  WHEN '팀B' THEN '인센티브120%' 
  ELSE '인센티브105%' 
END FROM Team t

 

JPQL 기본 함수

함수 설명
CONCAT(a, b) 문자열 결합
SUBSTRING(s, i, len) 문자열 자르기
TRIM(s) 공백 제거
LOWER, UPPER 소문자/대문자
LENGTH(s) 문자열 길이
LOCATE(sub, s) 위치 검색
ABS, SQRT, MOD 수치 연산
SIZE(c) 컬렉션 크기
INDEX(c) 컬렉션 인덱스 (JPA 전용)

 

사용자 정의 함수

SELECT function('group_concat', i.name) FROM Item i

💡 사용자 정의 함수는 DB 방언(Dialect)에 등록 필요 (이미 대부분 등록되어있음) 사용만 하면돼

 

 

반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 프로젝션  (2) 2025.07.31
[JPA] JPQL 기본 문법과 기능  (2) 2025.07.30
[JPA] JPQL 소개  (2) 2025.07.30
[JPA] 값 타입 컬렉션  (1) 2025.07.25
[JPA] 값 타입과 불변 객체  (2) 2025.07.23
반응형

JPA에서 프로젝션(Projection)은

SELECT 절에서 어떤 데이터를 조회할지 명시하는 것을 말합니다.

프로젝션의 3가지 대상

엔티티 프로젝션

String jpql = "SELECT m FROM Member m";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();

 

  • 영속성 컨텍스트 관리 대상 → result 안에 들어있는 각 Member는 수정 시 dirty checking 되어 UPDATE 발생

연관된 엔티티 조회 (Team)

String jpql = "SELECT m.team FROM Member m";
List<Team> result = em.createQuery(jpql, Team.class).getResultList();

주의: 내부적으로 JOIN 발생
추천 방식은 아래처럼 명시적 JOIN으로 작성 (쿼리랑 비슷해야 예측이 가능함)

String jpql = "SELECT t FROM Member m JOIN m.team t";
List<Team> result = em.createQuery(jpql, Team.class).getResultList();

임베디드 타입 프로젝션

String jpql = "SELECT o.address FROM Order o";
List<Address> result = em.createQuery(jpql, Address.class).getResultList();

 

더보기

해당 Address 객체는 영속성 컨텍스트 상태인가?

=> 즉 변경하면 update쿼리 나가?

 

안나감! Embedded는 값타입이니까!

=> 얘는 영속성 컨텍스트 돼? 찾아보기

스칼라 타입 프로젝션

// 강의에서 설명한 쿼리방식은 생략 너무 보기 불편함

String jpql = "SELECT m.username, m.age FROM Member m";
List<Object[]> result = em.createQuery(jpql).getResultList();

for (Object[] row : result) {
    String username = (String) row[0];
    int age = (int) row[1];
}

new 명령어로 DTO 직접 조회

String jpql = "SELECT NEW com.example.dto.MemberDto(m.username, m.age) FROM Member m";
List<MemberDto> result = em.createQuery(jpql, MemberDto.class).getResultList();

 

  • 단순 값을 DTO로 바로 조회
  • 패키지명 포함한 전체 클래스명 필수
  • DTO에는 해당 생성자가 있어야 함

 

반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 페이징, 조인, 서브쿼리 등  (3) 2025.07.31
[JPA] JPQL 기본 문법과 기능  (2) 2025.07.30
[JPA] JPQL 소개  (2) 2025.07.30
[JPA] 값 타입 컬렉션  (1) 2025.07.25
[JPA] 값 타입과 불변 객체  (2) 2025.07.23
반응형

JPQL 설명에 필요할 예제 모델은 아래와 같다

 

JPQL 문법 구조

1. SELECT 문

 

select_절

from_절

[where_절]

[groupby_절]

[having_절]

[orderby_절]

SELECT m FROM Member m WHERE m.age > 18

 

2. UPDATE 문

update_절 [where_절]
UPDATE Member m SET m.age = 30 WHERE m.username = :name

3. DELETE 문

delete_절 [where_절]
DELETE FROM Member m WHERE m.age < 18
  • Member는 테이블이 아니라 엔티티 이름 (@Entity(name="이값")
  • m은 필수 별칭(alias) (as 생략 가능)
  • 대소문자 구분:
    • 엔티티와 필드: 구분함 (username ≠ USERNAME)
    • JPQL 키워드: 구분 안함 (select, SELECT 둘 다 가능)

 

집계 함수

SELECT 
    COUNT(m),     -- 총 회원 수
    SUM(m.age),   -- 나이 합
    AVG(m.age),   -- 평균 나이
    MAX(m.age),   -- 최대 나이
    MIN(m.age)    -- 최소 나이
FROM Member m

SELECT m.team.name, AVG(m.age)
FROM Member m
GROUP BY m.team.name
HAVING AVG(m.age) > 20
ORDER BY AVG(m.age) DESC

TypedQuery vs Query

구분 설명 사용 예

 

구분 설명
TypedQuery<T> 반환 타입이 명확할 때
Query 반환 타입이 다양한 경우 (e.g. username, age 같이 복합)
// 반환 타입 명확
TypedQuery<Member> query1 = em.createQuery("SELECT m FROM Member m", Member.class);

// 반환 타입 모호 (String, int 두개 반환)
Query query2 = em.createQuery("SELECT m.username, m.age FROM Member m");

 

결과 조회 API

메서드 설명 예외
메서드 설명 예외
getResultList() 결과가 0개 이상일 때 리스트 반환 없음 (0개면 빈 리스트)
getSingleResult() 결과가 정확히 1개여야 함 0개: NoResultException, 2개 이상: NonUniqueResultException

파라미터 바인딩

1. 이름 기준 바인딩 (권장)

TypedQuery<Member> query = em.createQuery(
    "SELECT m FROM Member m WHERE m.username = :username",
    Member.class
);
query.setParameter("username", "kim");

2. 위치 기준 바인딩 (비추천)

Query query = em.createQuery(
    "SELECT m FROM Member m WHERE m.username = ?1"
);
query.setParameter(1, "kim");
반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 페이징, 조인, 서브쿼리 등  (3) 2025.07.31
[JPA] 프로젝션  (2) 2025.07.31
[JPA] JPQL 소개  (2) 2025.07.30
[JPA] 값 타입 컬렉션  (1) 2025.07.25
[JPA] 값 타입과 불변 객체  (2) 2025.07.23
반응형

JPA는 CRUD 수준에서는 EntityManager.find() 나 Repository.save() 등으로 충분하지만

복잡한 조회나 조건 검색이 필요한 시점부터 다양한 쿼리 전략이 필요해집니다.

 

1. JPQL (Java Persistence Query Language)

객체를 대상으로 쿼리하는 SQL (가장 단순한 조회 방법)

  • SQL과 문법 유사하지만 테이블이 아닌 엔티티를 대상으로 쿼리
  • SELECT m FROM Member m WHERE m.age > 18
  • 내부적으로는 SQL로 변환되어 실행됨

장점

  • SQL 문법에 익숙하다면 금방 적응 가능
  • 표준 JPA 기능으로 설정 없이 사용 가능

단점

  • 동적 쿼리 작성이 어려움
    → 문자열을 직접 조합해야 함 → 유지보수 어렵고 버그 유발
  • 오타나 구조 오류는 런타임에 발견됨
List<Member> result = em.createQuery(
    "SELECT m FROM Member m WHERE m.username LIKE '%kim%'",
    Member.class
).getResultList();

2. Criteria API (JPA 표준 동적 쿼리 빌더)

자바 코드로 JPQL을 빌드하는 방식

장점

  • 동적 쿼리에 적합
  • 컴파일 타임에 타입 오류 검출 가능

단점

  • 너무 복잡하고 장황함 → 실무에서는 거의 사용하지 않음
  • 가독성과 유지보수성 떨어짐
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
Root<Member> m = query.from(Member.class);
query.select(m).where(cb.equal(m.get("username"), "kim"));
List<Member> resultList = em.createQuery(query).getResultList();

 

3. QueryDSL (실무 추천)

장점

  • 코드로 JPQL을 작성 → 문법 오류는 컴파일 시점에 검출
  • 동적 쿼리 작성이 쉽고 가독성이 좋음
  • IDE 자동완성, 리팩토링 쉬움
  • 실무에서 가장 많이 사용되는 JPA 쿼리 도구

단점

  • 초기 설정 필요 (Q클래스 생성, Annotation Processor 설정)
  • JPA에 대한 이해도 필요
QMember m = QMember.member;
List<Member> result = queryFactory
    .selectFrom(m)
    .where(m.age.gt(18))
    .orderBy(m.name.desc())
    .fetch();

4. Native SQL (순수 SQL 사용)

JPA가 지원하지 않는 DB 고유 기능 사용할 때

장점

  • 복잡한 SQL, DB 전용 문법 사용 가능 (예: CONNECT BY, WITH RECURSIVE, 힌트 등)
  • 성능 튜닝 시 유용

단점

  • SQL에 강하게 의존 → DB 이식성 X
  • 결과 매핑이 번거로움 (DTO나 엔티티 수동 매핑 필요)
String sql = "SELECT ID, NAME FROM MEMBER WHERE NAME = 'kim'";
List<Member> result = em.createNativeQuery(sql, Member.class).getResultList();

5. JDBC 직접 사용 / Spring JdbcTemplate / MyBatis 병행 사용

 

  • 단, JPA의 영속성 컨텍스트 플러시를 수동으로 해줘야 함

 

 

 

반응형

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 프로젝션  (2) 2025.07.31
[JPA] JPQL 기본 문법과 기능  (2) 2025.07.30
[JPA] 값 타입 컬렉션  (1) 2025.07.25
[JPA] 값 타입과 불변 객체  (2) 2025.07.23
[JPA] 값 타입  (1) 2025.07.22

+ Recent posts