ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

๐Ÿ’ก์š”์ฆ˜ ์™œ ๋‹ค๋“ค ์นดํ”„์นด, ์นดํ”„์นด ํ•˜๋Š” ๊ฑธ๊นŒ?

    - ๋Œ€๊ทœ๋ชจ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ์œ„ํ•œ ๋ถ„์‚ฐ ๋ฉ”์„ธ์ง• ์‹œ์Šคํ…œ

    - ๋†’์€ ์ฒ˜๋ฆฌ๋Ÿ‰ ๋ฐ ๊ฐœ๋ฐœ ํšจ์œจ์„ ์œ„ํ•œ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ ๊ณ ๊ฐ€์šฉ์„ฑ๊ณผ ์œ ์—ฐํ•จ์„ ๊ฐ–์ถ˜ ์—ฐ๊ณ„์‹œ์Šคํ…œ์ด ํ•„์š”

 

์นดํ”„์นด Overview

ํ–ฅํ•ด 99

1. Producer & Consumer

  • Producer - ๋ฉ”์„ธ์ง€๋ฅผ ์นดํ”„์นด ๋ธŒ๋กœ์ปค์— ์ ์žฌ(๋ฐœํ–‰)ํ•˜๋Š” ์„œ๋น„์Šค
  • Consumer - ์นดํ”„์นด ๋ธŒ๋กœ์ปค์— ์ ์žฌ๋œ ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์–ด์˜ค๋Š”(์†Œ๋น„) ์„œ๋น„์Šค

         - ๋ฉ”์„ธ์ง€๋ฅผ ์ฝ์„ ๋•Œ๋งˆ๋‹ค ํŒŒํ‹ฐ์…˜ ๋ณ„๋กœ offset์„ ์œ ์ง€ํ•ด ์ฒ˜๋ฆฌํ–ˆ๋˜ ๋ฉ”์„ธ์ง€์˜ ์œ„์น˜๋ฅผ ์ถ”์ 

         - CURRENT-OFFSET

            ์ปจ์Šˆ๋จธ๊ฐ€ ์–ด๋””๊นŒ์ง€ ์ฒ˜๋ฆฌํ–ˆ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” offset์ด๋ฉฐ, ๋™์ผํ•œ ๋ฉ”์„ธ์ง€๋ฅผ ์žฌ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ , ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์€ ๋ฉ”์„ธ์ง€๋ฅผ ๊ฑด๋„ˆ๋›ฐ์ง€ ์•Š๊ธฐ์œ„ํ•ด ๋งˆ์ง€๋ง‰๊นŒ์ง€ ์ฒ˜๋ฆฌํ•œ offset์„ ์ €์žฅ(์ปค๋ฐ‹) ํ•ด์•ผํ•จ

        - ๋งŒ์•ฝ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ, ์ปจ์Šˆ๋จธ ๊ทธ๋ฃน ์ฐจ์›์—์„œ --reset-offsets ์˜ต์…˜์„ ํ†ตํ•ด ํŠน์ •์‹œ์ ์œผ๋กœ offset์„ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์Œ

 

2. Broker

  • ์นดํ”„์นด ์„œ๋ฒ„ Unit
  • Producer์˜ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ›์•„ offset ์ง€์ • ํ›„ ๋””์Šคํฌ์— ์ €์žฅ
  • Consumer์˜ ํŒŒํ‹ฐ์…˜ Read์— ์‘๋‹ตํ•ด ๋””์Šคํฌ์˜ ๋ฉ”์„ธ์ง€ ์ „์†ก
  • Cluster ๋‚ด์—์„œ ๊ฐ 1๊ฐœ์”ฉ ์กด์žฌํ•˜๋Š” Role Broker

        - Controller

           ๋‹ค๋ฅธ ๋ธŒ๋กœ์ปค๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•œ Broker์— ํŠน์ • ํ† ํ”ฝ์˜ Leader ํŒŒํ‹ฐ์…˜์ด ์กด์žฌํ•œ๋‹ค๋ฉด, ๋‹ค๋ฅธ ๋ธŒ๋กœ์ปค์˜ ํŒŒํ‹ฐ์…˜ ์ค‘ Leader๋ฅผ ์žฌ๋ถ„๋ฐฐํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰

        - Coordinator

           ์ปจ์Šˆ๋จธ ๊ทธ๋ฃน์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ํ•ด๋‹น ๊ทธ๋ฃน ๋‚ด์˜ ํŠน์ • ์ปจ์Šˆ๋จธ๊ฐ€ ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•ด ๋งค์นญ๋œ ํŒŒํ‹ฐ์…˜์˜ ๋ฉ”์„ธ์ง€๋ฅผ Consume ํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ, ํ•ด๋‹น ํŒŒํ‹ฐ์…˜์„ ๋‹ค๋ฅธ ์ปจ์Šˆ๋จธ์—๊ฒŒ ๋งค์นญํ•ด์ฃผ๋Š” ์—ญํ•  ์ˆ˜ํ–‰(Rebalance)

 

