728x90
반응형
728x90
반응형
반응형

캐시는 다음과 같은 이유로 주로 사용된다.

 

  • 성능 개선: 자주 요청되는 데이터를 빠르게 제공하여 응답 속도를 높임.
  • 부하 감소: 데이터베이스, API 서버 등 원본 소스에 대한 요청을 줄여 리소스 절약.
  • 비용 최적화: 외부 API 호출이나 고비용 데이터 처리 작업을 줄임.
  • 안정성 강화: 원본 데이터 소스 장애 시 캐시 데이터를 활용해 서비스 지속 가능.
  • 복잡한 연산 제거: 고비용 연산 결과를 캐싱하여 반복 작업 최소화.
  • 사용자 경험 향상: 빠르고 개인화된 데이터를 제공해 UX 개선.
  • 분산 시스템 효율성: 로컬 캐시와 CDN을 통해 네트워크 지연 감소.

 

따라서 캐시와 관련된 문제들은 애플리케이션 성능에 영향을 미칠 수 있다. 어떠한 문제들이 있을 수 있을까.

 

1. 캐시 미스(Cache Miss)

  • 정의: 애플리케이션이 필요한 데이터가 캐시에 없는 상황
  • 유형:
    • Cold Miss: 캐시가 초기화되거나 처음 사용될 때 발생하는 미스. 캐시에 아무 데이터가 없어서 모든 요청이 미스 처리
    • Capacity Miss: 캐시의 용량이 부족해 더 이상 데이터를 저장할 수 없는 상황에서 기존 데이터가 삭제되면서 발생하는 미스.
    • Conflict Miss: 특정 데이터가 저장될 수 있는 캐시 공간이 제한되어 있을 때 발생하는 미스입니다. 캐시에 이미 동일한 위치에 저장된 데이터가 있을 때 교체되면서 발생
  • 해결 방안: 캐시 크기 조절(키워서 미스 안 나게), 효율적인 캐싱 전략 설정, LRU(Least Recently Used)나 LFU(Least Frequently Used) 같은 캐시 교체 알고리즘 도입 등

2. 캐시 스탬피드(Cache Stampede)

  • 정의: 캐시가 만료된 경우 다수의 요청이 동시에 캐시 미스를 일으켜 데이터베이스나 원본 서버에 과부하를 유발
  • 예시: 특정 데이터의 캐시가 만료되었을 때, 많은 사용자나 프로세스가 동시에 캐시 된 데이터를 요청하며 원본 서버에 큰 부하가 걸림
  • 해결 방안:
    • 락(Locking): 캐시가 만료되었을 때 첫 번째 요청자만 원본 데이터에 접근하도록 락을 걸어 다른 요청자가 대기하도록.. 대기하고 나서는 캐시에서 가져가게
    • 백오프(Backoff): 캐시 업데이트를 요청하는 다수의 주체들이 동일한 시점에 갱신 요청을 보내는 것을 방지하기 위해, 재시도 전에 일정한 대기 시간을 설정하는 방식
      • API 호출 실패 후 1초 → 2초 → 4초로 대기 시간을 증가시키며 재요청.
      • 캐시가 만료된 데이터를 가져오기 위해 여러 클라이언트가 요청을 보낼 때, 요청 간의 간격을 두어 스탬피드를 방지.
    • 예상 만료 처리(Early Expiration): 캐시의 데이터가 만료되기 전에 미리 갱신 작업을 수행하여 캐시 미스 상황을 방지하는 방식
      • TTL(Time To Live) 설정 시, 실제 만료 시간(T)보다 짧은 시간(T-E)으로 갱신 작업을 예약.
      • 백그라운드 작업을 통해 갱신 작업 수행.
    • 탄력적 TTL: 캐시의 TTL을 랜덤하게 설정하여 여러 캐시 항목이 동시에 만료되지 않도록 조정(TTL skew)

3. 캐시 독(Cache Thundering Herd)

  • 정의: 특정 캐시 키에 대한 동시 접근이 일어나거나 캐시가 갱신되어야 할 때 한 번에 모든 스레드가 동일한 데이터를 요청하는 문제입니다. 캐시 스탬피드와 유사하지만 주로 특정 데이터에 집중된 대량 요청
  • 문제상황
    • 백엔드 부하 증가: 모든 요청이 원본 소스(DB, API 등)에 쏠려 장애를 유발.
    • 성능 저하: 응답 속도가 느려지고, 전체 시스템 성능이 저하.
    • 서비스 가용성 위협: 심한 경우 서비스 중단으로 이어질 수 있음.
  • 해결 방안: 분산 락, 지연 갱신(Lazy Update) 같은 접근을 통해 특정 리소스에 동시 접근하는 것을 방지

 

위와 같이 여러 해결책이 있지만 대체적으로 즉각적인 해결책이 되지 못한다.(응답 지연의 가능성)

캐시 웜업(Cache Warming)

캐시 웜업은 애플리케이션이나 서비스가 시작되거나 재시작될 때, 캐시를 미리 채워두는 작업. 이를 통해 서비스 초기 상태에서 캐시 미스(Cache Miss)로 인한 성능 저하를 방지하고, 원본 데이터 소스(DB, API 등)에 대한 부하를 줄일 수 있다.

캐시 웜업의 필요성

 

  • 초기 캐시 미스 문제 방지 -> 초기 성능 향상
    • 애플리케이션이 시작된 직후 캐시가 비어 있는 상태에서는 모든 요청이 원본 데이터 소스로 전달됨.
    • 갑작스러운 트래픽 증가로 원본 소스(DB/API)가 과부하 상태에 빠질 수 있음.
  • 서비스 안정성 향상, 사용자 경험 개선
    • 캐시 웜업으로 자주 사용되는 데이터를 미리 준비해 두면 초기 요청 처리 속도가 빨라지고, 사용자 경험이 개선됨.
  • 트래픽 분산
    • 캐시가 미리 채워져 있으면 요청이 분산되므로, 백엔드 부하가 줄어듦.

 

캐시 웜업의 일반적인 방법

  1. 프리로드(Preloading): 시스템 초기화나 캐시 시작 시점에 미리 정해진 데이터 집합을 캐시에 로드. 예를 들어, 자주 요청되는 데이터나 중요한 설정값을 미리 캐시에 넣어둔다.
  2. 배치 처리: 배치 작업을 통해 일정 시간 간격으로 특정 데이터를 캐시에 로드하여 캐시가 항상 최신 상태를 유지하도록 한다.
    • @Scheduled(fixedDelay = 300000) 혹은 별도의 배치 프로그램 
    • 만료 전 갱신으로 캐시만을 사용하여 응답하게 끔하여 트래픽 전이 되지 않고 응답 지연 없게 처리; 1분 주기 배치
      • 캐시 웜업 대상 누락 시 cache stampede 발생!
      • 캐시 웜업 대상 조회의 자동화 필요
  3. API 또는 관리자 도구 활용: 관리자 페이지나 별도의 API를 통해 수동으로 캐시를 웜업
  4. 온디맨드 웜업(On-Demand Warming): 실제 사용자가 특정 데이터를 요청하기 직전에, 예측 분석을 통해 해당 데이터가 자주 요청될 것으로 판단될 경우 이를 캐시에 미리 로드한다.
    1. 데이터 분석 시 "호출 횟수(view count) / 최근 호출 시간" 통계화 고려(날짜-id-view)
    2. 로컬 디비에 관련 데이터 쌓고(레디스 부하 발생 가능)
    3. 주기적으로 레디스에 올려
    4. 관련하여 레디스 자료구조 활용(분산 자료구조인 hash)
    5. 레디스에 올리고 로컬 삭제
    6. 해당 데이터 기반으로 캐시를 갱신한다

프리로드 예시(ApplicationRunner 이용)

@Component
public class CacheWarmupRunner implements ApplicationRunner {
    @Autowired
    private CacheService cacheService;
    @Autowired
    private DataService dataService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<Data> dataList = dataService.getFrequentlyAccessedData();
        dataList.forEach(data -> cacheService.put(data.getKey(), data));
    }
}

캐시 웜업의 장단점

  • 장점: 캐시 미스로 인한 부하를 줄이고, 사용자에게 보다 빠른 응답 시간을 제공
  • 단점: 데이터 신선도 이, 필요성이 낮은 데이터의 경우 불필요한 리소스 사용이 될 수 있음

 

하이브리드 캐시

: 로컬 캐시 + 리모트 캐시

  • 로컬 캐시로: 빈번하게 조회되고/공통 데이터/데이터 크기가 작고/업데이트 빈도가 낮은 데이터
  • 리모트 캐시: 최신 상태 데이터/중앙 관리(마스터 캐시)
  • 디비 -> 리모트 -> 로컬 순으로 갱신

주의사항

  • 로컬 캐시마다 TTL이 다 다르기 때문에 데이터 일관성을 유지하기 힘듦
  • 데이터 변경 시 누가 old로 내리는지 찾기 힘듦
  • 즉시 반영 시에는 별도의 만료 프로세스 필요
    • 주키퍼 활용해서 데이터 변경 시 이벤트 발행하여 동기화 가능
    • 변경 이벤트 ->리모트 캐시 만료 처리 -> 로컬 캐시 만료 처리

 

참고

초단위 캐시 웜업(준 실시간)

로컬 캐시 TTL 다 달라.. 초단위 웜업 시 rabbit 의 DLQ 활용 가능

1~60초 각각의 만료시간을 가진 메세지 발행

https://youtu.be/BUV4A2F9i7w?si=eVdHEBzJ3h8bZOIu


캐시 갱신 정책

캐시와 원본 데이터 간의 동기화 방법.

  1. Write-Through
    • 캐시에 쓰는 동시에 원본 데이터도 즉시 갱신.
    • 장점: 데이터 일관성 보장.
    • 단점: 쓰기 성능 저하.
  2. Write-Behind (Write-Back)
    • 캐시에 먼저 쓰고 나중에 원본 데이터 갱신.
    • 장점: 쓰기 성능 향상.
    • 단점: 데이터 손실 가능성.
  3. Read-Through
    • 캐시에 없을 때 원본 데이터를 읽고 캐시에 저장.
    • 장점: 자동 갱신.
    • 단점: 초기 요청 지연.
  4. Manual Update
    • 명시적으로 캐시를 갱신.
    • 장점: 제어 용이.
    • 단점: 관리 복잡.

 

캐시 만료 정책

캐시 데이터의 유효 기간을 설정하는 방법.

  1. TTL (Time-To-Live)
    • 데이터가 캐시에 저장된 후 유지 시간 설정.
    • 예: 30초 후 만료.
  2. TTI (Time-To-Idle)
    • 마지막 접근 후 일정 시간 지나면 만료.
    • 예: 10분 동안 미사용 시 삭제.
  3. Fixed Expiry
    • 특정 시간에 모든 데이터 만료.
    • 예: 매일 자정 초기화.
  4. Forever Cache
    • 만료 없이 지속적으로 유지.
    • 데이터 변경이 거의 없을 때 사용.
728x90
반응형

'architecture > micro service' 카테고리의 다른 글

[Dead Letter] PDL, CDL  (0) 2024.11.14
E2E(end to end) 테스트  (0) 2024.11.13
대용량 데이터 처리 고민  (1) 2024.11.10
transaction outbox pattern + polling publisher pattern  (0) 2024.11.07
2PC vs 2PL  (1) 2024.11.06
반응형

Dead Letter (데드 레터)

"Dead Letter"란, 메시지 큐에서 소비자(consumer)에게 전달할 수 없거나 처리 중에 문제가 발생하여 처리가 불가능한 메시지를 말합니다. 보통 메시지를 큐에 전달하려 했으나 여러 번 재시도해도 실패하거나 타임아웃이 발생한 경우, 해당 메시지를 일반 큐에서 제거하고 "Dead Letter Queue (DLQ)"라고 불리는 별도의 큐로 이동시킵니다.

이 과정은 메시지 처리 실패 시 메시지를 손실하지 않고 안전하게 보관하여 나중에 점검하거나 문제를 해결할 수 있도록 하기 위함입니다.

Producer Dead Letter (생산자 데드 레터)

"Producer Dead Letter"는 메시지를 큐로 전송하는 생산자(producer) 측에서 발생한 문제로 인해 큐에 정상적으로 전달되지 못한 메시지를 의미합니다. 프로듀서가 메시지를 발행할 때 네트워크 오류, 메시지 큐 자체의 장애, 또는 메시지 크기 제한 등으로 인해 큐에 메시지가 도달하지 못하는 경우, 이 메시지를 Dead Letter로 처리할 수 있습니다.

이러한 상황에서는 메시지를 버리거나 다른 DLQ에 저장하여 생산자 측 문제를 추적하고 나중에 재전송할 수 있습니다.

Consumer Dead Letter (소비자 데드 레터)

"Consumer Dead Letter"는 소비자가 특정 메시지를 처리하는 도중 실패하거나 오류가 발생하여 메시지를 정상적으로 처리하지 못한 경우를 의미합니다. 일반적으로 메시지 큐는 메시지가 소비자에게 여러 번 재시도된 후에도 실패하는 경우, 해당 메시지를 "Dead Letter Queue"로 이동시킵니다.

이러한 "Consumer Dead Letter"는 일반적으로 소비자가 처리할 수 없는 메시지(잘못된 형식, 누락된 데이터, 또는 예상치 못한 내용)를 담고 있어, 이를 DLQ에 넣고 이후 개발자나 운영자가 수동으로 처리하거나 점검할 수 있습니다.


Kafka에서 Dead Letter Queue 설정 방법

Kafka 자체에는 기본적으로 DLQ 기능이 없지만, 컨슈머 애플리케이션 쪽에서 구현 가능하다.

  1. DLQ 토픽 생성:
    • Kafka에 별도의 DLQ 토픽을 생성.
    • 예를 들어, 원본 토픽이 my-topic이라면, DLQ 토픽은 my-topic-dlq와 같은 방식으로 이름을 지정
  2. 컨슈머 예외 처리 및 DLQ 전송:
    • 메시지 처리 중 오류가 발생할 때 예외 처리를 통해 실패한 메시지를 DLQ 토픽으로 전송
    • 예를 들어, 메시지 처리 로직에서 예외가 발생하면 KafkaProducer를 사용하여 DLQ 토픽에 메시지를 전송
public void processMessage(String message) {
    try {
        // 메시지 처리 로직
    } catch (Exception e) {
        // DLQ로 메시지 전송
        ProducerRecord<String, String> record = new ProducerRecord<>("my-topic-dlq", message);
        kafkaProducer.send(record);
        log.error("Failed to process message, sent to DLQ", e);
    }
}

 

3. Kafka Connect 사용:

Kafka Connect는 Kafka와 다른 시스템 간에 데이터를 쉽게 전송(스트리밍)할 수 있게 해주는 프레임워크이다. 이를 통해 다양한 데이터베이스, 파일 시스템, 클라우드 서비스, 로그 시스템 등과 Kafka를 통합할 수 있음.

  • Source Connectors: 외부 시스템에서 Kafka로 데이터를 전송하는 커넥터
    • 예를 들어, 데이터베이스에서 데이터를 Kafka로 보내거나, 로그 파일에서 Kafka로 스트리밍
  • Sink Connectors: Kafka에서 외부 시스템으로 데이터를 전송하는 커넥터
    • 예를 들어, Kafka에서 받은 데이터를 데이터베이스나 HDFS, Elasticsearch 등으로 보냄

Kafka Connect는 standalone modedistributed mode로 실행할 수 있으며, connector configuration을 통해 여러 외부 시스템과의 통합을 자동화함.

Dead Letter Queue는 메시지 처리 중 오류가 발생한 메시지를 별도의 큐에 저장해두고, 후속 처리를 통해 문제를 해결할 수 있게 해주는 시스템이다. Kafka Connect에서 DLQ는 주로 메시지 처리 실패 시 데이터를 안전하게 보관하고, 문제가 해결된 후 재처리를 할 수 있도록 도와줌.

Kafka Connect DLQ 기능은 주로 dead-letter-policy와 관련된 설정을 통해 구성된다. 예를 들어, 메시지 처리에 실패할 경우 해당 메시지를 DLQ로 이동시켜 추가적인 검토 및 처리가 가능하게 함.

  • Kafka Connect 프레임워크와 Dead Letter Queue 기능을 지원하는 커넥터를 사용하고(ex. Debezium) 커넥터 설정에서 errors.deadletterqueue.topic.name을 지정하여 특정 토픽을 DLQ로 사용 가능.

 

RabbitMQ에서 Dead Letter Queue 설정 방법

RabbitMQ는 기본적으로 DLQ 기능을 지원함

  1. DLQ용 큐 생성:
    • Dead Letter를 위한 별도의 큐를 생성합니다.
    • 예를 들어 my-queue-dlq라는 이름의 큐를 생성
  2. Dead Letter Exchange 생성:
    • x-dead-letter-exchange와 같은 DLX(Dead Letter Exchange)를 생성하여 메시지를 전송할 곳을 설정. DLX는 메시지 처리 실패 시, 또는 메시지가 만료된 경우 메시지를 다른 큐로 라우팅하는 역할을 함.
  3. 원본 큐 설정에 DLX 추가:
    • 원본 큐를 설정할 때 x-dead-letter-exchange 속성을 추가하여 DLX를 지정.
    • 예를 들어, my-queue라는 원본 큐가 있을 때 해당 큐의 메시지가 실패하면 DLX를 통해 DLQ 큐로 메시지가 전달
    • 메시지 만료 시간(x-message-ttl)을 설정하여 특정 시간이 지나면 DLQ로 이동하도록 설정 가능
