Skip to content

Commit aa30042

Browse files
committed
feat: 내용 추가
1 parent d3b7b0d commit aa30042

File tree

2 files changed

+148
-2
lines changed

2 files changed

+148
-2
lines changed

content/blog/2025y/graphql/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ GraphQL 기반 서비스를 처음으로 접하면서 다음과 같은 고민을
1515
- `"/graphql은 누가 라우팅할까?"`
1616
- `"예외는 어떻게 처리할까?"`
1717

18-
이 글에서는 Netflix DGS Framework를 기준으로 이러한 의문들을 해결해보고자 한다.
18+
이 글에서는 Netflix DGS Framework를 기준으로 이러한 고민들을 해소해보고자 한다.
1919

2020
> 이 글을 통해 아래의 내용을 학습할 수 있다.
2121
> 1. GraphQL API의 호출 구조 이해

content/blog/2025y/review/index.md

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,157 @@
11
---
22
title: 2025년 기록
33
date: "2025-01-01"
4-
update: "2025-05-11"
4+
update: "2025-07-21"
55
tags:
66
- 기록
77
---
88

9+
# 보상 트랜잭션으로 분산 환경에서도 안전하게 환전하기
10+
11+
> [토스ㅣSLASH 24 - 보상 트랜잭션으로 분산 환경에서도 안전하게 환전하기](https://www.youtube.com/watch?v=xpwRTu47fqY)
12+
13+
양방향 무료 환전! MSA 환경에서 서로 데이터베이스도 다르게 보고 있을 때 트랜잭션을 어떻게 지켜내야할까?
14+
15+
<h3>분산 트랜잭션</h3>
16+
17+
1. **2PC**
18+
- Commit 가능 여부를 질의 (Voting)
19+
- DB가 가능하다고 응답, 한 개라고 불가능하다고 응답한다면 이전 작업들을 모두 롤백한다.
20+
- 강한 일관성, 낮은 가용성, 낮은 확장성
21+
2. **사가 패턴**
22+
- 각 서비스의 작은 트랜잭션들을 실행하면서 진행
23+
- 특정 단계에서 실패하면 보상 트랜잭션 실행
24+
- **높은 가용성, 높은 확장성, 중간 상태가 노출되고 보상 트랜잭션 구현을 필요로 한다.**
25+
- **코레오그래피 사가**
26+
- 중앙제어자 없이 메세지 브로커를 통해 이벤트를 교환하는 방식
27+
- 단일장애지점이 없고 각 서비스가 느슨하게 결합됨
28+
- 하지만 현재 진행중인 트랜잭션의 상태를 추적하거나 디버깅하기 어렵다.
29+
- **오케스트레이션 사가**
30+
- 중간제어자가 직접 서비스에게 트랜잭션과 보상 트랜잭션을 명령하는 방식
31+
- 중간제어자가 단일장애지점이 되고 모든 서비스들이 결합된다.
32+
- 하지만 현재 진행중인 상태를 추적하기가 쉽다.
33+
3. **예외 핸들링**
34+
- 정상적인 실패 : 잔액 부족, 계좌해지, 거래제한
35+
- 비정상적인 실패 : 서버에러, 타임아웃
36+
4. **배지 재처리**
37+
- 환전 지연 이벤트를 발행하지도 못하고 서버가 죽은 경우 배치를 통해 ‘출금 취소’ → ‘환전 실패’ 처리
38+
5. **결과적 일관성**
39+
- 출금 취소에 실패한다면 CDL에 적재하여 지정한 횟수만큼 재시도 한다.
40+
- 그래도 실패한다면 개발자가 직접 개입하여 DL 서버를 통해 다시 메시지를 발행한다.
41+
6. **DB: State Path**
42+
- 이벤트 조율을 오케스트레이터로 관리하기 때문에 매 상태를 확인하여 중간에 멈춘 환전을 알림으로 보낸다.
43+
7. **트랜잭셔널 메세징**
44+
- 입금 실패로 인한 환전 실패 처리와 출금 취소 메세지 발행은 항상 같이 이루어져야 한다!
45+
- 즉, 로컬 트랜잭션 커밋과 메세지 발행이 원자적으로 이루어져야 한다.
46+
- 트랜잭셔널 아웃박스 패턴으로 해결할 수 있지만, 토스는 프로듀서 데드 레터(PDL)로 해결한다.
47+
- 출금취소 메시지 발행에 실패한다면 PDL에 메시지를 발행한다. DL서버가 PDL을 컨슘하여 브로커로 다시 메세지를 발행한다.
48+
49+
사가패턴에서는 중간 상태가 노출되기 때문에 출금부터 진행해야 한다.
50+
입금,출금은 HTTP로 동기처리로 진행하기 때문에 타임아웃 구현이 필요하다.
51+
만약 출금은 성공했지만 입금에 실패한 경우 출금 취소로 처리한다.
52+
상대 계좌 서버나 네트워크 문제로 출금 결과 확인에 실패한다면 어떻게 처리해야 하나?
53+
카프카 메세지를 지연 발송하여 상대 계좌 서버나 네트워크 문제가 회복되는 시간을 벌 수 있다. (30초 → 1분 ..)
54+
55+
56+
> **트랜잭셔널 아웃박스 패턴과 PDL에 대한 질문**
57+
> **Q. 내부적으로 메시징 발행 재처리가 DL 중심으로 잡혀 있는지?**
58+
> A. 토스뱅크는 모든 토픽에 대하여 기본적으로 PDL이 적용되어 있기에 따라서 추가적인 아웃박스 패턴 등을 적용하지 않았다.
59+
> **Q. DL 서버에서 메시지 발행에 실패하면 어떻게 되는지?**
60+
> A. DL 서버의 PDL 메시지 처리도 컨슈머로 동작하므로, CDL을 이용하여 재처리할 수 있다.
61+
> **Q. PDL이 아웃박스 패턴보다 나은 점**
62+
> A. 모든 서비스에서 일괄 적용하기 쉽다는 장점이 있다. 아웃박스 패턴은 서비스 변경과 아웃박스 데이터 저장을 트랜잭션으로 묶어 처리해야 하므로, 서비스 DB 내에 아웃박스 테이블이 존재해야한다.
63+
> 토스뱅크에서는 수백개의 MSA 서버가 독립된 스키마(db)를 바라보고 있고, 이 스키마들은 수십개의 분리된 물리 서버 위에 그룹핑되어 존재한다.
64+
> 또, 사용하는 DB 역시 Oracle, MySQL, Mongo 등으로 다양하기에, 모든 MSA에 일괄적으로 아웃박스 패턴을 적용하려면, DB종류, 물리서버, 스키마마다 아웃박스 테이블들을 만들어줘야하고, 테이블에서 메시지를 발행하는 어플리케이션도 각각 작성해야 한다. 반면 PDL은 모든 서버에서 동일한 메시지 브로커를 바라볼 수 있기 때문에, 라이브러리 형태로 제공되어 일괄 적용하는데 편리한 점이 있다.
65+
> **Q. PDL 메시지 브로커도 고가용성이 보장되는지, 그렇다면 불필요한 인프라 비용이 생기는 것은 아닌지?**
66+
> A. PDL 메시지 브로커로 서버에서 로그를 남길때 사용하는 로그 Kafka 클러스터를 사용하고 있다. 즉 고가용성을 위한 세팅이 적용되어 있으며 평상시에도 항상 사용하고 있기 때문에, PDL 메시지가 발행되지 않더라도 메시지 브로커에 문제가 생기면 그 즉시 알게 된다.
67+
68+
69+
## 생각할 점
70+
71+
- 데이터 일관성을 아웃박스 패턴으로 해결할 수 있지만, 이 방법의 단점을 이해하고 PDL을 고려할 수 있음
72+
73+
# 선물하기 시스템의 상품 재고는 어떻게 관리되어질까?
74+
75+
> [우아한 기술블로그 링크](https://techblog.woowahan.com/2709/)
76+
77+
1. **총 4가지 시스템**
78+
- 서비스는 상품의 속성을 정의하고, 관리하는 `상품시스템`
79+
- 상품 엔티티 : 상품명, 상품 이미지 등 보여지는 요소
80+
- 판매상품 엔티티 : 상품이 어떤식 (판매기간)으로 판매될지를 결정
81+
- **가격정책 엔티티 : 어떻게 판매될지 결정된 판매상품을 어떠한 가격에 얼만큼 팔지 (원금액, 할인금액, 인당재고, 총재고)**
82+
- 정의된 상품을 어느 카테고리에 매핑시켜 노출시킬지를 결정하는 `전시시스템`
83+
- 상품을 상품권화 시키기 위해 고객님의 구매가 이루어질 수 있도록 하는 `구매시스템`
84+
- 상품권을 음식주문시 사용할 수 있도록 하는 `상품권 시스템`
85+
2. **요구사항**
86+
- 상품의 권종별로 전체 재고수량과 인당 재고수량이 관리되어야 한다.
87+
- 상품의 권종은 전체 재고량을 초과하여 판매되면 안된다.
88+
- 판매가 한번 시작된 상품의 경우에는 재고량 수정이 가능하나 최초 설정된 재고량 이상을 설정할 수 없어야 한다.
89+
- 상품권은 한 개씩 구매한다.
90+
3. **설계**
91+
- 전체 재고량의 경우 RDB에 저장하여 관리하고, 트랜잭션이 일어나는 재고사용량의 관리는 연산속도가 빠른 in-memory DB를 사용한다.
92+
- 연산처리는 단일 스레드에서 처리하는 Redis를 이용하여 동시성 이슈를 해결한다.
93+
- 레디스의 데이터 유실이 일어날 수 있으므로, 재고 사용량 데이터를 RDB에 싱크한다.
94+
- 구매번호는 유니크한 값이고, Redis의 Set 자료구조는 중복을 허용하지 않기때문에 구매번호를 Set에 저장할 경우 SCARD 오퍼레이션을 통해 손쉽게 사용량을 가져올 수 있다.
95+
- 재고량 증가 혹은 감소시점에 (삽입만 발생하는) 재고량 히스토리 정보를 누적한다.
96+
97+
<h3>구현</h3>
98+
99+
```
100+
`{상품번호}:{권종}:stock:{타입}`
101+
102+
# 전체 재고 관리
103+
S0630000RU:5000:stock:total
104+
105+
# 개인별 재고 관리
106+
S0630000RU:5000:stock:{회원번호}
107+
```
108+
109+
- **S0630000RU**: 상품번호 (오늘도 수고했어 상품)
110+
- **5000**: 권종 (5000원권)
111+
- **stock**: 재고 구분자
112+
- **total**: 전체 재고 / **201209320003**: 회원번호
113+
- `ADD 구매번호`
114+
115+
<h3>재고 사용량 증가</h3>
116+
117+
구매 과정에 필요한 타 시스템의 API와 동기 방식으로 진행
118+
119+
> 이 플로우는 동기화된 메소드로 실행됨 (배민이지만 상품권 구매는 빈번하지 않다고 판단한듯)
120+
121+
- **상품 시스템 플로우**
122+
- `트랜잭션 시작`
123+
- 구매가 가능한 상태인지 (왜 트랜잭션 열고 유효성 검증하지?)
124+
- 총 재고수량 & 인당 제한수량 확인
125+
- 가능한 경우 Redis에 구매번호 ADD
126+
- 총 재고수량 & 인당 재고 사용량 증가를 트랜잭션으로 묶음
127+
- 가능한 경우 RDB에 재고 히스토리 테이블 INSERT
128+
- `트랜잭션 커밋`
129+
- **재고를 차감한 이후에 구매가 실패한 경우**
130+
- 재고사용량을 차감시키라는 이벤트를 `재고사용량 감소 큐`에 발행
131+
- `트랜잭션 시작`
132+
- 레디스에서 전체 재고, 인당 재고 사용량을 구매번호로 제거
133+
- 재고 히스토리 테이블에 재고량 감소
134+
- 메세지 ack 처리
135+
- `트랜잭션 커밋`
136+
137+
<h3>생각할 점</h3>
138+
139+
1. 재고 관리가 빡빡하지 않은 것 같음
140+
- 상품권을 한 개씩 구매함
141+
- 레디스 재고 사용량 변경에 대한 동시성 문제를 메소드 동기화로 해결했음
142+
- 외부 서비스 연동을 동기로 처리함
143+
2. 만약
144+
- 상품을 한 번에 여러 개 구매 가능하고
145+
- 재고 변경에 대한 동시성 문제를 분산 환경에 대응해야 하고
146+
- 외부 서비스 연동을 비동기로 처리해야 할만큼 요청이 많이 몰린다면?
147+
3. **재고를 확인하는 시점과 실제로 차감하는 시점 사이에 시간 차이가 존재하기 때문에, 두 요청이 거의 동시에 들어와서 같은 재고 상태를 확인한 후, 각각 재고를 차감하는 상황**
148+
- 조회와 업데이트가 분리된 두 개의 독립적인 Redis 명령어로 이루어짐
149+
- Synchronized가 JVM 레벨에서만 동작하여 Redis 레벨의 원자성은 보장하지 못함
150+
4. **Redis 업데이트는 성공했지만 RDB 트랜잭션이 실패하여 롤백되는 경우**
151+
- Redis의 MULTI/EXEC와 Spring의 @Transactional은 완전히 독립적인 트랜잭션 시스템이므로, RDB 예외 발생 시 Redis 트랜잭션은 롤백되지 않음
152+
- EXEC를 실행하는 시점에 큐잉되어있던 명령어들 모두 실행
153+
5. **분산 환경에서 Synchronized가 의미가 없음**
154+
9155
# 프롬프트 엔지니어링 논문
10156

11157
[구글, 화제의 프롬프트 엔지니어링 논문 공개](https://www.perplexity.ai/page/google-shares-viral-prompt-eng-TEzNkJMNSm2kdu.xiZ8YWQ?login-source=oneTapPage&login-new=false)에서 소개하는 [프롬프트 엔지니어링 논문](https://www.kaggle.com/whitepaper-prompt-engineering)을 읽어보았다.

0 commit comments

Comments
 (0)