3.Message

  • ์นดํ”„์นด์—์„œ ์ทจ๊ธ‰ํ•˜๋Š” ๋ฐ์ดํ„ฐ์˜ ๋‹จ์œ„๋กœ <Key, Message> ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑ

4. Topic & Partition

ํ–ฅํ•ด99

  • Topic์€ ๋ฉ”์„ธ์ง€๋ฅผ ๋ถ„๋ฅ˜ํ•˜๋Š” ๊ธฐ์ค€์ด๋ฉฐ N๊ฐœ์˜ Partition์œผ๋กœ ๊ตฌ์„ฑ
  • Partition์— ๋ฐœํ–‰๋œ ์ˆœ์„œ๋Œ€๋กœ ์ปจ์Š˜ํ•จ์œผ๋กœ์จ ์ˆœ์ฐจ์ฒ˜๋ฆฌ๋ฅผ ๋ณด์žฅ

         - ๋Œ€์šฉ๋Ÿ‰ ํŠธ๋ž˜ํ”ฝ์„ ํŒŒํ‹ฐ์…˜์˜ ๊ฐœ์ˆ˜๋งŒํผ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด ๋น ๋ฅธ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

         - ์ „์ฒด ๋ฉ”์„ธ์ง€์˜ ๋ฐœํ–‰ ์ˆœ์ฐจ์ฒ˜๋ฆฌ๋ฅผ ๋ณด์žฅํ•˜์ง€ ์•Š์ง€๋งŒ, ๊ฐ™์€ ํŒŒํ‹ฐ์…˜์˜ ๋ฉ”์„ธ์ง€๋ฅผ ๋Œ€ํ•ด์„œ๋Š” ์ˆœ์ฐจ์ฒ˜๋ฆฌ๋ฅผ ๋ณด์žฅ

         - ๋™์‹œ์„ฑ ์ œ์–ด์˜ ๊ฐœ๋…์„ ์ƒ๊ฐํ–ˆ์„ ๋•Œ, ๋™์‹œ์— ์ฒ˜๋ฆฌ๋˜๋ฉด ์•ˆ๋˜๋Š” ์ž์›์˜ Id ๋“ฑ์„ ๋ฉ”์„ธ์ง€์˜ ํ‚ค๋กœ ์„ค์ •ํ•˜๋ฉด ์ˆœ์ฐจ์ฒ˜๋ฆฌ๊ฐ€ ๋ณด์žฅ๋˜์–ด์•ผ ํ•˜๋Š” ์ผ€์ด์Šค๋Š” ๋ณด์žฅ๋„ ๋˜๋ฉด์„œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ๋†’์€ ์ฒ˜๋ฆฌ๋Ÿ‰์„ ๋ณด์žฅํ•œ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ

  • Producer์—์„œ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐœํ–‰ํ•  ๋•Œ, ์ €์žฅ๋  Partition์„ ๊ฒฐ์ •ํ•˜๊ธฐ ์œ„ํ•ด ๋ฉ”์„ธ์ง€์˜ ํ‚ค ํ•ด์‹œ๋ฅผ ํ™œ์šฉํ•˜๋ฉฐ, ํ‚ค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ๊ท ํ˜• ์ œ์–ด๋ฅผ ์œ„ํ•ด 
key: "842"
hash: "55478" // "842".hashCode()
partitionCnt: "3"

targetPartition: 2 // 55478 % 3

- Partitioner

  ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐœํ–‰ํ•  ๋•Œ, ํ† ํ”ฝ์˜ ์–ด๋–ค ํŒŒํ‹ฐ์…˜์— ์ €์žฅ๋  ์ง€ ๊ฒฐ์ •ํ•˜๋ฉฐ Producer ์ธก์—์„œ ๊ฒฐ์ •. ํŠน์ • ๋ฉ”์„ธ์ง€์— ํ‚ค๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด ํ‚ค์˜ ํ•ด์‹œ ๊ฐ’์— ๋งค์นญ๋˜๋Š” ํŒŒํ‹ฐ์…˜์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•จ์œผ๋กœ์จ ํ‚ค๊ฐ€ ๊ฐ™์€ ๋ฉ”์‹œ์ง€๋ฅผ ๋‹ค๊ฑด ๋ฐœํ–‰ํ•˜๋”๋ผ๋„, ํ•ญ์ƒ ๊ฐ™์€ ํŒŒํ‹ฐ์…˜์— ๋ฉ”์„ธ์ง€๋ฅผ ์ ์žฌํ•ด ์ฒ˜๋ฆฌ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Œ.

 

- ํ•œ Partition์€ ํ•˜๋‚˜์˜ ์ปจ์Šˆ๋จธ์—์„œ๋งŒ ์ปจ์Š˜ํ•  ์ˆ˜ ์žˆ์Œ

    - ํ•˜๋‚˜์˜ ํŒŒํ‹ฐ์…˜์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ปจ์Šˆ๋จธ๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ์ปจ์Š˜ํ•˜๋ฉด ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ์˜ ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•  ์ˆ˜ ์—†๊ฒŒ ๋จ

 