// Dead Letter Exchange 및 Queue 설정
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "my-dlx");  // 익스채인지 설정
args.put("x-dead-letter-routing-key", "my-queue-dlkey"); // 라우팅 키 설정
// 메시지 만료 시간 설정 (TTL)
args.put("x-message-ttl", 60000);  // 60초 후 메시지 만료

// 원본 큐 선언
channel.queueDeclare("my-queue", true, false, false, args); // 큐에 엮음

// Dead Letter Queue 선언
channel.queueDeclare("my-queue-dlq", true, false, false, null);
channel.queueBind("my-queue-dlq", "my-dlx", "my-queue-dlkey"); // 큐, 익스채인지, 라우팅 키

 

  • "my-queue"라는 이름의 원본 큐를 선언
  • args는 위에서 설정한 Dead Letter Exchange 및 라우팅 키를 포함하는 속성. 이 속성에 따라 메시지가 만료되거나 처리 실패 시 DLX를 통해 my-queue-dlq로 전달됨
  • "my-queue-dlq"라는 이름의 Dead Letter Queue(DLQ)를 선언. 이 큐는 Dead Letter로 전달된 메시지를 저장하는 역할을 함
  • channel.queueBind는 "my-dlx" DLX와 "my-queue-dlq" 큐를 지정한 라우팅 키 "my-queue-dlkey"를 통해 연결하여, DLX에서 발생한 Dead Letter 메시지가 이 큐에 전달되도록 설정

Dead Letter가 Dead Letter Queue 에 쌓이면 그 다음 단계는?

1. Dead Letter 모니터링 및 알림

  • DLQ에 메시지가 쌓이면 이를 모니터링하고, 일정 수 이상이 되거나 특정 조건을 만족하면 자동으로 알림이 오도록 설정
  • 알림 시스템을 통해 개발자나 운영팀이 문제를 인식하고 원인을 분석

2. 원인 분석

  • DLQ에 쌓인 메시지를 검토하여 처리 실패 원인을 파악.
    • 메시지 형식 오류 또는 잘못된 데이터
    • 처리 로직의 버그 또는 예외
    • 메시지 크기 초과나 타임아웃
  • 로그를 통해 오류 상황을 구체적으로 분석하거나, DLQ에 쌓인 메시지 내용 자체를 검토하여 어떤 부분에서 문제가 발생했는지 확인

3. 수동 재처리 또는 자동 재처리

  • 문제가 해결된 후 DLQ의 메시지를 다시 원래 큐에 넣어 재처리 가능. 이를 자동화할 수도 있지만, 데이터 일관성을 위해 보통 수동으로 처리
  • 자동화 시 재처리 시나리오를 신중하게 정의해야 하며, 동일한 실패가 발생하지 않도록 로직을 수정

 

728x90
반응형
반응형

E2E 테스트?

여러 구성 요소가 함께 예상대로 작동하는지 확인하는 테스트

  • 환경 유사성: E2E 서버는 실제 운영 환경을 최대한 유사하게 설정하여, 실제 서비스에서 발생할 수 있는 문제를 미리 발견할 수 있도록 합니다.
  • 자동화 테스트: Selenium, Cypress, Playwright 같은 도구를 사용하여 사용자의 인터랙션을 자동으로 시뮬레이션하고, 여러 계층 간의 흐름을 확인합니다.
  • 데이터 일관성: 일부 E2E 서버는 테스트 데이터베이스나 샌드박스 환경을 사용하여 실제 운영 데이터와 분리된 상태에서 테스트를 수행합니다.
  • CI/CD 통합: E2E 테스트는 CI/CD 파이프라인에 통합되어 배포 과정에서 발생할 수 있는 문제를 조기에 발견할 수 있도록 자동으로 실행됩니다.

 

E2E 서버 = E2E 테스트를 위한 서버

1. 테스트 환경 구성

E2E 서버는 운영 환경과 유사한 환경을 제공하는 것이 중요합니다. 이를 위해 다음과 같은 환경을 구성합니다.

  • 테스트용 데이터베이스: 실제 데이터베이스와 분리된 테스트용 데이터베이스를 설정하여 데이터 충돌을 방지하고 테스트 데이터를 자유롭게 사용할 수 있게 합니다.
  • 테스트 API 엔드포인트: E2E 서버는 운영 API와 구분된 테스트용 API 엔드포인트를 설정해 두는 것이 좋습니다.
    • url을 또 따는건 아닌 것 같음, 같은 소스에 profile 만 stage 이런식으로 주는게 좋을 것 같음. 매번 주소 바꾸고 관리하는거 불편
  • 샌드박스 모드: 외부 API를 사용하는 경우, 실제 외부 API와 통신하지 않고 샌드박스 모드(테스트 모드)로 설정하여 과금 등의 위험을 줄입니다.
    • 테스트 용 api를 제공해주는게 제일 좋은 것 같음.. 그렇지 않다면.. mock server를 띄워서 상황별로 일정한 값을 내리도록 세팅해야 할 듯
  • 인프라 설정: 실제 환경을 모방하여 Docker 등을 활용해 마이크로서비스 아키텍처를 구성하거나 CI/CD 파이프라인에서 테스트 환경을 동적으로 띄우는 방법을 사용할 수 있습니다.

2. 자동화 도구 설정

자동화 도구는 사용자의 행동을 시뮬레이션하고, 전체 흐름을 테스트하는 데 중요한 역할을 합니다. 몇 가지 인기 있는 도구는 다음과 같습니다.

  • Selenium: 주로 웹 애플리케이션의 UI 테스트를 자동화하는 데 사용되며, 다양한 브라우저를 지원합니다.
  • Cypress: 현대 웹 애플리케이션 테스트에 적합한 빠르고 간편한 도구로, 백엔드와의 상호작용을 테스트하는 데 강점을 가집니다.
  • Playwright: 다양한 브라우저와 모바일 디바이스까지 지원하며, 최신 웹 애플리케이션에 적합한 자동화 도구입니다.
  • soup ui: 사용해서 api 순차 호출하고 전 결과를 후 api 에 사용 가능. 시나리오별로 관리 가능

이 도구들을 CI/CD 파이프라인과 통합하여 특정 조건에서 E2E 테스트가 자동으로 실행되도록 설정할 수 있습니다.

3. 테스트 코드 작성

테스트 코드는 사용자가 실제로 애플리케이션을 사용하는 흐름을 최대한 비슷하게 재현해야 합니다.

  • 시나리오 설계: 로그인, 상품 조회, 결제 등 사용자의 주요 작업 시나리오를 정리하고, 순서대로 테스트가 진행되도록 합니다.
  • 상태 관리: 테스트 중 서버의 상태(예: 로그인 상태, 장바구니에 담긴 상품 등)를 일관되게 관리하기 위해 각 테스트 케이스를 독립적으로 작성하거나, 필요한 경우 테스트 실행 전후에 초기화 과정을 둡니다.
  • 에러 및 예외 처리 테스트: 정상적인 흐름뿐 아니라, 잘못된 입력이나 서버 오류 등 다양한 예외 상황을 테스트하여 애플리케이션이 안정적으
  • 로 작동하는지 확인합니다.

4. CI/CD 통합

테스트가 성공적으로 작성되었다면, 이를 CI/CD 파이프라인에 통합하여 배포 전 자동으로 실행되도록 설정합니다.

  • GitHub Actions, Jenkins, GitLab CI 등과 같은 CI/CD 도구를 사용하여 코드가 변경될 때마다 자동으로 E2E 테스트를 실행할 수 있습니다.
  • 테스트 결과가 실패하면 알림을 받도록 하여, 문제를 빠르게 확인하고 수정할 수 있도록 합니다.

cypress 예시

npm install cypress --save-dev

위 명령어로 설치하고 루트에 cypress.json 파일을 생성하고 아래처럼 api서버 url 설정

{
  "baseUrl": "http://localhost:8080"
}

테스트 파일 생성: cypress/integration/user_spec.js 파일을 생성하고 아래와 같이 작성

describe('User API E2E Test', () => {
  it('should create a new user and retrieve the user list', () => {
    const user = { name: 'John Doe', email: 'john.doe@example.com' };

    // Create a new user
    cy.request('POST', '/users', user)
      .its('status')
      .should('eq', 200);

    // Retrieve the user list and verify the new user exists
    cy.request('/users').then((response) => {
      expect(response.status).to.eq(200);
      expect(response.body).to.have.length.greaterThan(0);
      expect(response.body.some(u => u.email === user.email)).to.be.true;
    });
  });
});

 

  1. POST /users 엔드포인트에 사용자 데이터를 보내 사용자를 생성
  2. GET /users 엔드포인트로 모든 사용자를 조회하고, 방금 생성한 사용자가 포함되어 있는지 확인

아래 명령어로 cypress 실행, Cypress 창이 열리면 user_spec.js 테스트 파일을 선택

npx cypress open

 

github actions와 연동

테스트를 해야하는 서버의 코드에 .github/workflows/e2e-test.yml 파일생성하고 다음 설정을 추가

(gitHub는 .github/workflows/ 폴더에 있는 YAML 파일을 모두 인식하여 워크플로우로 처리)

name: E2E Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  e2e-test:
    runs-on: ubuntu-latest

    services:
      db:
        image: mysql:5.7
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: e2e_test_db
        ports:
          - 3306:3306
        options: >-
          --health-cmd="mysqladmin ping --silent"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3

    steps:
      - name: Check out the code
        uses: actions/checkout@v2

      - name: Set up JDK 11
        uses: actions/setup-java@v2
        with:
          java-version: '11'

      - name: Install dependencies
        run: ./gradlew build -x test

      - name: Start Spring Boot application with E2E profile
        run: ./gradlew bootRun -Dspring.profiles.active=e2e &
        env:
          SPRING_DATASOURCE_URL: jdbc:mysql://localhost:3306/e2e_test_db
          SPRING_DATASOURCE_USERNAME: root
          SPRING_DATASOURCE_PASSWORD: password
        timeout-minutes: 2

      - name: Wait for Spring Boot to start
        run: |
          timeout 60s bash -c "until echo > /dev/tcp/localhost/8080; do sleep 5; done"

      - name: Run Cypress tests
        uses: cypress-io/github-action@v2
        with:
          start: npm start
        env:
          CYPRESS_BASE_URL: http://localhost:8080

main 브랜치에 머지하거나 pr 날리면 cypress 실행하도록 작성

728x90
반응형

'architecture > micro service' 카테고리의 다른 글

[캐시] 캐시 관련 문제들과 캐시웜업  (0) 2024.11.17
[Dead Letter] PDL, CDL  (0) 2024.11.14
대용량 데이터 처리 고민  (1) 2024.11.10
transaction outbox pattern + polling publisher pattern  (0) 2024.11.07
2PC vs 2PL  (1) 2024.11.06
반응형

1. 대용량 데이터를 처리할 때의 주요 고려사항은 무엇인가요?

  • 대용량 데이터 처리에서는 확장성, 성능, 데이터 일관성 등을 고려해야 합니다.
    • 데이터를 효율적으로 저장하고 처리하기 위해 분산 시스템을 활용하고(확장성)
    • 캐싱 전략을 사용하여 읽기 성능을 높입니다.
  • 데이터 중복 방지와 실패 시 복구 전략이 필요합니다.
    • 중복방지: 멱등성 API(PUT); 디비에 UNIQUE KEY잡아서 중복 데이터 삽입 안되게
      • Redis 또는 Memcached를 사용해 데이터 처리 전 고유 키를 캐싱하여 중복 여부를 빠르게 확인할 수 있습니다. 예를 들어, 특정 ID가 Redis에 존재하면 중복으로 간주하고 처리하지 않는 방식입니다.
    • 복구 전략
      • 트랜잭션과 롤백 (Transaction & Rollback)
        • RDBMS의 트랜잭션 기능을 사용해 작업 단위로 처리하고, 실패 시 모든 작업을 롤백하여 데이터 일관성을 유지합니다. 트랜잭션 단위가 클 경우에는 단계별로 커밋을 처리하는 Savepoint를 활용할 수도 있습니다.
        • NoSQL 데이터베이스에서도 MongoDB와 같은 시스템은 단일 문서 수준에서 트랜잭션을 지원하고, Cassandra는 클라이언트 라이브러리에서의 조정을 통해 일부 트랜잭션 효과를 제공할 수 있습니다.
      • 분산 트랜잭션 및 분산 락
        • 여러 시스템 간 트랜잭션을 위해 2PC (Two-Phase Commit) 또는 SAGA 패턴을 사용합니다. SAGA 패턴은 트랜잭션 단위로 실행되는 작업을 분리하고, 오류 발생 시 보상 작업을 수행해 데이터 일관성을 유지합니다.
        • 데이터 충돌을 방지하기 위해 분산 락을 적용할 수 있습니다. 예를 들어, Redis의 SETNX 명령을 사용해 분산 잠금을 구현하여, 하나의 리소스에 동시에 접근하는 것을 방지할 수 있습니다.
      • 재시도 및 지연 재시도 (Retry & Backoff)
        • 네트워크 문제나 일시적인 오류에 대해 재시도 정책을 설정하여 실패한 요청을 다시 시도할 수 있습니다. 무조건 재시도하기보다 지수적 백오프(Exponential Backoff) 전략을 사용해 지연 시간을 점진적으로 늘리면 서버에 부담을 줄일 수 있습니다.
          • 과도한 요청 방지: 동일한 요청을 반복적으로 보내는 것을 막아 서버가 과부하에 걸리지 않도록 하여, 서버가 일정한 시간 동안 안정적으로 요청을 처리할 수 있습니다.
          • 서버 복구 시간 확보: 지연 시간이 늘어날수록 서버가 안정화되거나 부하를 해소할 시간이 생기므로, 문제 해결 후 요청을 성공적으로 처리할 가능성이 커집니다.
          • 네트워크 효율성 향상: 클라이언트와 서버 간의 불필요한 트래픽을 줄이고, 네트워크 자원을 효율적으로 사용하게 합니다.
          • 트랜잭션 유지 시간이 너무 길어지면 잠금 자원이나 연결 자원이 오래 점유되어 다른 요청이나 트랜잭션에 영향을 줄 수
            • 최대 재시도 횟수와 백오프 한계 설정(무한히 기다리지 않도록)
            • 장기 트랜잭션을 여러개의 작은 단위로 분리하거나 비동기 처리하여 트랜젝션이 길어지지 않도록
            • 트랜젝션 타임아웃 설정
            • 회복 가능한 트랜잭션 설계
              • 장애나 재시도가 필요한 경우에도, 중간까지 완료된 트랜잭션 부분이 유지되고 나머지 작업을 이어갈 수 있도록 분산 트랜잭션 관리SAGA 패턴을 활용해 트랜잭션을 보다 유연하게 설계합니다.
        • Circuit Breaker 패턴을 함께 사용해 오류가 지속될 경우 특정 시간 동안 요청을 차단해 전체 시스템의 안정성을 높일 수 있습니다.
      • 데이터 복제 및 백업
        • 데이터 손실을 방지하기 위해 백업 및 복제 전략을 설정합니다. 예를 들어, RDBMS에서는 정기적인 백업과 함께 로그 기반 복구(Log-based Recovery)를 사용해 장애 발생 시 특정 시점으로 데이터를 복원할 수 있습니다.
        • 분산 시스템의 경우 데이터 복제를 통해 여러 노드에 데이터를 분산 저장하여 데이터 유실 가능성을 줄입니다. Cassandra나 MongoDB와 같은 분산 DB에서는 노드 간 자동 복제를 지원해 복구성을 높입니다.
      • 이벤트 소싱과 로그 기반 복구
        • 이벤트 소싱을 통해 상태 변화가 발생할 때마다 이벤트를 저장해, 장애 발생 시 해당 이벤트를 재생하여 이전 상태로 복구할 수 있습니다.
        • 로그 기반 복구 시스템은 Kafka와 같은 메시지 큐에 이벤트를 기록해 실시간으로 복구할 수 있으며, 장애가 발생해도 로그를 재생하여 데이터 상태를 원래대로 복원할 수 있습니다.
  • 마지막으로 비용 절감 측면에서 클라우드 서비스를 활용하거나 데이터 파이프라인의 최적화가 필요할 수 있습니다.

2. 대용량 데이터를 실시간으로 처리해야 한다면 어떤 아키텍처를 선택할 것인가요?

  • 실시간 데이터 처리가 필요한 경우 이벤트 기반 아키텍처(EDA)와 스트리밍 처리 시스템을 선호합니다. Apache Kafka와 같은 메시지 브로커를 통해 데이터를 스트리밍으로 전송하고, Apache Flink 또는 Spark Streaming을 사용해 데이터를 실시간으로 처리합니다. 이렇게 하면 지연을 최소화하면서도 높은 처리량을 유지할 수 있습니다.
  • CQRS: 조회와 데이터변경을 시스템적으로 분리하여 각각 최고의 성능을 낼 수 있도록 개선, 독립적으로 확장 가능하도록 개발
    • 변경: RDBMS, 조회: MONGO, ELASTIC..

 

3. 대용량 데이터 처리 중 병목현상을 해결한 경험이 있나요?

  • 디비
    • 이전 프로젝트에서 MySQL 데이터베이스에서 많은 데이터를 읽어와야 하는 작업이 있었는데, 특정 쿼리에서 병목 현상이 발생했습니다. 이를 해결하기 위해 인덱스를 최적화하고, 비동기 처리를 도입해 읽기 작업을 분산했습니다. 또한, 캐싱 레이어를 추가해 반복되는 읽기 작업을 줄였고, 결과적으로 처리 속도를 크게 향상시켰습니다.
  • 서비스
    • 긴 트랜젝션을 여러 개의 작은 트랜젝션으로 나눔
    • 트랜젝션 안에 외부 api 호출이 있어
      • api호출 성공 시 db작업하도록 개선
      • transaction outbox pattern + polling publish pattern으로 바꾼 적 있음

 