5. Consumer Group

  • ํ•˜๋‚˜์˜ ํ† ํ”ฝ์— ๋ฐœํ–‰๋œ ๋ฉ”์„ธ์ง€๋ฅผ ์—ฌ๋Ÿฌ ์„œ๋น„์Šค๊ฐ€ ์ปจ์Š˜ํ•˜๊ธฐ ์œ„ํ•ด ๊ทธ๋ฃน์„ ์„ค์ •

       - ํ•˜๋‚˜์˜ ์ฃผ๋ฌธ์™„๋ฃŒ ๋ฉ”์„ธ์ง€๋ฅผ ๊ฒฐ์ œ์„œ๋น„์Šค์—์„œ๋„, ์ƒํ’ˆ์„œ๋น„์Šค์—์„œ๋„ ์ปจ์Š˜

  • ๋ณดํ†ต ์†Œ๋น„ ์ฃผ์ฒด์ธ Application ๋‹จ์œ„๋กœ Concumer Group์„ ์ƒ์„ฑ, ๊ด€๋ฆฌํ•จ
  • ๊ฐ™์€ ํ† ํ”ฝ์— ๋Œ€ํ•œ ์†Œ๋น„์ฃผ์ฒด๋ฅผ ๋Š˜๋ฆฌ๊ณ  ์‹ถ๋‹ค๋ฉด, ๋ณ„๋„์˜ ์ปจ์Šˆ๋จธ ๊ทธ๋ฃน์„ ๋งŒ๋“ค์–ด ํ† ํ”ฝ๊ตฌ๋…..

ํ–ฅํ•ด 99

- ํŒŒํ‹ฐ์…˜์˜ ๊ฐœ์ˆ˜๊ฐ€ ๊ทธ๋ฃน ๋‚ด ์ปจ์Šˆ๋จธ ๊ฐœ์ˆ˜๋ณด๋‹ค ๋งŽ๋‹ค๋ฉด ์ž‰์—ฌ ํŒŒํ‹ฐ์…˜์˜ ๊ฒฝ์šฐ ๋ฉ”์„ธ์ง€๊ฐ€ ์†Œ๋น„๋  ์ˆ˜ ์—†์Œ์„ ์˜๋ฏธํ•จ

- (์ฐธ๊ณ ) ํฌํ‹ฑ์˜ Patition ๊ฐœ์ˆ˜์™€ Consumer ๊ฐœ์ˆ˜์— ๋”ฐ๋ฅธ ์†Œ๋น„

ํ–ฅํ•ด99

 

ํ–ฅํ•ด99

 

ํ–ฅํ•ด99

 

6. Rebalancing

  • Consmuer Group์˜ ๊ฐ€์šฉ์„ฑ๊ณผ ํ™•์žฅ์„ฑ์„ ํ™•๋ณดํ•ด์ฃผ๋Š” ๊ฐœ๋…
  • ํŠน์ • ์ปจ์Šˆ๋จธ๋กœ๋ถ€ํ„ฐ ๋‹ค๋ฅธ ์ปจ์Šˆ๋จธ๋กœ ํŒŒํ‹ฐ์…˜์˜ ์†Œ์œ ๊ถŒ์„ ์ด์ „์‹œํ‚ค๋Š” ํ–‰์œ„
  • e.g. Consumer Group ๋‚ด์— Concumer๊ฐ€ ์ถ”๊ฐ€๋  ๊ฒฝ์šฐ, ํŠน์ • ํŒŒํ‹ฐ์…˜์˜ ์†Œ์œ ๊ถŒ์„ ์ด์ „์‹œํ‚ค๊ฑฐ๋‚˜ ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ธด Consumer๋กœ๋ถ€ํ„ฐ ์†Œ์œ ๊ถŒ์„ ํšŒ์ˆ˜ํ•ด ๋‹ค๋ฅธ Consumer์— ๋ฐฐ์ •ํ•จ
  • (์ฃผ์˜) ๋ฆฌ๋ฐธ๋Ÿฐ์‹ฑ ์ค‘์—๋Š” ์ปจ์Šˆ๋จธ๊ฐ€ ๋ฉ”์„ธ์ง€๋ฅผ ์ฝ์„ ์ˆ˜ ์—†์Œ

Rebalancing Case

1. Concumer Group ๋‚ด์— ์ƒˆ๋กœ์šด Consumer ์ถ”๊ฐ€

2. Consumer Group ๋‚ด์˜ ํŠน์ • Consumer ์žฅ์• ๋กœ ์†Œ๋น„ ์ค‘๋‹จ

3. Topic ๋‚ด์— ์ƒˆ๋กœ์šด Partition ์ถ”๊ฐ€

 

7. Cluster

  • ๊ณ ๊ฐ€์šฉ์„ฑ (HA)๋ฅผ ์œ„ํ•ด ์—ฌ๋Ÿฌ ์„œ๋ฒ„๋ฅผ ๋ฌถ์–ด ํŠน์ • ์„œ๋ฒ„์˜ ์žฅ์• ๋ฅผ ๊ทน๋ณตํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ
  • Broker๊ฐ€ ์ฆ๊ฐ€ํ•  ์ˆ˜๋ก ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ , ์ „๋‹ฌ ์ฒ˜๋ฆฌ๋Ÿ‰์„ ๋ถ„์‚ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ™•์žฅ์— ํœด๋ฆฌ
  • (๋™์ž‘์ค‘์ธ ๋‹ค๋ฅธ Broker์— ์˜ํ–ฅ ์—†์ด ํ™•์žฅ์ด ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ, ํŠธ๋ž˜ํ”ฝ ์–‘์˜ ์ฆ๊ฐ€์— ๋”ฐ๋ฅธ ๋ธŒ๋กœ์ปค ์ฆ์„ค์ด ์†์‰ฝ๊ฒŒ ๊ฐ€๋Šฅ)

8. Replication

  • Cluster์˜ ๊ฐ€์šฉ์„ฑ์„ ๋ณด์žฅํ•˜๋Š” ๊ฐœ๋…
  • ๊ฐ Partition์˜ Replica๋ฅผ ๋งŒ๋“ค์–ด ๋ฐฑ์—… ๋ฐ ์žฅ์•  ๊ทน๋ณต
  • Leader Replica - ๊ฐ ํŒŒํ‹ฐ์…˜์€ 1๊ฐœ์˜ ๋ฆฌ๋” Replica๋ฅผ ๊ฐ€์ง„๋‹ค. ๋ชจ๋“  Producer, Consumer ์š”์ฒญ์„ ๋ฆฌ๋”๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌ๋˜๊ฒŒ ํ•˜์—ฌ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•œ๋‹ค.
  • Follwer Replica - ๊ฐ ํŒŒํ‹ฐ์…˜์˜ ๋ฆฌ๋”๋ฅผ ์ œ์™ธํ•œ Replica์ด๋ฉฐ ๋‹จ์ˆœํžˆ ๋ฆฌ๋”์˜ ๋ฉ”์„ธ์ง€๋ฅผ ๋ณต์ œํ•ด ๋ฐฑ์—…ํ•œ๋‹ค. ๋งŒ์ผ, ํŒŒํ‹ฐ์…˜์˜ ๋ฆฌ๋”๊ฐ€ ์ค‘๋‹จ๋˜๋Š” ๊ฒฝ์šฐ ํŒ”๋กœ์›Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์ƒˆ๋กœ์šด ๋ฆฌ๋”๋ฅผ ์„ ์ถœํ•œ๋‹ค.

๋น„๋™๊ธฐ ๋ฉ”์„ธ์ง€ ํ†ต์‹ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ์ฃผ์˜ํ•  ์ 

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰ ์ดํ›„์— ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๋˜์ง€ ์•Š์„ ๊ฒฝ์šฐ, ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ๋ฐ”๋ผ๋ณด๋‹ˆ Consumer ๋˜ํ•œ ๋ณธ์ธ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์ „์ฒด์ ์ธ ๋น„์ฆˆ๋‹ˆ์Šค ํ๋ฆ„์— Hole์ด ์ƒ๊ธฐ๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค.

 

์นดํ”„์นด์™€ ๋น„์Šทํ•œ ์‹œ์Šคํ…œ

๋ฉด์ ‘ ๋‹จ๊ณจ ์งˆ๋ฌธ [Messaging System ๊ฐ„ ์ฐจ์ด์ ]

Redis

  • pub/sub ์„ ํ†ตํ•ด MQ ๊ตฌํ˜„ ๊ฐ€๋Šฅ
  • publisher ๊ฐ€ ์ฑ„๋„์— ๋ฉ”์„ธ์ง€๋ฅผ ๊ฒŒ์‹œ → ๋ชจ๋“  ์ฑ„๋„ subscriber ๊ฐ€ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌ
  • channel : ์ด๋ฒคํŠธ ์ €์žฅ x, subscriber ์—†์œผ๋ฉด ์†Œ์‹ค, subscriber ๋Š” ์—ฌ๋Ÿฌ channel ๊ตฌ๋… ๊ฐ€๋Šฅ

RabbitMQ

  • AMQP ํ”„๋กœํ† ์ฝœ์˜ ๋ฉ”์„ธ์ง€ ํ
  • producer ๋ฉ”์„ธ์ง€ ์ „์†ก→ consumer ๊ฐ€ Queue ์˜ ๋ฉ”์„ธ์ง€ ์ˆ˜์‹ 
  • → exchange ๊ฐ€ ํ‚ค์— ๋งž๊ฒŒ Queue ์— ๋ถ„๋ฐฐ
  • MQ ์„œ๋ฒ„์™€ Queue ๋‚ด์šฉ์ด ์ƒ๋ช…์ฃผ๊ธฐ๊ฐ€ ๊ฐ™์Œ → ์ข…๋ฃŒ ์‹œ ๋ชจ๋‘ ์‚ญ์ œ