4. MapReduce와 같은 분산 처리 기법을 설명해 주시고, 이를 언제 사용하면 좋을까요?

MapReduce는 대규모 데이터를 분산하여 처리할 수 있게 해주는 프레임워크입니다. 데이터를 맵(Map) 단계에서 분산하여 처리하고, 리듀스(Reduce) 단계에서 그 결과를 통합합니다. 주로 배치 작업에 적합하며, 대규모 로그 분석, 대량의 텍스트

데이터 처리, ETL 작업 등에 사용됩니다.

 

5. 대용량 데이터의 효율적 관리를 위해 어떤 데이터베이스를 사용할 것인가요?

데이터 특성과 처리 요구 사항에 따라 데이터베이스를 선택

  • 실시간 읽기 및 쓰기가 빈번한 경우 : Redis와 같은 인메모리 데이터베이스를 고려
  • 로그나 이벤트 데이터를 저장 :  쓰기 성능확장성이 중요한데, 이러한 요구에 맞는 데이터베이스로는 Cassandra와 MongoDB, Elasticsearch 등
    • Cassandra
      • 분산형 NoSQL 데이터베이스로, 특히 칼럼 패밀리(Column Family) 기반의 데이터 모델을 사용하는 시스템. 높은 쓰기 성능과 수평 확장성 덕분에 대량의 로그 데이터를 빠르게 저장 가능. 분산 구조로 데이터가 여러 노드에 분산되어 저장되고 복제되어 고가용성과 안정성이 높습니다.
      • 적합한 경우: 대규모의 로그 데이터를 수집하고 분석하는 경우, 트래픽이 많이 발생하는 IoT 데이터 또는 웹 트래픽 로그 등에서 효율적입니다.
      • 장점: 특히 쓰기 성능이 뛰어나며 노드 간의 데이터 복제와 장애 허용성이 우수해, 고가용성 환경에서도 데이터를 안정적으로 처리할 수 있습니다. 노드 추가 시 용량이 수평으로 확장되며 성능 저하 없이 대규모 데이터를 처리할 수 있습니다.
    • MongoDB
      • 문서 기반 NoSQL 데이터베이스로, JSON 형식의 유연한 데이터 구조를 지원합니다. 복잡한 스키마를 요구하지 않기 때문에 로그 데이터의 다양한 필드와 동적 스키마를 저장하는 데 적합합니다.
      • 적합한 경우: 애플리케이션 로그, 이벤트 데이터를 JSON 형태로 저장해 실시간으로 조회하고 분석해야 하는 경우. 대화형 애플리케이션에서 발생하는 로그 데이터와 이벤트 처리에 적합합니다.
      • 장점: 샤딩(데이터 분산 저장)을 통해 수평 확장이 가능하며, 다양한 인덱싱을 통해 실시간 쿼리에 적합한 구조를 제공합니다.
    • Elasticsearch
      • 분산형 검색 엔진으로 로그와 이벤트 데이터를 저장하고, 실시간 검색과 분석을 수행하는 데 특화된 시스템입니다. Kibana와 함께 사용하면 데이터를 시각화할 수 있어 로그 모니터링과 분석에 특히 유용합니다.
      • 적합한 경우: 로그 모니터링, 애플리케이션 성능 관리(APM), 보안 로그 분석, 실시간 검색 등이 필요한 경우. ELK(Elasticsearch, Logstash, Kibana) 스택으로 많이 사용됩니다.
      • 장점: 실시간 검색과 시각화 기능이 강력하며, 텍스트 기반의 로그 데이터에서 유용한 인사이트를 빠르게 얻을 수 있습니다.
  • 데이터 일관성이 중요한 트랜잭션성 데이터 : MySQL, PostgreSQL과 같은 RDBMS가 적합

6. 대용량 데이터 처리에서 성능을 최적화하기 위해 사용할 수 있는 기법에는 어떤 것들이 있나요?

아키텍쳐 적인 방법:

  • 캐싱 레이어를 추가해 빈번히 조회되는 데이터를 미리 저장
  • 비동기 처리를 통해 응답시간을 줄여 타임아웃 방지 
    • 네트워크 요청, 파일 IO등의 시간이 오래걸리는 작업을 비동기로 처리하면 메인 스레드가 다른 작업을 수행할 수 있어 리소스를 효율적으로 사용할 수 
  • 배치 처리로 시스템 자원을 효율적으로 사용
    • 데이터를 한꺼번에 처리해 트랜잭션 관리나 데이터 일관성 유지에 유리
    • 주로 비업무 시간이나 서버 부하가 적은 시간대에 작업을 수행해 시스템 자원을 효율적으로 사용

디비적인 방법:

  • 인덱스를 적절히 사용하여 데이터 검색 속도를 높입니다.
  • 데이터 파티셔닝과 샤딩을 통해 데이터베이스 부하를 분산

7. 대용량 데이터를 다룰 때 발생할 수 있는 장애 및 복구 전략에 대해 설명해 주세요.

대용량 데이터 시스템에서 장애가 발생할 경우, 데이터 유실을 방지하고 빠르게 복구하는 것이 중요

서비스적:

  • 디비 등 데이터 복제를 통해 고가용성을 확보
  • Kafka와 같은 시스템에서는 메시지 리플레이를 통해 복구
  • Dead Letter Queue (DLQ)와 Retry 메커니즘을 통해 이벤트 처리 실패에 대비.

인프라적:

  • 장애가 발생했을 때 특정 노드로 트래픽을 우회하거나, 백업 데이터에서 복원
  • 클라우드 서비스를 사용할 경우 데이터 센터의 지역 분산(재해복구 관련 DR)을 통해 데이터 유실 위험을 줄여

 

(IF 분산) 소스 내부적으로는 circuit braker pattern 적용하여 장애가 전파되지 않도록(대용량 시스템의 장애와 분산 시스템의 장애는 다른가?)

대용량 시스템의 장애와 분산 시스템의 장애는 약간 관점이 다르다.

하지만 대용량 시스템의 단점인 SPOF를 막기 위해서는 분산 시스템으로 구성해야하고 그렇게되면 결국 대용량 + 분산 시스템의 특징을 모두 지닐 수 밖에 없게 된다.

728x90
반응형

'architecture > micro service' 카테고리의 다른 글

[Dead Letter] PDL, CDL  (0) 2024.11.14
E2E(end to end) 테스트  (0) 2024.11.13
transaction outbox pattern + polling publisher pattern  (0) 2024.11.07
2PC vs 2PL  (1) 2024.11.06
[arch] EDA, event sourcing, saga, 2pc  (0) 2024.02.29
반응형

트랜잭션 아웃박스 패턴폴링 퍼블리셔 패턴분산 시스템에서 데이터베이스와 메시징 시스템 간의 메시지 전달을 보장하기 위해 자주 함께 사용되는 패턴입니다. 특히 이벤트 주도 아키텍처에서 데이터베이스 상태 변화와 해당 이벤트 발행의 일관성을 유지해 줍니다.

아웃박스 패턴(유실방지)

배경 및 필요성

분산 시스템에서는 데이터베이스에 데이터를 저장하는 트랜잭션과 외부 메시징 시스템에 이벤트를 보내는 작업이 분리되어 있기 때문에, 두 작업이 동시에 성공하거나 실패하도록 일관성 있는 처리가 어려울 수 있습니다. 특히, 트랜잭션 내에서 데이터베이스는 성공했는데 메시지 큐로의 전송은 실패하는 경우, 데이터 일관성이 깨질 수 있습니다. 이 문제를 해결하는 방법 중 하나가 아웃박스 패턴입니다.

트랜잭션 아웃박스 패턴의 주요 흐름:

  1. 데이터베이스 트랜잭션 및 아웃박스 기록 생성:
    • 애플리케이션이 데이터베이스 변경(예: 주문 생성)을 해야 할 때, 메인 테이블(예: Orders)에 데이터를 저장하는 동시에, 하나의 트랜잭션 안에서 "아웃박스" 테이블에 관련 이벤트 정보를 기록합니다.
    • 이 아웃박스 테이블에는 메시징 시스템에 발행할 이벤트와 관련된 정보가 포함되며, 아직 발행되지 않았음을 나타내는 상태 정보도 포함됩니다.
  2. 트랜잭션 커밋:
    • 모든 데이터베이스 작업(메인 테이블과 아웃박스 테이블에 저장 작업)이 하나의 트랜잭션으로 묶이기 때문에, 트랜잭션이 성공적으로 커밋되면 데이터베이스와 이벤트 기록이 일관성을 유지하게 됩니다.

아웃박스 패턴의 구조

  1. 아웃박스 테이블:
    • 서비스의 데이터베이스에 아웃박스 테이블을 둡니다. 이 테이블은 데이터베이스의 업데이트 내용과 함께 발행할 이벤트를 저장합니다. 즉, 비즈니스 로직에서 데이터베이스에 기록할 때, 메시지 큐에 보내야 할 이벤트 정보도 이 테이블에 같이 저장됩니다.
  2. 로컬 트랜잭션:
    • 데이터베이스에 데이터를 저장할 때, 같은 트랜잭션 내에서 아웃박스 테이블에도 이벤트 데이터를 함께 기록합니다. 단일 트랜잭션을 통해 데이터베이스의 데이터와 이벤트 정보를 동시에 커밋하므로, 이 과정에서 실패가 발생하면 전체 트랜잭션이 롤백됩니다.
  3. 이벤트 리스너/폴링 프로세스:
    • 별도의 폴링 서비스 또는 트리거가 아웃박스 테이블을 주기적으로 스캔하여 새로운 이벤트가 있으면 이를 메시징 시스템(예: Kafka, RabbitMQ)으로 전송합니다. 이 작업은 별도의 비동기 프로세스로 수행되며, 메시지 전송이 성공하면 해당 이벤트는 아웃박스 테이블에서 삭제되거나 상태가 업데이트됩니다. (폴링 주기에 따른 지연 있음)
  • 데이터베이스 상태와 이벤트 전송 상태의 일관성을 보장
  • 메시지 전송 실패 시에도 데이터는 손실되지 않으며, 시스템은 나중에 재시도할 수 있음
    • 유실 발생 시 배치로 재발행

폴링 퍼블리셔 패턴

폴링 퍼블리셔 패턴은 아웃박스 테이블에 저장된 이벤트를 주기적으로 조회(polling)하여 메시징 시스템에 발행하는 역할을 합니다.

폴링 퍼블리셔 패턴의 주요 흐름:

  1. 주기적인 조회:
    • 폴링 작업은 일정한 간격으로 아웃박스 테이블을 조회하여 발행되지 않은 이벤트를 찾습니다.
  2. 메시지 발행:
    • 발행되지 않은 이벤트를 찾아 메시징 시스템(예: Kafka 또는 RabbitMQ)으로 발행합니다.
    • 발행이 성공하면 해당 이벤트의 상태를 “발행 완료”로 업데이트하여, 동일한 이벤트가 다시 발행되지 않도록 합니다.

두 패턴을 결합한 이점

  • 일관성 보장: 트랜잭션 아웃박스 패턴을 통해 데이터베이스 변경과 이벤트 기록을 하나의 트랜잭션에서 처리할 수 있어 데이터와 이벤트의 일관성을 유지할 수 있습니다.
  • 내결함성: 폴링 퍼블리셔 패턴을 통해 이벤트 발행이 실패하더라도 재시도가 가능해 내결함성을 확보할 수 있습니다.
  • 확장성: 메시징 시스템을 통해 이벤트가 발행되므로, 여러 마이크로서비스가 이 이벤트를 구독하여 비동기로 처리할 수 있어 시스템 확장성에 유리합니다.

세팅 예시

  • 예시 1. mysql + kafka

1. 트랜젝션 처리 시 outbox 테이블에 이벤트 정보도 추가

@Transactional
public void createOrder(Order order) {
    // 1. Orders 테이블에 주문 정보 저장
    ordersRepository.save(order);
    
    // 2. Outbox 테이블에 이벤트 기록
    OutboxEvent event = new OutboxEvent(
        order.getId(),
        "ORDER",
        "ORDER_CREATED",
        new JSONObject().put("orderId", order.getId()).put("status", order.getStatus()).toString(),
        "PENDING"
    );
    outboxRepository.save(event);
}

2. 폴링용 스케줄 생성 -> 카프카로 이벤트 발행

@Scheduled(fixedDelay = 5000) // 5초마다 실행
public void publishEvents() {
    List<OutboxEvent> pendingEvents = outboxRepository.findByStatus("PENDING");
    
    for (OutboxEvent event : pendingEvents) {
        try {
            // 1. Kafka에 이벤트 발행
            kafkaTemplate.send("order-events", event.getPayload());
            
            // 2. 발행 성공 시, Outbox 테이블에서 상태를 'COMPLETED'로 업데이트
            event.setStatus("COMPLETED");
            outboxRepository.save(event);
            
        } catch (Exception e) {
            // 발행 실패 시 별도의 로깅 또는 재시도 처리
            logger.error("Failed to publish event: " + event.getEventId(), e);
        }
    }
}

  • 예시2. 스케줄 대신 디비 변경 감지 이용하여 이벤트 전송

MySQL Kafka 커넥터Debezium을 사용하면 트랜잭션 아웃박스 패턴을 더욱 쉽게 구현할 수 있습니다. 이 조합은 변경 데이터 캡처(Changed Data Capture, 데이터베이스에서 발생하는 삽입(INSERT), 업데이트(UPDATE), 삭제(DELETE)와 같은 변경 사항을 실시간으로 감지하고 추적하는 기술) 방식을 통해 MySQL 데이터베이스의 변화(즉, 새로운 아웃박스 레코드)를 자동으로 Kafka에 발행하는 구조를 제공합니다. 이를 통해 폴링을 위한 추가 프로세스 없이, 이벤트가 발생할 때마다 Kafka에 실시간으로 이벤트를 전송할 수 있습니다.

  • Debezium은 MySQL Kafka 커넥터 중에서도 가장 널리 사용되고 인기 있는 CDC 커넥터입니다. Debezium은 MySQL을 포함해 다양한 데이터베이스의 변경 사항을 실시간으로 감지하여 Kafka에 전송하는 강력한 기능을 제공하는 오픈소스 CDC 플랫폼입니다. Kafka와 MySQL을 연동하는 데 있어서 CDC를 필요로 할 때, Debezium이 대표적인 솔루션으로 많이 활용됩니다.
  • MySQL Kafka 커넥터는 Kafka Connect 프레임워크를 사용하여 MySQL 데이터베이스와 Apache Kafka 간에 데이터를 스트리밍하는 도구입니다. 주로 MySQL에서 발생한 데이터 변경 사항을 Kafka로 전송하는 데 사용됩니다. 이를 통해 MySQL 데이터베이스의 변경 로그를 실시간으로 Kafka로 스트리밍하고, Kafka의 여러 소비자가 이 데이터를 처리할 수 있습니다.

Debezium을 활용한 MySQL Kafka 커넥터 구현

Debezium은 CDC 플랫폼으로, MySQL의 바이너리 로그(binlog)를 읽어 Kafka로 변경 사항을 스트리밍할 수 있도록 합니다. MySQL의 binlog는 데이터베이스에 일어나는 모든 변경 사항을 기록하며, Debezium은 이를 Kafka 이벤트로 변환해 전송합니다. 

  1. 트랜잭션 아웃박스 패턴 적용: 애플리케이션에서 데이터베이스에 트랜잭션을 수행할 때, Outbox 테이블에 이벤트 정보를 함께 기록합니다.
  2. Debezium이 변경 감지 및 Kafka로 발행:
    • Debezium은 MySQL 바이너리 로그를 모니터링하고, Outbox 테이블에 새로운 행이 추가되면 해당 이벤트를 자동으로 Kafka로 발행합니다.
      • 바이너리 로그를 통한 순서 보장 및 오프셋을 활용한 발행 보장 -> 실패시 재시도로 발행 보장
    • 예를 들어, Outbox 테이블에 새로운 주문 이벤트가 추가되면 Debezium이 이를 감지해 Kafka의 dbserver1.yourDatabase.Outbox 토픽으로 해당 데이터를 스트리밍합니다.
  3. Kafka 소비자 서비스: Kafka에 연결된 소비자 서비스는 dbserver1.yourDatabase.Outbox 토픽에서 이벤트를 수신하여, 그 이벤트를 바탕으로 필요한 로직을 실행하거나 다른 서비스로 전달합니다.

장점

  • 실시간 처리: Debezium이 CDC 방식으로 아웃박스 테이블의 변경을 감지하여 바로 Kafka에 발행하므로 실시간으로 이벤트를 처리할 수 있습니다.
  • 추가 폴링 프로세스 불필요: 별도의 폴링 프로세스를 구현할 필요가 없으므로 시스템 자원을 절약할 수 있습니다.
  • 내결함성: Debezium은 Kafka로 이벤트 발행 중 문제가 발생하더라도 Kafka의 내장된 내결함성 기능 덕분에 안정적으로 이벤트를 재시도하고, 누락 없이 처리할 수 있습니다.
  • 처리량 증설 가능; 아웃박스 테이블 (이벤트 키를 기반으로) 파티셔닝을 통한 처리량 증대 가능 
    • 로그를 순서대로 읽느라 단일 커넥터 사용 -> 테이블에 쌓이는 속도가 더 많아서 slow
    • 분산처리로 속도 향상: 토픽(이벤트 키) 별로 outbox table 분리하여 분산 처리 가능토록(같은 키는 같은 테이블에 쌓이도록 -> 순서보장)

 

참고: https://youtu.be/DY3sUeGu74M?si=L4jk0qBOdTcRYHPb