Kafka

  • pub-sub ๊ธฐ๋ฐ˜์˜ ๋ฉ”์„ธ์ง€ ๋ฐœํ–‰/๊ตฌ๋… ์‹œ์Šคํ…œ
  • ํŒŒํ‹ฐ์…˜์„ ์—ฌ๋Ÿฌ๊ฐœ ๋‘์–ด์„œ ํ† ํ”ฝ์˜ ๋ฉ”์„ธ์ง€ ์“ฐ๊ธฐ ๋ถ€ํ•˜๋ฅผ ํ•ด์†Œํ•  ์ˆ˜ ์žˆ์Œ. ( ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ )
  • ๋˜ํ•œ ์ด ๋•Œ๋ฌธ์— ๊ฐ ๋ฉ”์„ธ์ง€์˜ ์ฒ˜๋ฆฌ ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•˜์ง€ ๋ชปํ•จ. ( ๊ณ ์ • key ๋ฅผ ๋„ฃ์–ด์„œ ๋™์ผ ํŒŒํ‹ฐ์…˜์— ์ˆœ์„œ๋Œ€๋กœ ๋“ค์–ด๊ฐ€๊ฒŒ๋” ํ•  ์ˆ˜ ์žˆ์Œ )
  • ํŒŒํ‹ฐ์…˜์€ ๋Š˜๋ฆฐ ํ›„์— ์ค„์ผ ์ˆ˜ ์—†์Œ.
  • ์ด์ „ ๋ฉ”์„ธ์ง€๋ฅผ ๋‹ค์‹œ ์ฝ์–ด ์˜ฌ ์ˆ˜ ์žˆ์Œ. ์ด ๊ฒฝ์šฐ, ์ค‘๋ณต ๋ฉ”์„ธ์ง€ ์ฒ˜๋ฆฌ ๋“ฑ์— ๋Œ€ํ•ด์„œ๋„ ๊ณ ๋ฏผํ•ด์•ผํ•จ.

3. ์นดํ”„์นด ๋น„๋™๊ธฐ ๋ฉ”์„ธ์ง€ ํ†ต์‹ ์„ ํ†ตํ•œ ์ฑ…์ž„ ๋ถ„๋ฆฌ

์ง€๋‚œ์ฃผ์— ํ•ด์น˜์› ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋˜ ๋กœ์ง์„ ๋‹ค์‹œํ•œ๋ฒˆ ๋ถˆ๋Ÿฌ์™€๋ณผ๊นŒ์š”?

๊ฐœ์„ ๋กœ์ง ์ตœ์ข…์•ˆ

class OrderPaymentService {
	
	@Transactional
	public void pay() {
		decreaseUserPoint(); // ์œ ์ € ํฌ์ธํŠธ ์ฐจ๊ฐ
		updateOrderStatus(); // ์ฃผ๋ฌธ ์ƒํƒœ ๋ณ€๊ฒฝ
		savePayment(); // ๊ฒฐ์ œ ์ •๋ณด ์ €์žฅ
		eventPublisher.publish(new orderPaidEvent());
	}
}

class OrderPaidEventListener {

	@Async
	@TransactionalEventListener(phase = AFTER_COMMIT)
	public void sendOrderInfo() {
			try {
				dataPlatformMockApiClient.sendOrder();
			} catch(Exception e) {
				log.error("์ฃผ๋ฌธ์ •๋ณด์ „๋‹ฌ์— ์‹คํŒจํ–ˆ์–ด์š”~์ž‰");
			}
	}
}
  • ์„œ๋น„์Šค ์ฝ”๋“œ๋„ ๊น”๋”ํ•ด์ง€๊ณ , ์™ธ๋ถ€ ๋กœ์ง์ด ์–ด๋–ค๊ฒƒ๋“ค์ด ์žˆ๋Š”์ง€ ๊ด€์‹ฌ์‚ฌ๊ฐ€ ๋ถ„๋ฆฌ๋จ
  • ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์ดํ›„์— ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ˆ˜ํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์™ธ๋ถ€ api์— ์˜ํ–ฅ์„ ์ •ํ™•ํ•˜๊ฒŒ ์ œ๊ฑฐํ•จ
  • ์™€ ํ•ด์น˜์› ๋‚˜!!??
  • ๊ทธ๋Ÿฐ๋ฐ… ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ํ”Œ๋žซํผ์ด ์ผ์‹œ์ ์ธ ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•ด์„œ api๊ฐ€ ์‹คํŒจํ–ˆ๋‹ค๋ฉด?
    • ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ํ”Œ๋žซํผ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ ์ „๋‹ฌํ•ด์ค˜์•ผํ•˜๋Š” ์ฑ…์ž„์€ ์šฐ๋ฆฌํ•œํ…Œ ์žˆ๋Š” ์–ต์šธํ•œ ๋Š๋‚Œ..
    • ์žฌ์ „์†ก ๋กœ์ง์€ ๋˜ ์–ด๋””๋‹ค ๋งŒ๋“ค์ง€…

๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ํ”Œ๋žซํผ์˜ ๋ฉ”์„ธ์ง€์˜ ์ˆ˜์‹ ๋ถˆ๊ฐ€ ์ƒํ™ฉ์˜ ์ฑ…์ž„์„ ์šฐ๋ฆฌ๊ฐ€ ๊ฐ–์ง€ ์•Š์œผ๋ ค๋ฉด?? ์นดํ”„์นด๋กœ ํ•ด๊ฒฐ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ ??

  • ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ํ”Œ๋žซํผ์ด ์ผ์‹œ์ ์ธ ์žฅ์• ์ƒํ™ฉ์ผ ๋•Œ์—๋„ ์นดํ”„์นด์— ์ฃผ๋ฌธ์ •๋ณด๋ฅผ ์ •์ƒ์ ์œผ๋กœ ์ ์žฌํ•ด๋‘”๋‹ค๋ฉด, ์žฅ์• ์ƒํ™ฉ ํ•ด์ œ ์ดํ›„ ์ ์žฌ๋˜์—ˆ๋˜ ์ฃผ๋ฌธ์ •๋ณด๋ฅผ “์•Œ์•„์„œ” ์ปจ์Š˜ํ•ด๊ฐ€๋ฉด ๋˜์ง€ ์•Š์„๊นŒ?]

 

๊ฐœ์„ ๋กœ์ง ์ตœ์ข…์•ˆ์˜ ์ตœ์ข…์•ˆ

class OrderPaymentService {
	
	@Transactional
	public void pay() {
		decreaseUserPoint(); // ์œ ์ € ํฌ์ธํŠธ ์ฐจ๊ฐ
		updateOrderStatus(); // ์ฃผ๋ฌธ ์ƒํƒœ ๋ณ€๊ฒฝ
		savePayment(); // ๊ฒฐ์ œ ์ •๋ณด ์ €์žฅ
		eventPublisher.publish(new orderPaidEvent());
	}
}

class OrderPaidEventListener {

	@TransactionalEventListener(phase = AFTER_COMMIT)
	public void sendOrderInfo() {
			kafkaPublisher.publishOrderInfo();
	}
}
  • ์ฃผ๋ฌธ์ •๋ณด๋ฅผ ์นดํ”„์นด๋กœ ์ „๋‹ฌํ•œ๋‹ค๋ฉด ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ํ”Œ๋žซํผ์—์„œ ๋ฉ”์„ธ์ง€๋ฅผ ์ž˜ ๋ฐ›์•˜๋˜ ๋ชป ๋ฐ›๋˜ ๋‚ด ์ฑ…์ž„์€ ๋!!
  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋‚˜์š”?
    • ์›น์„œ๋น„์Šค์˜ API๋Š” ์„œ๋ฒ„์˜ ์ž์ฒด ๋กœ์ง๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ์ ์žฌํ•˜๋Š” ๋น„์šฉ์ด ํ•จ๊ป˜ ํฌํ•จ๋˜์–ด latency๊ฐ€ ์•ˆ์ •์ ์ด์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋‚˜, ์ผ๋ฐ˜์ ์œผ๋กœ ์นดํ”„์นด์— ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐœํ–‰ํ• ๋•Œ๋Š” ๋ณ„๋„ ๋กœ์ง ์—†์ด ๋ฐœํ–‰๋œ ๋ฉ”์„ธ์ง€๋ฅผ ์ €์žฅ๋งŒ ํ•˜๊ธฐ์— ๋ฐœํ–‰์— ๋Œ€ํ•œ ๋น„์šฉ์ด ๋” ์ ๋‹ค๊ณ  ํŒ๋‹จ
    • ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ์—์„œ ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญํ•˜๋Š” ๋น„์šฉ์ด ์˜คํžˆ๋ ค ๋” ํด ์ˆ˜๋„ ์žˆ์Œ
  • ์นดํ”„์นด์— ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰์ด ์‹คํŒจํ–ˆ๋‹ค๋ฉด?
    • … ์ง„์งœ ์–ธ์ œ๊นŒ์ง€ ์‹คํŒจ ๊ณ ๋ฏผ ํ•ด์•ผํ•˜๋‹ˆ…
    • ์ผ๋ฐ˜์ ์ธ ์›น ์„œ๋น„์Šค๋ณด๋‹ค๋Š” ๊ณ ๊ฐ€์šฉ์„ฑ์— ํŠน์žฅ์ ์„ ๊ฐ–๋Š” ์นดํ”„์นด๊ฐ€ ๋” ์•ˆ์ •์ ์ด์ง€ ์•Š์„๊นŒ?
    • ๊ทผ๋ฐ… ๊ทธ๋ž˜๋„… ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋œ ์ดํ›„์— ์นดํ”„์นด ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰์„ ํ•˜๋Š”๋ฐ ์ด๊ฒŒ ์‹คํŒจํ•˜๋ฉด…?

 