728x90
반응형
반응형

2024.11.06 - [개발/sql] - 2 Phase Lock & mysql

2024.02.29 - [architecture/micro service] - [arch] EDA, event sourcing, saga, 2pc

 

2PC, 2PL 모두 알아봤는데 뭔가 미래에도 헷갈릴 것 같아서 정리...

 

1. 2PC (Two-Phase Commit)

2PC는 분산 트랜잭션 관리 방식으로, 여러 시스템에서 분산된 트랜잭션을 일관되게 관리하고 커밋/롤백을 보장하는 프로토콜

2PC는 MSA의 서로 다른 서비스 간(회원 컴포넌트 & 배송 컴포넌트), 즉 분리된 데이터의 저장에 대해서(하나의 동작이지만) 하나의 트랜젝션으로 묶지 못하기 때문에 데이터가 틀어지지 않게 하는 방법이다

2PC는 현재 잘 안 쓰이고 MSA의 경우 결국 SAGA chreography가 더 자주 쓰이는 듯. 비동기에 이벤트 기반인 게 제일 안전하다. 결국 메시지 브로커를 누가 누가 잘 쓰냐의 경쟁이랄까..

동작 방식:

  • Phase 1: Prepare Phase
    • 트랜잭션 코디네이터가 참가자들에게 트랜잭션을 커밋할 준비가 되었는지 물어봅니다 (Vote).
    • 각 참가자는 트랜잭션이 커밋 가능한지, 아니면 롤백해야 하는지를 답변합니다.
    • 모든 참가자가 "Yes"를 응답하면 커밋을 진행하고, 하나라도 "No"를 응답하면 롤백합니다.
    • ex. 주문 서비스(코디네이터)는 각 서비스(결제, 재고, 배송 등)에게 트랜잭션을 준비할 수 있는지 확인합니다. 각 서비스는 작업을 완료할 준비가 되었는지 확인하고, 준비되면 "Yes"를 응답합니다. 각 서비스는 해당 작업을 실제로 실행하지 않고, 작업을 예약해 놓습니다.
    • 트랜젝션 걸고 락 필요
  • Phase 2: Commit/Rollback Phase
    • 트랜잭션 코디네이터는 모든 참가자가 "Yes"라고 응답하면 커밋을 확정하고, 하나라도 "No"라고 응답하면 롤백합니다.
    • 트랜잭션 코디네이터는 모든 참가자들에게 최종 결과를 전달합니다.
    • ex. 모든 서비스가 "Yes" 응답을 하면, 주문 서비스는 모든 서비스에 대해 트랜잭션을 커밋하도록 지시합니다. 하나라도 "No" 응답이 있으면, 주문 서비스는 모든 서비스에 롤백을 지시합니다. 
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import javax.naming.InitialContext;

public class TwoPhaseCommitExample {