Transactional Messaging

  • Eventual Consistency - ๊ฒฐ๊ณผ์  ์ผ๊ด€์„ฑ
    • ์–ธ์  ๊ฐ€ ๋ชจ๋“  ์„œ๋น„์Šค ๊ฐ„์˜ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์€ ๋งž๊ฒŒ ๋œ๋‹ค
    • ๋ถ„์‚ฐ ์‹œ์Šคํ…œ / ๋น„๋™๊ธฐ ๋ฉ”์„ธ์ง• ํ™˜๊ฒฝ์—์„œ ์ถ”๊ตฌํ•˜๋Š” ๋ฐฉํ–ฅ
    • ๋Œ€๋Ÿ‰์˜ ํŠธ๋ž˜ํ”ฝ์„ ์ˆ˜์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž‘์—…์˜ ๋‹จ์œ„ (Transaction) ๋ฅผ ์ž‘๊ฒŒ, ๋น„๋™๊ธฐ๋กœ ๊ตฌ์„ฑ
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ˆ˜ํ–‰ + ํ›„์† ์ด๋ฒคํŠธ ๋ฐœํ–‰์„ ์›์ž์ ์œผ๋กœ ํ•จ๊ป˜ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ์‹
  • Transactional Messaging ์˜ ๋Œ€ํ‘œ์ ์ธ ํŒจํ„ด
  • (1) Transactional Outbox Pattern

ํ–ฅํ•ด 99

  • ๋„๋ฉ”์ธ ๋กœ์ง์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜ํ–‰๋˜์—ˆ๋‹ค๋ฉด, ์ด์— ํ•ด๋‹นํ•˜๋Š” ์ด๋ฒคํŠธ ๋ฉ”์„ธ์ง€๋ฅผ Outbox Table ์ด๋ผ๋Š” ๋ณ„๋„์˜ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•˜์—ฌ ํ•จ๊ป˜ Commit
  • ๋™์ผํ•œ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ด๋ฒคํŠธ ๋ฐœํ–‰์„ ์œ„ํ•œ Outbox ๋ฐ์ดํ„ฐ ์ ์žฌ๊นŒ์ง€ ์ง„ํ–‰ํ•ด ์ด๋ฒคํŠธ ๋ฐœํ–‰์— ๋Œ€ํ•ด ๋ณด์žฅ
  • ์ด๋ฒคํŠธ ๋ฐœํ–‰ ์ƒํƒœ ๋˜ํ•œ Outbox ๋ฐ์ดํ„ฐ์— ์กด์žฌํ•˜๋ฏ€๋กœ, ๋ฐฐ์น˜ ํ”„๋กœ์„ธ์Šค ๋“ฑ์„ ์ด์šฉํ•ด ๋ฏธ๋ฐœํ–‰๋œ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ Fallback ์ฒ˜๋ฆฌ๊ฐ€ ์šฉ์ด

(2) Change Data Capture ( CDC )

ํ–ฅํ•ด 99

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ( e.g. Debezium ) ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์œผ๋กœ ํ•ด๋‹น ๋ณ€๊ฒฝ์‚ฌํ•ญ์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ์ „ํŒŒํ•˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด ํ”„๋กœ์„ธ์Šค
  • DB ์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ๊ฐ์ง€ํ•˜๊ณ , ํ•ด๋‹น ๋ณ€๊ฒฝ ๊ฑด์— ๊ด€ํ•œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰

Transactional Outbox Pattern

E-commerce ์˜ ์ฃผ๋ฌธ๊ณผ ์žฌ๊ณ  ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด ๊ณ ๋ คํ•ด๋ณด์ž.

์ƒ๋Œ€์ ์œผ๋กœ Lock ์„ ์ด์šฉํ•œ ์ •ํ•ฉ์„ฑ ์ œ์–ด ๋“ฑ ๋Œ€๋Ÿ‰์˜ ํŠธ๋ž˜ํ”ฝ์ด ๋ชฐ๋ ค๋“ค์—ˆ์„ ๋•Œ์— ์ œ์–ด๊ฐ€ ํž˜๋“  ์žฌ๊ณ  ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ฃผ๋ฌธ ๋กœ์ง๊ณผ ๋ถ„๋ฆฌํ•ด๋‚ผ ๋ฐฉ๋ฒ•์„ ๊ณ ๋ คํ•ด๋ณด๊ณ , ๋ณธ์ธ์˜ ๊ด€์‹ฌ์‚ฌ๋ฅผ ์˜จ์ „ํžˆ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์‹œ์Šคํ…œ์„ ์„ค๊ณ„ํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค๊ณ„ํ•ด ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

ํ–ฅํ•ด 99