    public static void main(String[] args) {
        try {
            // JNDI를 통해 트랜잭션 관리자를 조회
            InitialContext ctx = new InitialContext();
            UserTransaction utx = (UserTransaction) ctx.lookup("java:comp/UserTransaction");

            // 1단계: 트랜잭션 시작
            utx.begin();
            System.out.println("Transaction started");

            // 참여자 1: 데이터베이스 A
            DatabaseParticipant dbA = new DatabaseParticipant("DB_A");
            dbA.prepare();

            // 참여자 2: 데이터베이스 B
            DatabaseParticipant dbB = new DatabaseParticipant("DB_B");
            dbB.prepare();

            // 2단계: 커밋 요청
            dbA.commit();
            dbB.commit();
            System.out.println("Transaction committed successfully");

            // 트랜잭션 종료
            utx.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 참여자 역할을 하는 클래스
class DatabaseParticipant {
    private String name;

    public DatabaseParticipant(String name) {
        this.name = name;
    }

    // 준비 단계
    public void prepare() {
        try {
            connection.setAutoCommit(false); // 트랜잭션 시작
            // 락 잡기
            PreparedStatement stmt = connection.prepareStatement("SELECT * FROM accounts WHERE id = ? FOR UPDATE");
            stmt.setInt(1, 1); 
            stmt.executeQuery();

            // 데이터 수정
            connection.prepareStatement("UPDATE accounts SET balance = balance - 100 WHERE id = 1").executeUpdate();

            System.out.println(name + " is prepared for the transaction");
        } catch (SQLException e) {
            e.printStackTrace();
            rollback();
        }
    }

    // 커밋 단계
    public void commit() {
        System.out.println(name + " has committed the transaction");
    }

    // 롤백 단계
    public void rollback() {
        System.out.println(name + " has rolled back the transaction");
    }
}

 

 

장점:

  • 원자성 보장: 모든 참가자가 트랜잭션을 커밋하거나 모두 롤백하여 트랜잭션의 원자성을 보장합니다.
  • 분산 트랜잭션 처리: 여러 시스템에서 하나의 트랜잭션을 관리할 수 있습니다.

단점:

  • Blocking 문제: 트랜잭션이 실패하거나 코디네이터가 실패하면, 참가자는 "결정할 수 없다"는 상태로 대기 상태에 빠질 수 있습니다. 트랜잭션 코디네이터나 참가자 서비스가 다운되면 트랜잭션이 중단될 수 있습니다.
  • 성능 저하: 2PC는 동기식으로 트랜잭션을 처리하기 때문에, 각 서비스 간의 통신과 협의 과정에서 지연이 발생할 수 있습니다.
  • 복잡성: 2PC는 구현이 복잡하며, 특히 장애 복구와 같은 시나리오에서 상태를 관리하는 데 어려움이 있을 수 있습니다.
  • 데드락: 준비 단계에서 여러 트랜잭션이 교착 상태에 빠질 수 있으므로, 트랜잭션 순서 및 타임아웃을 적절히 설정해야 합니다. prepare 했는데 commit 못하고 죽으면,, 영원한 데드락?(타임아웃 설정 필요)
  • 락 경합: 준비 단계에서 락이 너무 오래 유지되면, 다른 트랜잭션이 대기 상태가 되어 성능 저하가 발생할 수 있습니다. 이를 해결하려면 트랜잭션 범위를 최소화하고, 락 해제를 신속히 수행해야 합니다.

2. 2PL (Two-Phase Locking)

2LC는 데이터베이스의 동시성 제어 방법으로, 트랜잭션이 데이터에 대해 잠금을 설정하고 해제하는 순서를 규정하는 방식

디비(mysql) 내부에서 같은 요청이 여러번 들어왔을 때, 어떻게 잠그냐~ 하는 방법

@Transactional  Isolation과 연관하여

= 데이터 일관성과 성능 간의 균형을 맞추는 방법, 기본적으로데이터베이스의 격리 수준을 기반으로 동작

1. READ_COMMITTED (기본)

  • 매핑된 2PL 방식: Basic 2PL
    • 트랜잭션이 진행되면서 필요한 시점에만 락을 걸고, 트랜잭션이 종료될 때 락을 해제
    • Dirty Read, Non-Repeatable Read 방지
    • Phantom Read가 발생할 수 있으며, 이 경우 Repeatable ReadSerializable 격리 수준을 사용해야 함

2. READ_UNCOMMITTED

  • 매핑된 2PL 방식: None (락을 관리하지 않음)
    • 트랜잭션 간에 락을 걸지 않으며, Dirty Read가 가능하고 동시성 제어가 거의 없음
    • 2PL 방식이 적용되지 않음

3. REPEATABLE_READ(mySql default)

  • 매핑된 2PL 방식: S2PL (Strict 2PL)
    • 트랜잭션이 데이터를 읽고 쓰는 동안 해당 데이터를 락을 걸고 트랜잭션 종료 시점에 락을 해제
    • Phantom Read, Non-Repeatable ReadDirty Read 방지

4. SERIALIZABLE

  • 매핑된 2PL 방식: SS2PL (Strong Strict 2PL)
    • 트랜잭션이 시작될 때 모든 락을 미리 걸고, 트랜잭션 종료 시점에 락을 해제
    • Phantom Read, Non-Repeatable Read, Dirty Read를 방지하고, 모든 트랜잭션 간의 충돌을 방지
    • 트랜잭션이 끝날 때까지 모든 락을 보유하므로 성능이 안 좋음

 

동작 방식:

  • Phase 1: Growing Phase
    • 트랜잭션은 필요한 잠금을 획득할 수 있으며, 이 시점에서만 데이터에 대한 잠금을 증가시킬 수 있습니다. 새로운 잠금을 얻거나 기존 잠금을 확장할 수 있습니다.
  • Phase 2: Shrinking Phase
    • 트랜잭션은 잠금을 더 이상 추가할 수 없으며, 잠금을 해제하는 시점입니다. 잠금 해제는 commit 또는 rollback 후에 이루어집니다.

장점:

  • Serializable 격리 수준 보장: 이 방식은 데이터의 일관성을 유지하면서, 트랜잭션 간의 충돌을 방지할 수 있습니다.
  • 동시성 제어: 두 개의 단계에서 데이터 잠금과 해제를 관리하여 동시성 문제를 해결합니다.

단점:

  • 교착 상태 (Deadlock): 만약 여러 트랜잭션이 서로의 잠금을 기다리게 되면 교착 상태가 발생할 수 있습니다. 이를 해결하려면 교착 상태 감지 및 회피 기법이 필요합니다.
  • 성능 저하: 잠금 관리가 복잡하여 성능에 부정적인 영향을 미칠 수 있습니다.

 

728x90
반응형
반응형

event driven architecture

정의:

  • EDA는 시스템 내의 이벤트를 통해 컴포넌트나 서비스 간의 통신을 관리하는 아키텍처입니다. 어떤 상태 변화나 동작이 발생하면 이벤트가 생성되고, 이를 기반으로 다른 컴포넌트들이 반응합니다.
  • EDA 이벤트를 통해 시스템 간 통신과 상호작용을 관리하는 데 초점을 맞추며, 컴포넌트 간의 느슨한 결합과 비동기 처리를 가능하게 합니다.

특징:

  • 비동기 통신: 이벤트는 비동기적으로 발행되고, 이를 처리하는 소비자들이 독립적으로 이벤트를 처리합니다. 이는 시스템의 느슨한 결합을 가능하게 합니다.
  • 루스 커플링: 이벤트 발행자와 소비자는 서로 직접적으로 알 필요가 없습니다. 이는 시스템 간의 의존성을 줄이고 유연한 확장성을 제공합니다.
  • 확장성: 새로운 이벤트 소비자를 쉽게 추가할 수 있고, 시스템의 각 구성 요소가 독립적으로 확장 가능합니다.
  • 리액티브 시스템: EDA는 시스템의 비동기적 반응성을 높여 실시간으로 변화에 대응할 수 있게 합니다.
  • EDA: 시스템 간의 비동기적 상호작용을 통해 최종 일관성(Eventual Consistency)을 유지할 수 있습니다. 메시지 손실이나 중복 처리 등은 메시징 시스템의 특성에 따라 다르게 처리될 수 있습니다.
  •  monolithic
    • 단일 데이터베이스
    • 트랜잭션 처리를 완벽하게 -> ACID
      • atomicity
      • consistency
      • isloation
      • durable
  • msa
    • 각 서비스마다 독립적인 데이터베이스(polyglot)
    • API를 통해 접근
    • atomicy, consistency를 완벽하게 지키기 힘듦 -> commit transaction 사용
      • 작업 단위가 정상적으로 끝났음을 알려줌
      • A 서비스: 최초에 대기상태로 저장 후 요청(카프카) -> B서비스: 수량 등을 확인하고 재고처리 후 confirm 메세지 발행(카프카; 메세지 종류는 상태에 따라 다르게 발생) -> A서비스: confirm message를 받아서 상태값을 변경
      • 롤백이나 취소 상태로 변경 용이 

event sourcing

정의:

  • Event Sourcing은 시스템의 상태를 데이터베이스에 직접 저장하지 않고, 상태 변화를 나타내는 모든 이벤트를 저장하고, 이 이벤트들을 순차적으로 재생(Replay)하여 현재 상태를 복원하는 아키텍처 패턴입니다.
  • Event Sourcing은 시스템의 상태 변화를 이벤트로 기록하여 현재 상태를 유지하고, 데이터의 변경 이력을 완벽하게 보존하는 데 중점을 둡니다.

특징:

  • 이벤트 저장: 애플리케이션의 모든 상태 변화는 이벤트로 표현되어 이벤트 스토어에 기록됩니다. 예를 들어, "계좌 개설", "잔고 추가", "잔고 인출"과 같은 이벤트들이 저장됩니다.
  • 현재 상태 재생: 현재 상태는 저장된 모든 이벤트를 순차적으로 재생하여 계산할 수 있습니다. 이를 통해 애플리케이션의 상태를 특정 시점으로 되돌리거나 복원할 수 있습니다.
  • 이벤트 불변성: 이벤트는 불변이며, 한번 기록된 이벤트는 변경되지 않습니다. 이는 감사(audit) 기록이나 상태 추적에 유리합니다.
  • CQS와의 결합: Event Sourcing은 일반적으로 CQRS 패턴과 함께 사용되며, 명령(Command)이 발생하면 이벤트로 기록하고, 이 이벤트를 통해 읽기 모델(Query)를 업데이트합니다.
  • Event Sourcing: 이벤트 로그가 시스템의 단일 진실 원천(Single Source of Truth)입니다. 모든 상태는 이벤트 로그를 통해 재구성될 수 있기 때문에 데이터 일관성이 매우 중요합니다.
  • 데이터의 마지막 상태만을 저장하는 것이 아닌, 해당 데이터에 수행된 전체 이력을 기록
    • 이벤트 자체를 발행, 끌어가서 알아서 처리
    • insert만 존재; update/delete 없음
  • 데이터 구조 단순
  • 데이터의 일관성과 트랜잭션 처리 가능
  • 데이터 저장소의 개체를 직접 업데이트 하지 않기 때문에 동시성 충돌 문제 해결 
  • 도메인 주도 설계(domain driven design)
    • aggregate; 데이터의 상태 값을 바꾸기 위한 방법 
    • projection; 현재 데이터 상태 값을 표시하기 위한 방법
  • 메세지 중심의 비동기 작업 처리
  • 단점
    • 모든 이벤트에 대한 복원; 시간
      • snapshot(중간 세이브포인트; 어느 순간 이후부터 히스토리 쌓기)
    • 다양한 데이터가 여러 조회
      • cqrs(command and query responsibility segregation)도입으로 해결 가능
        • 명령과 조회의 책임 분리
        • 상태를 변경하는 command
        • 조회를 담당하는 query


saga pattern

SAGA 패턴은 주로 비동기적으로 분산 트랜잭션을 관리하기 위해 사용됩니다. 이 패턴은 분산 시스템에서 트랜잭션 일관성을 유지하는 데 유용하며, 특히 데이터 일관성이 필요한 여러 서비스 간의 장기 실행 트랜잭션(Long-Running Transactions)에 적합합니다.

Saga는 여러 서비스에 걸쳐 진행되는 일련의 로컬 트랜잭션으로 이루어져 있습니다. 각 서비스가 자신의 트랜잭션을 성공적으로 처리하면, 다음 서비스가 트랜잭션을 진행하고, 만약 어느 한 서비스에서 트랜잭션이 실패하면 이전에 성공한 트랜잭션을 보상 트랜잭션을 통해 취소합니다.

  • 구현방식: 어플리케이션에서 트랜젝션 처리하는 방식에 따라서 choreography, orchestration 방식으로 구분됨 
    • 어플리케이션이 분리된 경우에는 각각의 local transaction만 처리
  • 각 어플리케이션에 대한 연속적인 트랜젝션에서 실패할 경우
    • 롤백 시 모든 값을 원상복구해야하는데, 관련한 롤백 처리 구현 -> 보상 트랜젝션이 준비되어 있음.
  • 데이터의 원자성을 보장하지 않지만 일관성을 보장 

내 할일만 잘하면 됨

장점

  • 탈결합: 서비스가 이벤트를 통해 통신하므로 독립적으로 발전할 수 있습니다.
  • 확장성: 각 서비스가 자체 트랜잭션을 관리하므로 수평적으로 확장이 가능합니다.
  • 회복력: 부분적인 실패와 보상을 허용하여 시스템 회복력을 높일 수 있습니다.

단점

  • 복잡성: 여러 서비스 간에 발생하는 이벤트 흐름을 추적하기 어려워지며, 트랜잭션 경로가 복잡해질 수 있습니다.
  • 최종 일관성(Eventual Consistency): 사가는 즉각적인 일관성을 보장하지 않으며, 시간이 지남에 따라 데이터가 일관성을 갖도록 합니다.
  • 오류 처리: 강력한 오류 처리 및 보상 로직 구현이 어려울 수 있습니다.

 

choreography-based saga

  1. 비동기 메시징:
    • 코레오그라피 사가는 각 서비스가 이벤트를 발행하고, 다른 서비스들이 이 이벤트를 구독하여 작업을 수행(상태를 조정)하는 방식으로 진행됩니다. 이 과정은 비동기로 이루어지며, 각 서비스는 서로의 상태를 직접 알지 않고 이벤트를 통해 통신합니다. 확장성이 높음
  2. 자율성:
    • 각 서비스는 스스로/독립적으로 행동하며, 다른 서비스와의 상호작용을 이벤트로만 처리합니다. 이러한 자율성 덕분에 서비스 간의 결합도가 낮아지고, 시스템의 유연성이 증가합니다.
  3. 상태 관리:
    • 코레오그라피 사가는 각 서비스가 자신의 상태를 관리합니다. 서비스는 다른 서비스의 상태에 의존하지 않고, 이벤트에 따라 자신의 행동을 결정합니다.
  4. 장애 복구:
    • 장애 발생 시 각 서비스는 발행된 이벤트에 따라 적절한 복구 작업을 수행할 수 있습니다. 실패한 작업을 롤백하기 위한 이벤트를 수신하고 처리할 수 있기 때문에 전체 시스템의 복원력을 높일 수 있습니다.

SAGA Choreography의 장점

  • 서비스 간의 느슨한 결합: 각 서비스는 자신의 트랜잭션만 신경 쓰며, 다른 서비스와는 이벤트를 통해 통신하므로 결합도가 낮습니다.
  • 확장성: 서비스들이 독립적으로 동작하므로, 새로운 서비스를 쉽게 추가할 수 있습니다.
  • 비동기 처리: 이벤트 기반 아키텍처이기 때문에 각 서비스는 비동기로 트랜잭션을 처리할 수 있습니다.

SAGA Choreography의 단점

  • 복잡성 증가: 여러 서비스 간에 발생하는 이벤트 흐름을 추적하기 어려워지며, 트랜잭션 경로가 복잡해질 수 있습니다.
  • 순환 참조 문제: 서비스 간의 이벤트 의존도가 높아지면 이벤트가 서로 꼬이는 순환 참조 문제가 발생할 수 있습니다.
  • 보상 트랜잭션 관리: 오류가 발생했을 때 롤백을 위한 보상 트랜잭션을 관리하는 로직이 복잡해질 수 있습니다.

 

SAGA의 코레오그래피 패턴을 안정적으로 운영하려면 여러 가지 고려 사항이 필요합니다. 코레오그래피에서는 각 서비스가 자신의 비즈니스 로직을 처리하면서 필요한 메시지를 전송하고, 실패 처리도 자체적으로 관리하는 방식입니다. 이 방식의 주요 장점은 중앙 관리자가 없다는 것입니다. 하지만 이로 인해 시스템의 복잡성도 높아지고, 신뢰성과 일관성 확보가 어려운 경우도 있습니다. 안정적인 운영을 위한 몇 가지 중요한 점들을 아래에 정리해 보겠습니다. (다운타임 고려)

1. 메시지 브로커의 안정성 확보

코레오그래피에서는 서비스 간의 통신을 비동기적으로 메시지를 통해 이루어지기 때문에, 메시지 브로커(예: Kafka, RabbitMQ)가 핵심적인 역할을 합니다. 이 시스템이 다운되거나 메시지 유실이 발생하면 전체 사가가 실패할 수 있습니다.

  • 메시지 영속성: 메시지 브로커는 재시작되더라도 메시지가 손실되지 않도록 보장 / 메시지를 영속적으로 저장할 수 있도록 설정
    • Kafka는 기본적으로 로그 기반 아키텍처를 사용하여 메시지를 디스크에 영속적으로 저장하므로 높은 내구성을 제공합니다. 메시지가 파티션에 기록되고, 각 파티션은 순서가 보장되며, 복제 설정을 통해 장애 발생 시에도 데이터 손실을 방지할 수 있습니다. 소비자는 이전에 처리된 메시지로부터 다시 시작할 수 있는 유연성을 제공합니다.
      • 스토리지: Kafka는 로그 기반 시스템으로, 메시지가 디스크에 지속적으로 기록되며, 지정된 기간 동안 또는 설정된 크기까지 보존됩니다.
    • 반면 RabbitMQ는 기본적으로 메모리 기반 큐로, 영속성을 위해 추가적인 설정이 필요합니다. 영속성을 위해 메시지를 디스크에 저장하면 성능 저하가 발생할 수 있습니다. 따라서 대규모 데이터 처리 및 내구성이 중요한 시스템에서는 Kafka가 더 적합할 수 있습니다.
  • 메세지 보장:
    • 래빗 - 메시지 확인 및 재처리:
      • 소비자가 메시지를 처리할 때 ACK(Acknowledge) 또는 NACK(Negative Acknowledge)를 사용하여 메시지의 처리가 성공적으로 완료되었는지 여부를 브로커에 알립니다.
        • ACK: 소비자가 메시지를 성공적으로 처리한 후에 ACK를 보내면, RabbitMQ는 해당 메시지를 큐에서 제거합니다.
        • NACK: 메시지 처리가 실패했을 경우 소비자는 NACK를 보내어 메시지를 다시 큐에 추가하거나 다른 큐로 이동할 수 있습니다. 이는 메시지 손실을 방지하는 데 도움을 줍니다.
      • 만약 소비자가 다운될 경우, 처리되지 않은 메시지는 브로커에 남아 있게 되어, 후에 재시도할 수 있습니다. 이로써 서비스가 다시 시작되면 미처리된 메시지를 재처리할 수 있습니다.
    • 카프카 - 오프셋 관리:
      • Kafka와 같은 로그 기반의 메시징 시스템을 사용하면, 각 소비자는 자신의 오프셋(offset)을 관리할 수 있습니다. 이를 통해 서비스가 재시작되더라도 마지막으로 처리한 메시지 이후의 메시지만 읽을 수 있어, 데이터 손실 없이 안정적으로 운영할 수 있습니다.
      • 오프셋 관리: 소비자가 메시지를 처리한 후 해당 메시지의 오프셋을 커밋하여 카프카 브로커에 저장합니다. 오프셋은 각 파티션에서 메시지가 몇 번째인지에 대한 정보입니다. 이 커밋은 소비자가 해당 메시지를 성공적으로 처리했음을 카프카에 알려주는 역할을 합니다.
        • 메시지 처리 완료 확인: 소비자는 커밋된 오프셋을 기준으로 메시지가 이미 처리되었음을 확인하고, 다음에 읽을 메시지를 결정합니다.
        • 메시지 중복 방지: 카프카는 마지막 커밋된 오프셋부터 메시지를 소비하므로, 오프셋을 잘 관리하면 메시지의 중복 처리를 방지할 수 있습니다.
  • 재시도 메커니즘: 메시지 브로커에서 메시지 처리가 실패했을 때  일정 횟수까지 재시도를 할 수 있는 메커니즘을 도입합니다. 재시도 횟수를 제한하거나, 실패한 메시지는 DLQ(Dead Letter Queue)에 보관하고, 해당 메시지에 대해 수동 또는 자동으로 복구할 수 있는 절차를 마련합니다.
    • RabbitMQ는 Dead Letter Queue(DLQ)와 Retry Queue를 활용하여 메시지가 처리되지 않았을 경우 이를 다시 시도하도록 할 수 있습니다.

2. 모니터링과 트레이싱 / 알람

코레오그래피 패턴에서는 각 서비스가 독립적으로 행동하기 때문에, 문제 발생 시 전체 시스템에서 어떤 서비스가 문제를 일으켰는지 추적하는 것이 중요합니다. 메시지 처리 지연, 실패율 등을 관찰하고, 문제가 발생할 경우 경고를 받을 수 있습니다.

  • 분산 추적 시스템: 예를 들어, OpenTelemetry, Zipkin, Jaeger 등을 사용하여 분산 트레이싱을 설정합니다. 이를 통해 각 서비스에서 발생하는 요청과 응답을 추적하고, 실패나 지연이 발생한 지점을 쉽게 파악할 수 있습니다.
  • 로그 집합 시스템: 각 서비스에서 발생하는 로그를 통합하여 분석할 수 있는 시스템(예: ELK Stack, Prometheus, Grafana)을 구축합니다. 서비스 간 메시지 흐름을 추적하고, 오류를 신속하게 발견할 수 있도록 합니다.

3. 상태 관리와 보상 트랜잭션

코레오그래피 패턴에서는 각 서비스가 자신만의 상태를 관리하며, 실패 시 보상 트랜잭션을 처리해야 합니다. 보상 트랜잭션이란 이전 단계에서 발생한 변경을 되돌리는 작업을 의미합니다.

  • 보상 트랜잭션 설계: 각 서비스는 실패가 발생할 경우 보상 작업을 자동으로 실행해야 합니다. 예를 들어, 결제 처리 서비스에서 결제가 실패하면 이전에 발생한 예약을 취소하는 등의 처리를 해야 합니다.
  • 보상 트랜잭션의 신뢰성: 보상 트랜잭션이 실패하지 않도록, 처리되는 모든 작업은 idempotent(멱등성; 중복 실행 시 동일한 결과를 보장하는) 방식이어야 합니다. 이를 통해 여러 번 재시도가 이루어져도 시스템이 일관성 있게 동작할 수 있습니다.
  • 보상 트랜잭션의 회복: 보상 트랜잭션 역시 실패할 수 있기 때문에, 보상 작업이 실패하면 이를 다시 시도하거나 수동으로 복구할 수 있는 시스템을 마련해야 합니다.

4. 데이터 일관성 확보

코레오그래피 패턴에서는 이벤트 기반으로 서비스가 통신하므로, 데이터 일관성 문제를 해결하는 것이 중요합니다. 서비스 간의 데이터 변경이 일관되게 이루어지도록 해야 합니다.

  • 최종 일관성 보장: 코레오그래피 패턴은 강력한 일관성(ACID) 대신 최종 일관성(eventual consistency)을 제공합니다. 즉, 시간이 조금 걸릴 수 있지만 모든 서비스가 일관된 상태에 도달하도록 설계해야 합니다. 이를 위해 서비스들이 동일한 이벤트를 처리하는 순서를 보장하거나, 가능한 한 빠르게 일관된 상태로 수렴할 수 있도록 해야 합니다.

5. SLA(서비스 수준 계약, Service Level Agreement)와 트랜잭션의 타임아웃

서비스들이 비동기적으로 처리되기 때문에 각 단계의 응답 시간이 지연될 수 있습니다. 각 서비스가 적절한 시간 내에 작업을 완료하지 않으면 전체 SAGA가 실패할 수 있습니다.

  • SLA 설정: 각 서비스의 작업에 대해 적절한 SLA(Service Level Agreement)를 설정하고, 이 SLA를 기준으로 타임아웃을 설정하여 시스템이 적절한 시간 내에 작업을 완료할 수 있도록 합니다.
    • ex. 모든 API 요청에 대한 응답 시간은 200ms 이내로 처리
      • 응답 시간의 95%는 200ms를 초과하지 않아야 
      • 최대 응답 시간은 500ms를 초과할 수 없음
  • 타임아웃 처리: 각 서비스가 SLA를 지키지 않으면, 적절한 처리(예: 오류 로그 기록, 알림 발송, 재시도 등)를 통해 전체 트랜잭션을 관리합니다.

6. 이벤트 소싱

  • 모든 상태 변경을 이벤트로 기록하고 이를 통해 현재 상태를 재구성할 수 있는 이벤트 소싱(Event Sourcing) 패턴을 활용할 수 있습니다. 이렇게 하면 각 서비스가 발생한 이벤트를 기반으로 자신의 상태를 재구성할 수 있어, 데이터의 일관성을 유지하는 데 도움이 됩니다.
    •  

orchestration-based saga

  • 중앙 통제자: 중앙 조정자(orchestrator) 역할을 하는 서비스가 각 서비스의 트랜잭션을 관리합니다. 이 조정자는 트랜잭션 단계를 순차적으로 실행하고, 필요한 경우 보상 작업을 수행합니다.
  • 흐름 제어: 오케스트레이터는 각 서비스 호출의 순서를 정의하고, 에러 처리나 롤백도 중앙에서 관리합니다. 동기/비동기 모두 가능
  • 장점:
    • 단순한 흐름 제어: 중앙에서 모든 것을 관리하기 때문에 트랜잭션 흐름이 명확합니다.
    • 에러 처리 및 롤백이 통합되어 관리되므로 일관성을 유지하기 쉽습니다.; 오류 처리 쉬움
  • 단점:
    • Single Point of Failure: 중앙 통제자가 다운될 경우 전체 트랜잭션이 영향을 받습니다.
    • 확장성 문제: 모든 트랜잭션을 중앙에서 관리하기 때문에 트랜잭션 수가 많아질수록 부담이 커질 수 있습니다.

1. 동기식 오케스트레이션

  • 설명: 모든 서비스 호출이 순차적으로 진행되며, 각 서비스 호출이 완료될 때까지 대기합니다. 즉, 오케스트레이터가 각 서비스에 요청을 보내고 응답을 받을 때까지 기다립니다.
  • 장점:
    • 단순한 흐름: 트랜잭션의 흐름이 명확하고 예측 가능하여 디버깅과 모니터링이 용이합니다.
    • 즉각적인 오류 처리: 오류가 발생하면 즉시 대응할 수 있으며, 다음 단계로 진행하기 전에 에러 처리를 수행할 수 있습니다.
  • 단점:
    • 지연: 각 서비스 호출이 완료될 때까지 기다려야 하므로 전체 트랜잭션의 실행 시간이 늘어날 수 있습니다.
    • 스케일 문제: 동기식 호출이 많아지면 시스템의 성능이 저하될 수 있습니다.

2. 비동기식 오케스트레이션

  • 설명: 서비스 호출이 비동기적으로 이루어지며, 오케스트레이터는 각 서비스에 요청을 보내고 응답을 기다리지 않고 다음 작업을 진행합니다. 일반적으로 메시지 큐를 사용하여 서비스 간의 통신을 처리합니다.
  • 장점:
    • 성능 향상: 각 서비스가 독립적으로 작동하므로 동시에 여러 요청을 처리할 수 있어 성능이 향상됩니다.
    • 유연성: 서비스 간의 느슨한 결합을 유지할 수 있으며, 서비스가 독립적으로 확장될 수 있습니다.
  • 단점:
    • 복잡성: 비동기 통신은 흐름을 이해하고 디버깅하기 더 어렵게 만들 수 있습니다.
    • 오류 처리: 각 서비스가 비동기적으로 작동하므로 에러 발생 시 처리하기가 더 복잡해질 수 있습니다.

 

오케스트레이터는 복잡한 프로세스나 워크플로우를 관리하고 조정하는 시스템입니다. 마이크로서비스 아키텍처에서는 다양한 서비스 간의 상호작용을 관리하는 데 사용됩니다. 오케스트레이터는 일반적으로 다음과 같은 역할을 합니다:

  • 서비스 간의 호출 순서 및 의존성을 관리
  • 오류 처리 및 재시도 로직 구현
  • 상태를 모니터링하고 필요한 경우 알림 전송

오케스트레이터는 여러 형태로 구현될 수 있습니다:

  1. 전용 프로그램:
    • Apache Airflow, Camunda, Temporal과 같은 오케스트레이션 도구는 비즈니스 프로세스를 자동화하고 조정하는 데 사용됩니다. 이들 도구는 복잡한 워크플로우를 설계하고 실행하는 데 필요한 기능을 제공합니다.
  2. 메시지 브로커:
    • Kafka와 같은 시스템은 메시지를 전달하고 여러 서비스 간의 통신을 조정할 수 있지만, 기본적으로는 오케스트레이션 기능을 제공하지 않습니다. Kafka는 주로 데이터 스트리밍 및 이벤트 기반 아키텍처에 사용되며, 오케스트레이션과는 다릅니다. 하지만 오케스트레이터와 메시지 브로커를 함께 사용하여 전체 시스템을 구성하는 것이 일반적..
  3. 서비스 메쉬:
    • Istio와 같은 서비스 메쉬는 마이크로서비스 간의 통신을 관리하고 보안, 트래픽 관리, 모니터링 등의 기능을 제공합니다. 이러한 솔루션도 오케스트레이션의 일종으로 간주될 수 있습니다.

 


2 phase commit

2PC(2-Phase Commit, 2단계 커밋)는 분산 데이터베이스 시스템에서 트랜잭션을 안전하게 커밋하기 위해 사용하는 프로토콜입니다. 이 프로토콜은 여러 데이터베이스 또는 서비스가 관련된 트랜잭션을 일관성 있게 처리하기 위해 설계되었습니다.

2-Phase Commit 동작 단계

  1. Prepare 단계:
    • **트랜잭션 코디네이터(Coordinator)**가 모든 참여 노드(또는 서비스)에 "Prepare" 메시지를 전송합니다.
    • 각 참여자는 트랜잭션을 수행할 준비가 되면 로컬로 락을 설정하고 "Yes"(준비 완료) 응답을 보냅니다.
    • 만약 어떤 노드가 준비되지 않거나 실패하면 "No"(준비 실패) 응답을 보냅니다.
  2. Commit 단계:
    • 코디네이터가 모든 노드로부터 "Yes" 응답을 받으면, 각 노드에 **트랜잭션을 커밋(commit)**하도록 지시하고 트랜잭션이 완료됩니다.
    • 만약 어느 하나의 노드라도 "No" 응답을 보낸 경우, 트랜잭션을 전체적으로 롤백합니다.
    • 커밋이나 롤백 완료 후 각 노드는 락을 해제합니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;

@Service
public class InventoryService {

    @Autowired
    private DataSource warehouse1DataSource;

    @Autowired
    private DataSource warehouse2DataSource;

    @Transactional
    public void updateInventory(String productId, int quantity) throws Exception {
        try (Connection conn1 = warehouse1DataSource.getConnection();
             Connection conn2 = warehouse2DataSource.getConnection()) {

            String updateWarehouse1 = "UPDATE inventory SET quantity = quantity - ? WHERE product_id = ?"; 
            try (PreparedStatement ps1 = conn1.prepareStatement(updateWarehouse1)) {
                ps1.setInt(1, quantity / 2);
                ps1.setString(2, productId);
                ps1.executeUpdate();
            }

            String updateWarehouse2 = "UPDATE inventory SET quantity = quantity - ? WHERE product_id = ?";
            try (PreparedStatement ps2 = conn2.prepareStatement(updateWarehouse2)) {
                ps2.setInt(1, quantity / 2);
                ps2.setString(2, productId);
                ps2.executeUpdate();
            }
        } catch (Exception e) {
            throw new RuntimeException("Inventory update failed, rolling back transaction", e);
        }
    }
}

위 코드에서 2PC의 Prepare 단계Commit 단계는 트랜잭션 매니저(JtaTransactionManager)에 의해 관리됩니다. 코드 자체에는 Prepare와 Commit이라는 명시적인 단계가 보이지 않지만, JtaTransactionManager가 내부적으로 이 과정을 수행합니다.


MySQL에서 UPDATE 쿼리를 실행할 때, InnoDB는 기본적으로 **행 잠금(Row Lock)**을 사용하여 UPDATE 대상이 되는 행을 잠급니다. 다른 트랜잭션에서 읽을 수는 있지만, 실제로 쓰기를 시도하려면 트랜잭션이 완료될 때까지 기다려야 하므로 일시적인 충돌은 방지됩니다. 하지만 다수의 트랜잭션에서 동시에 동일한 데이터를 수정하려고 하면 **경쟁 조건(Race Condition)**이 발생할 수 있습니다.

INSERT 쿼리에서 InnoDB는 기본적으로 행 잠금을 사용하지 않습니다. 즉, INSERT가 실행될 때는 행에 락을 걸지 않으며, 다른 트랜잭션은 동시에 다른 행을 삽입할 수 있습니다.

 

  • auto_increment 필드를 사용하는 경우, InnoDB는 자동 증가 값을 생성할 때 해당 레코드에 잠금을 걸 수 있습니다. 이 값이 충돌하지 않도록 내부적으로 관리되기 때문입니다.
  • 예를 들어, 여러 트랜잭션이 동시에 INSERT를 시도할 때, 자동 증가 값에 대한 충돌을 피하기 위해 자동 증가 번호를 추적하기 위한 락이 필요합니다. 이 경우 다른 트랜잭션은 번호가 할당될 때까지 대기할 수 있습니다.

 

1. Prepare 단계

Prepare 단계는 트랜잭션 관리자가 각 데이터 소스에 트랜잭션을 시작하고 작업을 준비시키는 과정입니다. 여기서 중요한 역할을 하는 부분은 @Transactional 애너테이션입니다. 이 애너테이션을 통해 트랜잭션이 시작되면 다음과 같은 작업들이 순서대로 진행됩니다.

  • 트랜잭션 매니저는 각각의 데이터 소스에 대해 트랜잭션을 시작합니다.
  • updateWarehouse1와 updateWarehouse2 쿼리를 실행하여 각 데이터베이스에 변경 사항을 기록합니다.
  • Prepare 단계에서는 실제 커밋을 수행하지 않고, 각 데이터베이스에 잠금(Lock)을 통해 데이터의 일관성을 보장합니다.

만약 이 단계에서 에러가 발생하면 JtaTransactionManager가 rollback을 호출하여 전체 트랜잭션이 취소됩니다.

2. Commit 단계

Commit 단계는 모든 준비가 완료된 후, 각 데이터 소스에 대해 트랜잭션을 실제로 커밋하는 과정입니다. 이 단계에서는 다음 작업들이 수행됩니다.

  • 트랜잭션 매니저가 각 데이터 소스에 commit 명령을 내리고, 모든 변경 사항을 데이터베이스에 영구적으로 반영합니다.
  • 각 데이터베이스가 성공적으로 커밋되면, 트랜잭션이 완료됩니다.
  • 만약 이 과정에서 하나라도 실패하면 rollback이 호출되어 모든 데이터베이스의 작업이 원상복구됩니다.

정리하면:

  • @Transactional 애너테이션을 통해 시작되는 트랜잭션이 Prepare 단계에서 두 데이터베이스에 모두 준비된 상태로 변경 작업을 적용한 후 잠금을 유지합니다.
  • 트랜잭션 매니저가 커밋을 호출하면서, 실제 변경 사항이 모든 데이터베이스에 커밋되는 것이 Commit 단계입니다.

2-Phase Commit에서의 락(Lock)과 문제점

락의 사용:

  • 각 참여 노드는 "Prepare" 단계에서 트랜잭션에 관련된 자원을 락합니다. 이 락은 트랜잭션의 커밋 또는 롤백이 완료될 때까지 유지되어, 다른 트랜잭션이 해당 자원에 접근하지 못하게 합니다.
  • 이 락은 데이터 일관성을 유지하는 데 필수적이지만, 락을 길게 유지하면 성능 저하가 발생할 수 있습니다.

문제점:

  • 블로킹(Blocking): 한 노드가 준비 상태에서 응답을 보내지 못하거나 장애가 발생하면, 코디네이터는 트랜잭션의 커밋 또는 롤백을 기다려야 하므로 전체 트랜잭션이 중단될 수 있습니다.
  • 확장성 문제: 트랜잭션이 많아지고 락을 획득하는 시간이 길어질수록 시스템의 성능이 저하됩니다.
  • 고립된 락: 참여자가 죽거나 네트워크 분리가 발생하면 락이 해제되지 않는 문제가 발생할 수 있습니다.

이러한 문제로 인해, 2PC는 락 비용이 크고 고가용성을 요구하는 시스템에는 적합하지 않으며, 대신 SAGA 패턴이나 **3-Phase Commit(3PC)**과 같은 대안이 사용되기도 합니다.


실패 가능 시나리오

1. Prepare 단계에서 참가자 실패

  • 상황: 코디네이터가 각 참가자에게 Prepare 메시지를 보내고 승인을 기다리는 동안, 한 참가자가 응답하지 않거나 실패하는 경우입니다.
  • 처리 방식:
    • 대기 하다가
    • 타임아웃 설정(권장): 참가자가 Prepare 단계에서 정해진 시간 내에 응답하지 않으면, 코디네이터는 해당 참가자를 실패로 간주합니다.
    • 롤백 결정: 코디네이터는 전체 트랜잭션을 롤백하기로 결정하고 모든 응답한 참가자에게 Rollback 명령을 보냅니다.
    • 일관성 보장: 응답하지 않은 참가자 역시, 복구 시 자신의 트랜잭션을 롤백하는 규칙을 따릅니다.

2. Commit 단계에서 참가자 실패

  • 상황: 모든 참가자가 Prepare 요청에 승인했으나, Commit 명령을 기다리는 도중에 일부 참가자가 실패합니다.
  • 처리 방식:
    • Commit 의무: Prepare에서 Commit을 승인한 참가자는 이후 반드시 Commit을 완료해야 합니다.
    • 복구 프로세스: 실패한 참가자는 복구되었을 때 로그를 확인하여 Commit 또는 Rollback을 수행합니다. 참가자는 Prepare 단계에서 승인 후 실패하면, 복구 후 반드시 Commit을 실행해야 일관성이 유지됩니다.
      • 코디네이터가 '대기'하는 것이 아닌, 참가자가 로그를 확인하는 방식: 코디네이터는 더 이상 해당 트랜잭션을 대기하지 않고 종료

3.  Prepare 단계에서의 코디네이터 실패:

    • 대기 상태 유지: 참가자는 Commit 또는 Rollback 명령을 받을 때까지 대기합니다.
      • 코디네이터가 Prepare 메시지를 보낸 후 실패하면, 참가자들은 각자의 상태를 Prepared로 유지하며 Commit 또는 Rollback 명령을 기다립니다.
      • 이 시점에서는 코디네이터의 복구나 교체 없이는 참가자들이 개별적으로 Commit이나 Rollback을 결정하지 못하므로, 참가자들은 일시적으로 대기 상태에 들어갑니다.
    • 타임아웃 및 복구: 코디네이터가 복구되면 로그를 확인해 Prepare 상태를 마친 참가자에게 Commit 또는 Rollback을 지시합니다. 새로운 코디네이터가 지정되거나 기존 코디네이터가 복구되기를 기다리며 재시도를 합니다.
      • 재시도 메커니즘: 참가자가 코디네이터의 상태를 감지할 수 있도록 재시도 메커니즘을 적용할 수 있습니다. 코디네이터가 다시 살아날 경우 트랜잭션을 다시 시도할 수 있습니다.  
      • 참가자들은 일정 시간 동안 코디네이터의 응답을 기다리도록 타임아웃을 설정할 수 있습니다. 타임아웃이 발생하면 참가자는 문제가 생겼음을 인식하고 롤백 프로세스를 시작하게 됩니다.
    • 코디네이터가 복구되거나 새로운 코디네이터가 지정되면 모든 참가자들의 상태 로그를 검토하여 트랜잭션 상태(Commit 또는 Rollback)를 확인합니다.

4. Commit 단계에서의 코디네이터 실패:

  1. Commit 명령을 받은 후 코디네이터가 실패한 경우:
    • 코디네이터가 모든 참가자에게 Commit 명령을 전달한 후에 실패했다면, 대부분의 참가자는 이미 Commit을 완료했을 것입니다.
    • 참가자는 트랜잭션 로그에 Commit 완료 상태를 기록하여 코디네이터가 없는 상태에서도 트랜잭션 완료 상태를 기억할 수 있습니다.
    • 새로운 코디네이터가 복구되면 참가자들의 로그 상태를 조회 일관성을 검증하고, 트랜잭션이 Commit으로 완료되었는지 확인합니다.
  2. Commit 명령이 일부 참가자에게 전달되지 않은 경우:
    • 코디네이터가 일부 참가자에게 Commit 명령을 보내기 전에 실패하면, 이 경우 일부 참가자만 Commit을 수행하고 나머지 참가자는 Prepared 상태로 남아 있을 수 있습니다.
    • 이때 참가자들은 코디네이터의 복구나 새로운 코디네이터가 지정될 때까지 로그를 통해 자신의 상태를 유지하며 대기하게 됩니다.
    • 새로운 코디네이터는 각 참가자의 상태를 확인하여, 이미 Commit을 완료한 참가자가 있으면 전체 트랜잭션을 Commit하도록 모든 참가자에게 지시합니다.
  3. 최종적으로 Commit을 보장하기 위한 절차:
    • 새로운 코디네이터가 지정되면, 각 참가자에게 현재 트랜잭션 상태를 질의하고 이를 바탕으로 트랜잭션의 일관된 완료 상태를 결정합니다.
    • 만약 일부만 Commit을 완료한 상태로 확인되면, 모든 참가자에게 Commit 명령을 다시 전달하여 트랜잭션을 완결시킵니다.

이 과정을 통해 트랜잭션은 무조건 동일한 최종 상태(Commit 또는 Rollback)로 마무리되며, 코디네이터가 Commit 단계에서 실패하더라도 로그와 상태 확인을 통해 일관성을 보장합니다.

2PC의 장점

  1. 트랜잭션 일관성 보장:
    • 모든 참여 시스템이 트랜잭션을 동시에 커밋하거나 롤백하기 때문에 데이터 일관성을 유지할 수 있습니다. 이는 금융 시스템이나 다른 중요한 데이터가 관련된 애플리케이션에서 필수적입니다.
  2. 구현의 단순성:
    • 두 가지 단계(Prepare 및 Commit)로 이루어져 있어 구현이 상대적으로 단순합니다. 트랜잭션 관리가 필요한 경우 쉽게 적용할 수 있습니다.

2PC의 단점

  1. 코디네이터의 단일 실패:
    • 코디네이터가 실패하면 모든 참가자가 해당 트랜잭션에 대해 무기한으로 대기하게 되어 시스템이 불안정해집니다. 코디네이터 장애 복구가 어려워 전체 트랜잭션에 영향을 줄 수 있습니다.
  2. 참가자의 무한 대기 문제:
    • Prepare 단계에서 승인 후 // 코디네이터가 Commit이나 Rollback 명령을 전달하지 않으면 참가자들은 대기 상태에 빠집니다. 이는 네트워크 장애나 코디네이터 장애로 인한 무한 대기 문제로 이어질 수 있습니다.
  3. 락 유효성 문제
    • 2PC 프로토콜에서는 참가자들이 트랜잭션 상태를 준비하는 동안 데이터에 락을 걸고 대기하므로, 데이터베이스 자원의 락이 장기간 유지될 수 있습니다.
    • 트랜잭션이 길어지면 데이터베이스 자원에 대한 경합이 심해져, 다른 트랜잭션들이 성능 저하 겪을 수 있습니다.
  4. 네트워크 지연 문제:
    • 코디네이터와 각 참가자 간의 통신에서 지연이 발생할 경우 트랜잭션 완료까지 시간이 길어집니다. 이로 인해 자원이 오랫동안 잠금 상태로 유지되어 성능 저하가 발생할 수 있습니다.
  5. 네트워크 파티션 문제:
    • 2PC는 네트워크 파티션이 발생할 경우 각 노드가 올바른 결정을 내리기 어려운 문제가 있습니다. 네트워크가 복구될 때까지 모든 시스템이 일관성을 보장할 방법이 없어 데이터 일관성 유지가 어려워질 수 있습니다.
  6. 확장성 제한:
    • 2PC동기화 방식으로 작동하므로 확장성(scalability) 문제에 직면할 수 있습니다. 분산 시스템에서 많은 참가자가 동시에 참여하는 경우, 각 단계에서 지연이 누적되기 때문에 트랜잭션의 성능 저하가 발생할 수 있으며, 네트워크 지연, 대기 시간, 동기화 비용 등이 시스템 성능에 큰 영향을 미칠 수 있습니다.

로그 활용과 복구 절차

  • 참가자와 코디네이터의 로그 기록: 모든 참여자는 트랜잭션 진행 상황을 로그에 기록하여, 장애 발생 시 트랜잭션 상태를 복구하는 데 사용합니다. 예를 들어, Prepare 승인 여부, Commit 및 Rollback 상태 등을 기록합니다.
  • 복구 절차:
    • 코디네이터 복구: 코디네이터는 복구 시 자신의 로그를 확인하여 아직 완료되지 않은 트랜잭션을 찾고, 참가자에게 나머지 명령을 수행하도록 합니다.
    • 참가자 복구: 참가자는 복구 시, 마지막 로그 상태에 따라 트랜잭션을 Commit 또는 Rollback합니다.

동기화 문제와 대안

  • 2PC는 장애 시 동기화 문제로 인해 성능 저하가 발생할 수 있습니다. 이런 상황을 최소화하기 위해 많은 분산 시스템에서는 SAGA 패턴과 같은 비동기 트랜잭션 관리 방식을 선택하기도 합니다.
  • SAGA 패턴은 복구(데이터 일관성이 맞던 시점으로 돌아가) 및 보상 트랜잭션(되돌리기; 롤백)을 사용하여, 전체 시스템을 잠그지 않고 트랜잭션을 처리하는 대안을 제공합니다.
  • 2-Phase Commit (2PC)은 일반적으로 동기적으로 동작합니다. 즉, 모든 참가자가 각 단계에서 응답을 제공하기 전까지 다음 단계로 진행하지 않는 구조입니다.
  1. Prepare 단계의 동기성:
    • 코디네이터는 각 참가자에게 Prepare 요청을 보내고, 모든 참가자가 성공을 응답할 때까지 대기합니다. 모든 참가자가 준비 완료 상태임을 확인할 때까지 다음 단계인 Commit으로 넘어가지 않습니다.
    • 모든 응답을 기다리기 때문에 각 참가자는 해당 트랜잭션에 대해 락을 걸고 대기하며, 이는 다른 트랜잭션의 접근을 막아 동시성에 영향을 미칩니다.
  2. Commit/Abort 단계의 동기성:
    • 모든 참가자가 Prepare에 응답한 후, 코디네이터는 각 참가자에게 Commit 또는 Abort 명령을 동기적으로 보냅니다.
    • 코디네이터는 모든 참가자의 Commit 응답이 도착할 때까지 최종 완료를 확인하지 않으며, 트랜잭션이 완전히 완료된 것을 보장하기 위해 이 과정을 동기적으로 처리합니다.

동기적 특성으로 인한 제약사항

  • 성능 저하: 모든 참여자의 응답을 기다리는 과정에서 응답 지연이 발생할 수 있습니다.
  • 지연 시간 증가: 네트워크 지연이나 참가자의 성능 문제로 인해 2PC의 완료 시간이 길어질 수 있습니다.
  • 확장성 제한: 참가자가 늘어날수록 동기적으로 기다려야 하는 응답이 많아져 확장성이 제한됩니다.
  • 고가용성 문제: 코디네이터나 참가자가 중간에 실패할 경우 전체 트랜잭션이 중단되며, 동기성으로 인해 대기 상태에 빠지게 됩니다.

코디네이터는 누가 어떻게?

1. 트랜잭션 코디네이터 역할

  • 트랜잭션 시작: 트랜잭션을 시작하고, 모든 참여 노드(예: 여러 DB 인스턴스, 서비스 등)에게 트랜잭션 참여 요청을 전달합니다.
  • Prepare 단계 관리: 모든 노드에 Prepare 요청을 보내고, 각 노드가 트랜잭션을 커밋할 준비가 되었는지 확인합니다.
  • Commit 또는 Rollback 결정: 모든 노드가 "Prepare 성공" 응답을 반환하면 Commit을, 실패 응답이 있으면 Rollback을 지시합니다.
  • 상태 관리 및 오류 처리: 네트워크 오류나 장애가 발생하면 트랜잭션을 적절하게 중단시키고, 필요 시 롤백합니다.

2. 트랜잭션 코디네이터를 수행하는 시스템

  • 데이터베이스 관리 시스템(DBMS):
    • 많은 DBMS(예: Oracle, PostgreSQL, MySQL 등)에서 자체적으로 2PC를 지원하는 경우 DB 인스턴스 자체가 트랜잭션 코디네이터 역할을 할 수 있습니다.
    • 복수의 DB 노드가 분산 트랜잭션에 참여해야 할 때, DBMS가 코디네이터가 되어 트랜잭션을 조율합니다.
  • 트랜잭션 관리 시스템(TMS):
    • **Java EE 서버(JBoss, WebSphere)**와 같은 엔터프라이즈 애플리케이션 서버는 **JTA(Java Transaction API)**를 통해 트랜잭션을 관리하며, 코디네이터 역할을 할 수 있습니다.
    • JTA는 XA 프로토콜을 사용해 DBMS와 메시지 큐 시스템이 동시에 참여하는 분산 트랜잭션을 조율할 수 있습니다.
  • 클라우드 및 분산 시스템:
    • 클라우드 환경에서는 Google Spanner, Amazon Aurora, Microsoft Azure SQL Database 같은 관리형 데이터베이스 서비스가 트랜잭션 코디네이터 기능을 제공합니다.
    • Kubernetes와 같은 환경에서는 외부 트랜잭션 관리 툴을 통해 분산 트랜잭션을 관리할 수도 있습니다.

3. 트랜잭션 코디네이터의 필요성과 한계

  • 2PC 코디네이터는 분산 트랜잭션에서 데이터 일관성을 보장하는 중요한 역할을 하지만, 네트워크 지연데드락 위험 때문에 트랜잭션 수행 속도가 느려질 수 있습니다.
  • 최근에는 이러한 한계를 극복하기 위해 SAGA 패턴과 같은 분산 트랜잭션 대안이 각광받고 있습니다.

3PC의 단계와 개선 내용

  1. Can Commit 단계:
    • 코디네이터가 트랜잭션을 수행할 준비가 되었는지 모든 참여자에게 묻습니다.
    • 각 참여자는 승인 가능 여부를 응답하고, 타임아웃이 지나면 No로 간주됩니다.
    이 단계는 기존 2PC와 비슷하지만, 참가자가 제한 시간 내 응답을 못하면 자동으로 No로 처리되므로 무기한 대기가 줄어듭니다.
  2. Pre-Commit 단계 (3PC에서 추가된 단계):
    • 코디네이터가 모든 참여자로부터 Yes 응답을 받으면, Pre-Commit 요청을 보냅니다.
    • 이때 각 참가자는 트랜잭션을 일시적으로 저장하지만 실제 커밋하지 않고 대기합니다.
    • 이 과정에서도 타임아웃이 강제되어 응답이 지연되면 트랜잭션을 롤백할 수 있습니다.
    이 단계가 추가됨으로써 참여자들은 코디네이터나 다른 참가자가 실패하더라도 롤백으로 일관성을 유지할 수 있습니다.
  3. Commit 단계:
    • 코디네이터가 최종 커밋 명령을 보내며, 모든 참가자는 타임아웃 내에 커밋을 완료합니다.
    • 코디네이터가 실패하더라도 참가자들이 Pre-Commit 단계를 통해 트랜잭션을 커밋하거나 롤백할 수 있는 상태가 되므로, 무한 대기를 피하고 일관성을 유지할 수 있습니다.

3PC의 주요 개선점: 복구 및 안정성

  • 무한 대기 방지: 각 단계에 타임아웃을 설정하여 코디네이터 또는 참여자가 응답하지 않을 경우에도 결정할 수 있습니다.
  • 비동기적 장애 복구 가능: Pre-Commit 단계를 통해 참여자들이 커밋을 확정하기 전 준비 상태에서 롤백할 수 있어, 코디네이터가 중간에 실패하더라도 참여자들은 대기 없이 결정할 수 있습니다.
  • 시스템 신뢰성 증가: 네트워크 장애나 일시적인 서버 장애가 있어도 참여자들이 독립적으로 커밋/롤백을 결정할 수 있어, 전체 시스템의 신뢰성을 높입니다.

3PC(3-Phase Commit)는 기본적으로 동기 방식의 트랜잭션 프로토콜입니다. 하지만 비동기 처리와 관련된 개념이 몇 가지 존재합니다. 

동기 방식

  • 각 단계에서의 대기: 3PC는 각 단계(Prepare, Pre-Commit, Commit)에서 코디네이터와 참가자 간의 메시지 전송이 이루어지며, 코디네이터는 각 참가자로부터 응답을 기다립니다. 이 과정에서 모든 참가자가 응답할 때까지 대기하는 방식이므로 동기적입니다.
  • 모든 참여자와의 동기화: 트랜잭션의 일관성을 유지하기 위해 코디네이터는 모든 참가자로부터 동기적으로 응답을 받아야 하며, 이를 통해 트랜잭션을 진행합니다.

비동기 처리의 개념

  • 타임아웃 및 복구: 3PC에서는 각 단계에 타임아웃이 설정되어 있어, 특정 참가자가 응답하지 않으면 자동으로 트랜잭션을 롤백할 수 있습니다. 이로 인해 비동기적 장애 복구가 가능해지며, 대기하지 않고 다른 작업을 수행할 수 있는 가능성이 생깁니다.
  • Pre-Commit 단계: Pre-Commit 단계에서 각 참가자는 트랜잭션을 준비하지만, 실제로 커밋하기 전까지는 대기합니다. 이 상태에서 참가자는 코디네이터의 요청이 없더라도 자신이 결정할 수 있는 상태가 되므로, 이 점에서 비동기적인 특성을 가질 수 있습니다.

2PC에서의 상태 확인:

  • 2PC에서도 참가자는 로그를 통해 자신의 상태를 확인할 수 있습니다. 예를 들어, Prepare 단계에서 참가자가 Vote to Commit을 보낸 후, 이 정보는 참가자의 로그에 기록됩니다.
  • 그러나 코디네이터가 실패한 경우, 참가자는 여전히 트랜잭션이 완료되었는지, 롤백되었는지 알 수 없습니다. 코디네이터가 실패하면 참가자는 대기해야 하고, 자신의 상태만으로는 트랜잭션을 커밋할지 롤백할지 결정을 내릴 수 없습니다.
    • 참가자는 코디네이터의 결정을 기다려야 하기 때문입니다.
    • 2PC에서는 코디네이터가 트랜잭션을 완료할지 롤백할지를 최종적으로 결정합니다.

3PC에서의 복구 가능성:

  • 3PCPreCommit 단계를 추가로 도입하여, 참가자가 자신의 상태를 확인하고, 트랜잭션을 진행할 수 있는 준비 상태에 있다는 사실을 명확히 알 수 있게 합니다.
  • 코디네이터가 실패하면, 참가자는 PreCommit 상태에서 자기 상태를 확인하고 복구된 코디네이터가 트랜잭션을 커밋할지 롤백할지를 최종적으로 결정할 수 있도록 대기하는 상태로 넘어갑니다.
  • 참가자는 자신이 PreCommit 상태라는 정보를 가지고 있으며, 복구 후 트랜잭션을 진행할 준비가 되었다는 것을 알지만, 최종적인 결정은 코디네이터가 내리기 때문에 대기하는 것입니다.

결론:

  • 2PC에서는 참가자가 자신의 상태를 로그에서 확인할 수 있지만, 코디네이터가 실패한 경우 참가자는 대기해야 하며 자기 혼자서 결정을 내리기 어렵습니다.
  • 3PC에서는 PreCommit 상태로 참가자가 자기 준비 상태를 확인할 수 있지만, 결정은 여전히 코디네이터가 내리게 됩니다. 다만, PreCommit 상태로 명확한 정보를 제공하여, 복구 후 결정을 더 빨리 내릴 수 있는 장점이 있습니다.
728x90
반응형
반응형

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4/dashboard

 

Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA) 강의 - 인프런

Spring framework의 Spring Cloud 제품군을 이용하여 마이크로서비스 애플리케이션을 개발해 보는 과정입니다. Cloud Native Application으로써의 Spring Cloud를 어떻게 사용하는지, 구성을 어떻게 하는지에 대해

www.inflearn.com

해당 강의를 들으면서 note taking이 필요한 부분 정리

강의는 springboot2.4를 기준으로 하고있지만, 나는 springboot2.7을 기준으로 하였기 때문에 설정에 변경이 필요한 부분이 많이 있었음..

 

관련 글

2024.01.12 - [서버 세팅 & tool] - [mvn] springboot 프로젝트 바로/여러 인스턴스로 실행하기

 

[mvn] springboot 프로젝트 바로/여러 인스턴스로 실행하기

환경: 윈도우, maven 3.8이 설치되어 있음 터미널로 실행하는 명령어 pom.xml 이 있는 폴더로 가서 아래와 같이 명령어를 주면 실행된다. mvn spring-boot:run # 추가적으로 포트를 수정해야한다면 mvn spring-

bangpurin.tistory.com

2024.01.15 - [개발/spring] - [h2] h2를 기본 데이터베이스로 사용하기

 

[h2] h2를 기본 데이터베이스로 사용하기

환경: springboot2.7, java 17, maven h2를 내장하는 테스트 프로젝트 생성 아래와 같이 설정했다고 가정 com.h2database h2 1.4.200 runtime # 1.3.176으로 낮추면 해결됨 ((최신 버전으로 해도 그러는지 확인 필요)) h

bangpurin.tistory.com

2024.01.27 - [개발/spring] - [security] jwt NoClassDefFoundError: javax/xml/bind/DatatypeConverter

 

[security] jwt NoClassDefFoundError: javax/xml/bind/DatatypeConverter

환경: java 17, springboot 2.7.6 아래 라이브러리를 사용하여 JWT토큰 파싱할 때 에러날 경우 소스: io.jsonwebtoken jjwt 0.9.1 subject = Jwts.parser().setSigningKey(environment.getProperty("token.secret")) .parseClaimsJws(jwt).getBody

bangpurin.tistory.com

2024.01.29 - [개발/spring-cloud] - [cloud] 파일을 동적으로 관리하는 config server

 

[cloud] 파일을 동적으로 관리하는 config server

환경: springboot2.7.6, java 17, springcloud 2021.0.8 application.properties/yml과 같은 설정파일을 수정하면 서버를 재시작해야 한다는 부담이 있다. spring cloud에서 제공하는 config server를 이용하면 설정 파일을

bangpurin.tistory.com

2024.01.29 - [개발/spring-cloud] - [cloud] spring cloud bus

 

[cloud] spring cloud bus

환경: springboot2.7.6, java 17, springcloud 2021.0.8 사용 이유: 분산 시스템의 노드(서비스들)를 경량 메시지 브로커(rabbit mq)와 연결하여 상태 및 구성에 대한 변경 사항을 연결된 노드에게 전달(broadcast)한

bangpurin.tistory.com

2024.01.29 - [서버 세팅 & tool/rabbitmq] - [windows] rabbitmq cmd 설치

 

[windows] rabbitmq cmd 설치

환경: windows11 프로, 64비트 윈도우에서 rabbitmq 설치 시 erlang을 필수로 설치해야한다. 딱히 호환되는 버전 지정 없이, 최신버전들로 설치해도 문제없다. powershell 관리자 권한 실행 ctrl + r powershell

bangpurin.tistory.com

2024.01.29 - [개발/spring-cloud] - [cloud] property값 암호화하여 사용하기

 

[cloud] property값 암호화하여 사용하기

환경: springboot2.7.6, java 17, springcloud 2021.0.8 이전글: 2024.01.29 - [개발/spring] - [cloud] 파일을 동적으로 관리하는 config server 2024.01.29 - [개발/spring] - [cloud] spring cloud bus spring cloud config service에서 파일로

bangpurin.tistory.com

2024.02.04 - [서버 세팅 & tool/kafka] - [windows] kafka 실행

 

[windows] kafka 실행

환경: 윈도우11, kafka2.13-3.6.1 카프카는 공홈에서 받아주고, 압축을 풀어준다. 카프카/주키퍼 실행 시 아래와 같은 에러 발생한다. 확인해보니 kafka-server-start.bat실행 시 같은 폴더 안에 있는 kafka-run

bangpurin.tistory.com

2024.02.04 - [서버 세팅 & tool/kafka] - [windows] kakfa connect; mariadb 설치

 

[windows] kakfa connect; mariadb 설치

환경: windows11, java17, springboot2.7.6 kakfa connect를 통해 데이터를 import/export 가능 코드 없이 configuration으로 데이터 이동 가능 Standalone mode, distribution mode 지원 restfult API 지원 stream, batch 형태로 데이터

bangpurin.tistory.com

2024.02.04 - [서버 세팅 & tool/kafka] - [windows] kakfa connect 연동

 

[windows] kakfa connect 연동

2024.02.04 - [서버 세팅 & tool/kafka] - [windows] kakfa connect; mariadb 설치 사용하게 된 배경: 기존의 서버가 h2 디비를 사용하여 1 인스턴스 - 1 디비를 사용하고 있었는데, 이렇게 되면 여러 인스턴스를 띄

bangpurin.tistory.com

2024.02.04 - [서버 세팅 & tool/kafka] - kakfa 작업시 띄울 것

2024.02.08 - [개발/kafka] - [spring-kafka] producer, consumer 기초

 

[spring-kafka] producer, consumer 기초

환경: springboot2.7.6, java11, h2 연결 producer 정보 보내는 쪽 1. pom.xml 추가 org.springframework.kafka spring-kafka 2. producer kafka 연결 설정 @EnableKafka @Configuration public class KafkaProducerConfig { @Bean public ProducerFactory pr

bangpurin.tistory.com

2024.02.08 - [개발/kafka] - [spring-kafka] 데이터 저장 시 sink connect 사용하여 단일 디비로

 

[spring-kafka] 데이터 저장 시 sink connect 사용하여 단일 디비로

이전 작업: 2024.02.04 - [서버 세팅 & tool/kafka] - [windows] kakfa connect 연동 2024.02.08 - [개발/kafka] - [spring-kafka] producer, consumer 기초 환경: springboot 2.7.6, spring-kafka, java11 목표: 인스턴스 별로 하나씩 있던 디

bangpurin.tistory.com

2024.02.08 - [개발/spring-cloud] - [resilience4j] circuit breaker

 

[resilience4j] circuit breaker

환경: springboot2.7.6, java11 circuit breaker 장애가 발생하는 서비스에 반복적인 호출이 되지 못하게 차단 특정 서비스가 정상적으로 동작하지 않을 경우 다른 기능으로 대체수행하여 장애를 회피함 ope

bangpurin.tistory.com

2024.02.08 - [개발/spring-cloud] - [spring-cloud] zipkin 분산 환경 모니터링

 

[spring-cloud] zipkin 분산 환경 모니터링

환경: springboot2.7.6, spring cloud2021.0.8, java17 zipkin 분산 환경의 데이터 수집, 추적 시스템(오픈소스, 트위터 시작, google drapper에서 발전) 분산 환경에서의 시스템 병목 현상 파악 collector, query service, da

bangpurin.tistory.com

2024.02.08 - [개발/spring-cloud] - [spring-cloud] micrometer, prometheus

 

[spring-cloud] micrometer, prometheus

환경: springboot2.7.6, java17 micrometer jvm 기반의 애플리캐이션 metrics 제공 springboot2 + premetheus 등 다양한 모니터링 시스템 지원 (구) turbine server -> hystrix client timer 짧은 지연 시간, 이벤트의 사용 빈도

bangpurin.tistory.com

2024.02.08 - [서버 세팅 & tool] - [windows] prometheus & grafana 설치

 

[windows] prometheus & grafana 설치

환경: windows11, springboot2.7.6, java17 prometheus(저장 서버) metrics를 수집하고 모니터링 및 알람에 사용되는 오픈소스 애플리케이션 시간순으로 데이터가 남음(time series database; TSDB) pull 방식의 구조와

bangpurin.tistory.com

2024.02.10 - [서버 세팅 & tool/docker] - [windows] docker; 컨테이너 가상화

 

[windows] docker; 컨테이너 가상화

환경: windows11 virtualization 물리적인 컴퓨터 리소스를 다른 시스템이나 애플리케이션에서 사용할 수 있도록 제공 플랫폼 가상화 리소스 가상화 하이퍼바이저(hypervisor) Virtual machine manager(VMM) 다수

bangpurin.tistory.com

2024.02.10 - [서버 세팅 & tool/docker] - [windows] docker 이미지 만들고 올리고 실행

 

[windows] docker 이미지 만들고 올리고 실행

환경: windows11, 아래 설치 진행 2024.02.10 - [서버 세팅 & tool/docker] - [windows] docker; 컨테이너 가상화 [windows] docker; 컨테이너 가상화 virtualization 물리적인 컴퓨터 리소스를 다른 시스템이나 애플리케이

bangpurin.tistory.com

2024.02.12 - [서버 세팅 & tool/docker] - [windows] 네트워크 세팅, rabbitmq 세팅, config server 세팅

 

[windows] 네트워크 세팅, rabbitmq 세팅, config server 세팅

환경: windows11, springboot2.7.6, java17 2024.02.10 - [서버 세팅 & tool/docker] - [windows] docker 이미지 만들고 올리고 실행 [windows] docker 이미지 만들고 올리고 실행 환경: windows11, 아래 설치 진행 2024.02.10 - [서버

bangpurin.tistory.com

2024.02.18 - [서버 세팅 & tool/docker] - [windows] discovery server 세팅, docker 배포

 

[windows] discovery server 세팅, docker 배포

환경: windows11, springboot2.7.6, java17 2024.02.12 - [서버 세팅 & tool/docker] - [windows] 네트워크 세팅, rabbitmq 세팅, config server 세팅 [windows] 네트워크 세팅, rabbitmq 세팅, config server 세팅 환경: windows11, springboot2

bangpurin.tistory.com

2024.02.18 - [서버 세팅 & tool/docker] - [windows] gateway server 세팅, docker 배포

 

[windows] gateway server 세팅, docker 배포

환경: windows11, springboot2.7.6, java17 2024.02.12 - [서버 세팅 & tool/docker] - [windows] 네트워크 세팅, rabbitmq 세팅, config server 세팅 2024.02.18 - [서버 세팅 & tool/docker] - [windows] discovery server 세팅, docker 배포 gatew

bangpurin.tistory.com

2024.02.18 - [서버 세팅 & tool/docker] - [windows] mariadb 세팅, docker 배포

 

[windows] mariadb 세팅, docker 배포

환경: windows11, springboot2.7.6, java17 2024.02.18 - [서버 세팅 & tool/docker] - [windows] gateway server 세팅, docker 배포 2024.02.04 - [서버 세팅 & tool/kafka] - [windows] kakfa connect; mariadb 설치 마리아 DB는 프로젝트가 아

bangpurin.tistory.com

2024.02.25 - [서버 세팅 & tool/docker] - [windows] kafka/zookeeper docker배포, docker compose

 

[windows] kafka/zookeeper docker배포, docker compose

환경: windows11, springboot2.7.6, java17 DOCKER COMPOSE? Simplified control: Docker Compose allows you to define and manage multi-container applications in a single YAML file. https://docs.docker.com/compose/intro/features-uses/#:~:text=Simplified%20cont

bangpurin.tistory.com

2024.02.25 - [서버 세팅 & tool/docker] - [windows] zipkin 서버 docker 배포

 

[windows] zipkin 서버 docker 배포

환경: windows11, springboot2.7.6, java17 공식 사이트에서 제공하는 도커 이미지를 띄워보기 https://zipkin.io/pages/quickstart.html Quickstart · OpenZipkin Quickstart In this section we’ll walk through building and starting an instan

bangpurin.tistory.com

2024.02.25 - [서버 세팅 & tool/docker] - [windows] prometheus+grafana docker 배포

 

[windows] prometheus+grafana docker 배포

환경: windows11, springboot2.7.6, java17 공식 사이트에서 제공하는 도커 이미지를 띄워보기 도커이미지 프로메테우스 https://hub.docker.com/u/prom Docker hub.docker.com 그라파나 https://grafana.com/docs/grafana/latest/setu

bangpurin.tistory.com

2024.02.29 - [서버 세팅 & tool/docker] - [windows] user-service server 세팅, docker 배포

 

[windows] user-service server 세팅, docker 배포

환경: windows11, springboot2.7.6, java17 springboot 프로젝트인 user service 를 도커에 올려본다. 1. pom.xml 경로에 Dockerfile 생성 FROM openjdk:17-ea-slim-buster VOLUME /tmp COPY target/user-service-1.0.jar user-service.jar ENTRYPOINT ["

bangpurin.tistory.com

2024.02.29 - [서버 세팅 & tool/docker] - [windows] order-service server 세팅, docker 배포

 

[windows] order-service server 세팅, docker 배포

환경: windows11, springboot2.7.6, java17 springboot 프로젝트인 order service 를 도커에 올려본다. 1. pom.xml 경로에 Dockerfile 생성 FROM openjdk:17-ea-slim-buster VOLUME /tmp COPY target/order-service-1.0.jar order-service.jar ENTRYPOINT

bangpurin.tistory.com

2024.02.29 - [서버 세팅 & tool/docker] - [windows] catalog-server 세팅, docker 배포

 

[windows] catalog-server 세팅, docker 배포

환경: windows11, springboot2.7.6, java17 springboot 프로젝트인 catalog service 를 도커에 올려본다. 1. pom.xml 경로에 Dockerfile 생성 FROM openjdk:17-ea-slim-buster VOLUME /tmp COPY target/catalog-service-1.0.jar catalog-service.jar ENTR

bangpurin.tistory.com

2024.02.29 - [개발/spring] - [application.yml] 프로파일 옵션으로 배포 설정 분리

 

[application.yml] 프로파일 옵션으로 배포 설정 분리

환경: springboot2.7.6 로컬에서 개발할 때, 그리고 운영 환경으로 배포할 때 내용물에 따라 설정파일을 분리할 수 있으며, 환경에 따라 다르게 가져가야 한다. -- 소스 실행 mvn spring-boot:run -Dspring-boot.

bangpurin.tistory.com

2024.02.29 - [architecture/micro service] - [arch] event sourcing, cqrs, saga

 

[arch] event sourcing, cqrs, saga

event sourcing event driven architecture monolithic 단일 데이터베이스 트랜잭션 처리를 완벽하게 -> ACID atomicity consistency isloation durable msa 각 서비스마다 독립적인 데이터베이스(polyglot) API를 통해 접근 atomicy

bangpurin.tistory.com

 

도커에 모두 구성하고 최종 테스트

1. 유저 생성

2. 주문

3. 주문 후 수량 감소 확인

4. 주문 후 유저 매핑 확인

728x90
반응형

'architecture > micro service' 카테고리의 다른 글

2PC vs 2PL  (1) 2024.11.06
[arch] EDA, event sourcing, saga, 2pc  (0) 2024.02.29
[gRPC] gRPC gateway란  (0) 2022.01.24
[arch] gRPC란?  (0) 2022.01.20
[arch] DDD pattern 와 aggregate란  (0) 2022.01.13
반응형

gRPC proxy? gateway?

두 명칭이 혼용되는 것 같으나, 개념을 보면 같은 것을 알 수 있음

Grpc-gateway, a server for gRPC and RESTful styles Grpc-gateway is a server that routes HTTP/1.1 request with JSON bodies to gRPC handlers with protobuf bodies. This means you can define your entire API as gRPC methods with requests and responses defined as protobuf, and then implement those gRPC handlers on your API server. Grpc-gateway can then transform your gRPC service into a RESTful API.

gRPC gateway는 client로부터 restfulAPI형식으로(HTTP JSON) 정보를 받고, 이 정보를 gRPC server 서비스의 형식대로 protobuf로 변환한 다음에 그 message를 gRPC server에 gRPC로 보내는 서버다. 주로 rpc 통신이 힘든 경우 converter로 사용한다. 

gRPC gateway

gRPC Gateway 플러그인을 사용하면 gRPC 서비스에 REST API 인터페이스를 제공할 수 있도록 go 런타임에서 작동하는 프록시 서버와 Swagger 문서를 generate 해준다. (go 이외 다른 언어는 미지원) 

https://medium.com/@thinhda/compare-grpc-web-with-grpc-gateway-fa1e2acdf29f

 

Compare gRPC-Web with grpc-gateway

gRPC-Web and grpc-gateway have advantages and disadvantages. Depending on your business, what you have and what you need, …

medium.com

https://deepbaksu.github.io/2021/05/01/how-to-REST-from-gRPC/

 

gRPC에서 REST까지

gRPC를 통해 REST 서버를 만들어 보자. gRPC 클라이언트로 연결할 수 있으면 좋지만, REST가 보편적이기 때문에 REST API를 구현해줘야 할 필요가 있다. 또한, Heroku에서는 HTTP/2 를 지원하지 않기 때문에

deepbaksu.github.io

 

js에서 바로 gRPC 서버로 통신을 하려고 하는 경우?

JavaScript running in the browser does not provide full control over HTTP2.
The gRPC protocol uses features of HTTP/2 that cannot be controlled by JavaScript.
So that a proxy is required.

gRPC-WEB은 gRPC를 브라우저에서 사용가능하도록 브라우저 지원을 추가한 스펙이다. 현재 브라우저 API가 HTTP/2 엑세스를 지원하지 않기 때문에 gRPC-WEB을 사용하여 브라우저에서 서버로 직접 통신을 할 수는 없다.

즉, js단에서 gRPC-WEB 으로 개발해도 브라우저가  HTTP/2 통신을 지원하지 않기 때문에 proxy가 필요한 것.

https://velog.io/@kyusung/grpc-web-example

 

gRPC-web 삽질기

Javascript 기반으로 브라우저에서 gRPC를 사용하려고 하였지만, 적용하지 못한 내용에 대한 기록입니다.gRPC에 대해서 간략히 설명하고 gRPC 라이브러리를 이용하여 node.js 기반의 Application을 만드는

velog.io

https://medium.com/@denis.zhbankov/grpc-web-via-http2-b05c8c8f9e6

 

gRPC-Web via HTTP2

Lately, my backend colleagues and I decided to give gRPC framework a try instead of Protocol Buffers serialiser over WebSockets transport.

medium.com


사실 이 모든 글이 보고있던 프로젝트의 아래 설정 값 하나로부터 시작되었는데.. 혹시 spring/grpc에 haproxy 설정이 가능한지 궁금하여 이모저모 살펴본 것이다. 그러던 중 위 내용을 확인하였고 아래 값은 그냥 (개발자가 만든) 커스텀 값임을 파악하게 되었다.

grpc.use-haproxy=true

그래도 관련 내용 살펴본게 아까워서 추가적으로 적어본다.

HAProxy?

= software loadbalaner, reverse proxy

https://leffept.tistory.com/309

 

[HAProxy]HAProxy 란?

HAProxy 란? HAProxy는 기존의 하드웨어 스위치를 대체하는 소프트웨어 로드 밸런서로, 네트워크 스위치에서 제공하는 L4, L7 기능 및 로드 밸런서 기능을 제공한다. 설치가 쉽고 빠르기에 서비스 이

leffept.tistory.com

https://prohannah.tistory.com/65

 

로드밸런서 (L4, L7, NginX, HAProxy)

네트워크 로드밸런싱에서 주로 언급되는 로드밸런서 L4, L7, HAProxy 위주로 설명하겠다. L4 (Transport Layer) L4는 IP, Port, Session 기반으로 로드밸런싱하며 웬만한 서비스에서는 이것만으로 부하 분산이

prohannah.tistory.com

 

HAProxy vs nginx?

Nginx는 웹서버로서 load balancing, reverse proxy 기능 또한 제공.
굳이 웹 서버로서의 역할이 필요가 없다면(캐싱) HAProxy의 load balancing이 헬스체크가 가능하기도 하고 좀 더 가벼움

https://seokjun.kim/haproxy-and-nginx-load-balancing/

 

HAProxy 와 Nginx 의 로드밸런싱

NGINX Nginx 는 대표적인 웹서버인 Apache 의 문제점을 해결하면서 만들어진 웹서버로 비동기 방식으로 개발되어 가볍고 빠른 것으로 유명한 오픈소스 어플리케이션이다. Nginx 는 http 나 reverse proxy 같

seokjun.kim


완전 다른 내용/ 참고..

ingress proxy?

일반적인 Ingress는 외부로부터 서버 내부로 유입되는 네트워크 트래픽을, egress는 서버 내부에서 외부로 나가는 트래픽을 의미. 
쿠버네티스에서의 Ingress는 외부에서 실행 중인 Deployment와 Service에 접근하기 위한, 일종의 관문 (Gateway) 같은 역할을 하며 L7에서의 요청을 처리한다.

https://kubernetes.io/ko/docs/concepts/services-networking/ingress/

 

인그레스(Ingress)

FEATURE STATE: Kubernetes v1.19 [stable] 클러스터 내의 서비스에 대한 외부 접근을 관리하는 API 오브젝트이며, 일반적으로 HTTP를 관리함. 인그레스는 부하 분산, SSL 종료, 명칭 기반의 가상 호스팅을 제공

kubernetes.io

https://blog.naver.com/alice_k106/221502890249

 

162. [Kubernetes] 1편 : 쿠버네티스 Ingress 개념 및 사용 방법, 온-프레미스 환경에서 Ingress 구축하기

이번 포스트에서는 쿠버네티스에서 인그레스 (Ingress) 를 사용하는 방법, 그리고 Public 클라우드를 쓰...

blog.naver.com

 

728x90
반응형
반응형
gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment.

 

RPC(Remote Procedure Call)?

  • 별도의 원격 제어를 위한 코딩 없이 다른 주소 공간에서 함수나 프로시저를 실행할 수 있게하는 프로세스 간 통신 기술
  • 개발자가 원격 상호 작용에 대한 세부 정보를 명시적으로 코딩하지 않아도 프레임워크가 자동 핸들링함
  • 클라이언트 코드에서는 직접 서버 코드의 함수를 호출하는 것처럼 보임
  • 언어에 구애받지 않고 원격의 프로시져를 호출가능(즉, 클라이언트 코드 언어와 서버 코드 언어가 다른 언어로 쓰일 수 있음 )

grpc

gRPC?

  • RPC인데 구글이 만든 RPC이다. g가 구글인줄 알았는데 그건 또 아니라고 반박함ㅋㅋㅋ
    https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md
  • IDL: proto 사용
  • http/2 기반 통신
  • json 기반의 통신보다 가볍고 속도가 빠르다.(성능이점)
  • 클라이언트에는 서버와 통신 시 Stub을 inject 하여 통신하며 소스에 바로 로딩하여 사용가능
  • protoc 파일을 작성 시 request이나 response앞에 붙은 'stream' 유무에 따라 4가지 방식이 있음
    • unary(단방향): 클라이언트가 서버에 단일 요청을 보내고 일반 함수 호출처럼 단일 응답을 받음(1개 request , 1개 respone)
      • rpc SayHello(HelloRequest) returns (HelloResponse);
    • server stream(서버 스트리밍): 서버가 클라이언트의 요청에 대한 응답으로 메시지 스트림을 반환(1개 request, n개 response)
      • rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
    • client stream(클라이언트 스트리밍): 클라이언트가 단일 메시지 대신 서버에 메시지 스트림을 보냄(n개 request, 1개 response)
      • rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
    • bi-directional(양방향): 서버, 클라이언트 양쪽에서 메시지 스트림을 보냄(n개 request, n개 response)
      • rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
  • 클라이언트 stub: 서버를 추상화해놓은 것 / 클라이언트의 수신처리 방법
    • BlockingStub: 동기적으로 통신하는 방법으로, 서버로부터 응답이 올 때까지 대기.
      • Unary RPC와 Server Streaming RPC에서만 사용
    • AsyncStub (Stub): 비동기적으로 통신하는 방법으로, 서버로부터 오는 응답을 StreamObserver 객체가 대신 받아서 처리
      • 모든 방식의 RPC에서 사용
    • FutureStub: 비동기적으로 통신하는 방법으로, 서버로부터의 응답 도달에 상관 없이 일단 ListenableFuture로 래핑된 객체를 반환. 서버로부터 오는 응답이 오면 ListenableFuture 객체를 통해 전달받은 메시지를 언래핑할 수 있음.
      • Unary RPC에서만 사용

즉 위 내용을 모두 합치면 아래와 같은 7가지 통신방법이 존재한다.

  unary server stream client stream bi-directional
blocking o o x x
asyn o o o o
future o x x x

 

gRPC 단점

  • 브라우저에서 직접 gRPC 통신 불가, proxy 를 통하거나 브라우저 -> 클라이언트 연동 서버 -> gRPC 서버 순으로 요청해야 함
  • protobuf(이진형식)으로 인코딩되어 송수신에 효율적이나 사람이 읽을 수 없는 데이터라 네트워크 단에서 볼 때 어려움

 

왜 MSA에서 많이 쓰이나 싶었는데, MSA구조의 특정 상 안 그래도 많은 api 콜을 gRPC로 대체하면 성능 향상에 도움이 될 것 같다..

다음 글에서는 간단하게 서버와 클라이언트를 구현해본다.

2022.01.20 - [개발/spring] - [grpc] springboot2 grpc server/client coding

 

[grpc] springboot2 grpc server/client coding

목표: java11 / gradle 7.3.3 multi project / springboot 2.6.2 / grpc server & client 개발 참고 블로그: https://velog.io/@chb1828/Spring-boot%EB%A1%9C-Grpc%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B..

bangpurin.tistory.com


IDL(Interface Definition Language)?
: 정보를 저장하는 규칙

1. xml
2. json
3. proto

Protocol buffers(proto)?

  • 직렬화 데이터 구조, 데이터를 전송할 때 구조화된 데이터를 전환하는 방법 중 하나
  • XML의 문제점을 개선하기 위해 제안된 IDL이며, XML보다 월등한 성능을 지님
  •  .proto 파일에 protocol buffer 메세지 타입/구조를 정의
  • message = '이름-값'의 쌍을 포함하는 작은 논리적 레코드
  • protoc 컴파일러로 컴파일 하면 데이터에 접근할 수 있는 각 언어에 맞는 형태의 데이터 클래스를 생성해주며 만들어진 클래스는 각 필드를 위한 접근자 뿐 아니라 전체 구조를 바이트로 직렬화하거나 바이트로부터 전체 구조를 파싱하는 메서드들을 제공함

xml 보다..

  • XML보다 작고 빠르고 간단함
  • 파일 크기가 3에서 10배 정도 작음
  • 속도가 20에서 100배 정도 빠름
  • XML보다 가독성이 좋고 명시적

 


참고자료

https://tech.lattechiffon.com/2021/06/30/grpc-%EC%84%B8-%EA%B0%80%EC%A7%80-streaming-rpc-%EA%B5%AC%ED%98%84-java/

 

gRPC 세 가지 Streaming RPC 구현 (Java) – 라떼쉬폰의 코드 베이커리

이전 글에서 gRPC를 이용하여 Unary RPC를 구현했던 것에 이어서, 이번에는 Server Streaming, Client Streaming 및 Bidirectional Streaming RPC를 Java로 구현해보도록 하겠습니다. Streaming 패턴 구현에 앞서 gRPC에서

tech.lattechiffon.com

 

https://qwer9412.tistory.com/40

 

4. grpc의 여러가지 통신 기법

grpc는 4개의 통신을 지원한다. - unary (1개 request , 1개 respone) - server stream (1개 request, n개 response) - client stream (n개 request, 1개 response) - bi stream (n개 request, n개 response) 그리..

qwer9412.tistory.com

 

728x90
반응형

+ Recent posts