[์ฃผ๋ฌธ ์„œ๋น„์Šค]
// ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ
Tx 1 {
	์ฃผ๋ฌธ ์ƒ์„ฑ ( Order.Status = INIT(์ฒ˜๋ฆฌ์ค‘) )
	์•„์›ƒ๋ฐ•์Šค ์ƒ์„ฑ ( Outbox.Status = INIT )
	์ด๋ฒคํŠธ ๋ฐœํ–‰ ( Application Event Publish )
}
// ์ฃผ๋ฌธ ์ด๋ฒคํŠธ ๋ฐœํ–‰
Application Event Listener {
	์•„์›ƒ๋ฐ•์Šค ๋ฐ์ดํ„ฐ Kafka ์— ์ „์†ก
}

[Consumer 1 : ์•„์›ƒ๋ฐ•์Šค ์„œ๋น„์Šค]
// ์•„์›ƒ๋ฐ•์Šค ์ฒ˜๋ฆฌ
// ์„ฑ๊ณต์ ์œผ๋กœ Kafka ์— ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๋˜์—ˆ์Œ์„ ๋ณด์žฅ
Tx 1-1 {
	์•„์›ƒ๋ฐ•์Šค Update ( Outbox.Status = PUBLISHED )
}

[์žฌ๊ณ  ์„œ๋น„์Šค]
// ์žฌ๊ณ  ์ฒ˜๋ฆฌ
// Kafka ์˜ ์ด๋ฒคํŠธ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜์ทจํ•ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ˆ˜ํ–‰ํ–ˆ์Œ์„ ๋ณด์žฅ
Tx 2 {
	์ฒ˜๋ฆฌ์ •๋ณด ์ƒ์„ฑ ( OrderId ์— ๋Œ€ํ•œ Processed ์ •๋ณด ์ƒ์„ฑ Processed.Status = RECEIVED )
	์ƒํ’ˆ ์žฌ๊ณ  Update
	์™„๋ฃŒ ์ด๋ฒคํŠธ ๋ฐœํ–‰
}

Tx 2-1 {
	์ฒ˜๋ฆฌ์ •๋ณด Update ( Processed.Status = SUCCESS )
}

์ด๋ฒคํŠธ ๋ฐœํ–‰ ๋ฐ ์ฒ˜๋ฆฌ๋ฅผ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•œ ์„ค๊ณ„

  • Publisher
    • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ˆ˜ํ–‰
    • Outbox ์ €์žฅ
    ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ˆ˜ํ–‰๊ณผ Outbox ์ €์žฅ์„ ๋™์ผํ•œ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ˆ˜ํ–‰ํ•จ์œผ๋กœ์จ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์„ฑ๊ณต = ์ด๋ฒคํŠธ ๋ฐœํ–‰ ์„ ํ•ญ์ƒ ๋ณด์žฅํ•จ
  • Consumer 1
    • Outbox ๋ณ€๊ฒฝ
    ์นดํ”„์นด์— ์„ฑ๊ณต์ ์œผ๋กœ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๋˜์—ˆ๋‹ค๋ฉด Consumer 1 ์€ ์ด๋ฅผ ์ฝ์–ด Outbox ์˜ ์ƒํƒœ๋ฅผ ๊ฐฑ์‹ ํ•ด์คŒ์œผ๋กœ์จ ์ด๋ฒคํŠธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐœํ–‰๋˜์—ˆ์Œ์„ ๋ณด์žฅ
  • Consumer 2
    • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ˆ˜ํ–‰
    • Processed ์ €์žฅ
    ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฃผ์ฒด๊ฐ€ ์ด๋ฒคํŠธ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๊ตฌ๋…ํ•˜์˜€๊ณ  ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜์˜€์Œ์„ ๋ณด์žฅ

๊ฐœ์„ ๋กœ์ง ์ตœ์ข…์•ˆ์˜ ์ตœ์ข…์•ˆ์˜ ์ตœ์ข…์•ˆ

class OrderPaymentService {
	
	@Transactional
	public void pay() {
		decreaseUserPoint(); // ์œ ์ € ํฌ์ธํŠธ ์ฐจ๊ฐ
		updateOrderStatus(); // ์ฃผ๋ฌธ ์ƒํƒœ ๋ณ€๊ฒฝ
		savePayment(); // ๊ฒฐ์ œ ์ •๋ณด ์ €์žฅ
		orderCreatedEventRepository.save(orderCreatedEvent) // outbox ์ €์žฅ
		eventPublisher.publish(new orderPaidEvent());
	}
}

class OrderPaidEventListener {

	@TransactionalEventListener(phase = AFTER_COMMIT)
	public void sendOrderInfo() {
			kafkaPublisher.publishOrderInfo();
	}
}


class KafkaConsumer(orderCreatedMessage) {
	orderCreatedEventRepository.findById(message.id).published() // outbox ์ˆ˜์ •
}


// ๋ฐฐ์น˜ํ”„๋กœ์„ธ์Šค ๋“ฑ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์ข‹๊ฒ ์ฃ .
@Scheduler(fixedRate = 5000) 
orderCreatedEventRepository.findAllByStatus(INIT).forEach {
	if(it.createdAt < now() - 5minutes) {
		slackApiClient.sendMessageToDeveloper();
		kafkaPublisher.publishOrderInfo();
	}
}