Message Queue

์‚ฌ๋‚ด ๋ฉ”์„ธ์ง€ํ ๊ธฐ์ˆ  ์Šคํƒ ์ „ํ™˜: Kafka → PGMQ(PostgreSQL Message Queue)

ํ”„๋กœ๊ทธ๋ž˜๋จธ ์˜ค์›” 2025. 10. 8.

์‚ฌ๋‚ด์—์„œ ๊ธฐ์กด ๋ฉ”์„ธ์ง€ํ ๊ธฐ์ˆ  ์Šคํƒ์œผ๋กœ Kafka ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. 

Confluent์‚ฌ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Kafka Consumer ์™€ Publisher ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , ๊ฐ ๋„๋ฉ”์ธ์— ๋งž๋Š” ํ† ํ”ฝ์„ ์ƒ์„ฑํ•ด ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋ถ„๋ฆฌํ•ด์™”์Šต๋‹ˆ๋‹ค. ๋ฉ”์‹œ์ง€๋Š” ํ† ํ”ฝ ๋‹จ์œ„ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ฐœํ–‰๋˜๊ณ , ์ปจ์Šˆ๋จธ ๊ทธ๋ฃน์ด ์ด๋ฅผ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. 

 

 

์ด ๋ฐฉ์‹์€ ๋Œ€๋Ÿ‰ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋‚˜ ๋กœ๊ทธ ํŒŒ์ดํ”„๋ผ์ธ์ฒ˜๋Ÿผ “๊ณ„์† ํ˜๋Ÿฌ๊ฐ€๋Š” ๋ฐ์ดํ„ฐ”๋ฅผ ์†Œ๋น„ํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” ๋งค์šฐ ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์‚ฌ๋‚ด ์ดˆ๊ธฐ ์•„ํ‚คํ…์ฒ˜ ๋‹จ๊ณ„์—์„œ Kafka๋Š” ์ „์ž์ƒ๊ฑฐ๋ž˜ ์ฃผ๋ฌธ ์ˆ˜์ง‘ ๋กœ๊ทธ, ERP ์ด๋ฒคํŠธ ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ… ๊ฐ™์€ ๋ฐ์ดํ„ฐ ์ค‘์‹ฌ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ์˜์—ญ์—์„œ ์ถฉ๋ถ„ํžˆ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ด์™”์Šต๋‹ˆ๋‹ค.

 


ํ•˜์ง€๋งŒ, ์‹œ๊ฐ„์ด ํ๋ฅด๋ฉฐ ๋ฉ”์‹œ์ง€ ํ์˜ ์šฉ๋„๊ฐ€ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ์„ ๋„˜์–ด “์—…๋ฌด ์‹คํ–‰(Task Execution)” ์˜์—ญ์œผ๋กœ ํ™•์žฅ๋˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ์™ธ๋ถ€ ์‡ผํ•‘๋ชฐ ์ฃผ๋ฌธ์„ API๋กœ ์ˆ˜์ง‘ํ•˜๊ณ , ์ด๋ฅผ ๋‚ด๋ถ€ ERP ๋„๋ฉ”์ธ์— ๋ฐ˜์˜ํ•˜๋ฉฐ, ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„ํ•˜๊ฑฐ๋‚˜ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๋Š” ๋‹จ์ˆœ ์ด๋ฒคํŠธ ์†Œ๋น„๊ฐ€ ์•„๋‹ˆ๋ผ ์ž‘์—…(work) ์ž์ฒด์ž…๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ๋ฉ”์‹œ์ง€๊ฐ€ “์ด๋ฒคํŠธ”๊ฐ€ ์•„๋‹ˆ๋ผ ์‹คํ–‰ํ•ด์•ผ ํ•  ํ–‰์œ„(Action) ๋ฅผ ์˜๋ฏธํ•˜๊ธฐ ์‹œ์ž‘ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 


์ด ์ง€์ ์—์„œ Kafka๋Š” ๊ตฌ์กฐ์ ์œผ๋กœ ํ•œ๊ณ„๋ฅผ ๋“œ๋Ÿฌ๋ƒ…๋‹ˆ๋‹ค. Kafka๋Š” ์ด๋ฒคํŠธ ๋กœ๊ทธ์ด๋ฉฐ, ๋ฉ”์‹œ์ง€๋Š” append-only ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. Consumer๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์—ˆ๋Š”๊ฐ€(Offset)๋งŒ ํŒ๋‹จํ•  ๋ฟ, ์ฒ˜๋ฆฌ๊ฐ€ ์„ฑ๊ณตํ–ˆ๋Š”์ง€, ์‹คํŒจํ–ˆ๋Š”์ง€, ์žฌ์‹œ๋„ ๊ฐ€๋Šฅํ•œ์ง€, ์ž‘์—…์„ ์ชผ๊ฐœ์•ผ ํ•˜๋Š”์ง€๋ฅผ ์‹œ์Šคํ…œ ์ž์ฒด๋Š” ์•Œ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ ์ด ๋ชจ๋“  ์ฑ…์ž„์€ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์— ๊ท€์†๋˜๊ณ , ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๊ฐ•ํ•˜๊ฒŒ ์—ฎ์ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. Worker๋Š” Kafka API์— ์˜์กดํ•œ ์ƒํƒœ ๋จธ์‹ ์ฒ˜๋Ÿผ ๋ณ€ํ•ด๋ฒ„๋ฆฌ๊ณ , ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ๋‹จํŽธ์  ๋Œ€์‘ ์™ธ์—๋Š” ๋„๋ฉ”์ธ ๊ธฐ๋ฐ˜์˜ ๋ณต๊ตฌ๊ฐ€ ์–ด๋ ค์›Œ์ง‘๋‹ˆ๋‹ค.



ํŠนํžˆ Action Timeout๊ณผ ์žฅ๊ธฐ ์‹คํ–‰ ์ž‘์—…์ด ๊ฒฐํ•ฉ๋˜๋Š” ์ˆœ๊ฐ„ ๋ฌธ์ œ๊ฐ€ ์‹ฌ๊ฐํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์Šคํฌ๋ž˜ํ•‘ API ์‘๋‹ต์ด 45์ดˆ ์ด์ƒ ์ง€์—ฐ๋˜๋ฉด ์„œ๋ฒ„๋Š” ํƒ€์ž„์•„์›ƒ์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , ์‚ฌ์šฉ์ž๋Š” ์‹คํŒจ๋ฅผ ๊ฒฝํ—˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ์‹คํŒจ๋Š” ๊ธฐ์ˆ ์  ๋ฌธ์ œ์ผ ๋ฟ ๋น„์ฆˆ๋‹ˆ์Šค ์‹คํŒจ๋Š” ์•„๋‹ˆ์ง€๋งŒ, Kafka ๊ธฐ๋ฐ˜์—์„œ๋Š” ์ด ์‹คํŒจ๋ฅผ ์ ์ ˆํžˆ “ํšŒ๋ณต ๊ฐ€๋Šฅํ•œ ์ƒํƒœ”๋กœ ์˜ฎ๊ธฐ๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ๋ฉ”์‹œ์ง€๋Š” ์ด๋ฏธ ์†Œ๋น„๋œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ๋˜๊ณ , ์žฌ์‹œ๋„๋Š” ๋ณ„๋„ ํ† ํ”ฝ ๋˜๋Š” DLQ๋ฅผ ๊ฑฐ์ณ์•ผ ํ•˜๋ฉฐ, ์žฌ์‹œ๋„ ๊ณผ์ • ์ „์ฒด๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์—์„œ ์ปค์Šคํ…€ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค. ์„ค๊ณ„ ๋‚œ์ด๋„๋Š” ๊ธฐํ•˜๊ธ‰์ˆ˜์ ์œผ๋กœ ์ฆ๊ฐ€ํ•˜๊ณ , ์ฝ”๋“œ์™€ ์ธํ”„๋ผ๊ฐ€ ๋‹จ๋‹จํžˆ ๊ฒฐํ•ฉ๋ฉ๋‹ˆ๋‹ค.

 

 

์ด๋Ÿฐ ๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ์‚ฌํ•ญ ๋ฌธ์ œ์ ๋ฟ ์•„๋‹ˆ๋ผ ์ธํ”„๋ผ ์šด์˜ ๊ด€์ ์—์„œ๋„ ์กฐ๊ธˆ ํ•œ๊ณ„๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ €ํฌ ์กฐ์ง์€ PostgreSQL์„ ๋น„๋กฏํ•œ RDB ์ค‘์‹ฌ์œผ๋กœ ๊ตฌ์ถ•๋œ ERP SaaS ํšŒ์‚ฌ์ž…๋‹ˆ๋‹ค.
์ฆ‰, DB ์กฐ์ง์ด ๋งค์šฐ ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. DB ์„ฑ๋Šฅ ์ตœ์ ํ™”, ํŒŒํ‹ฐ์…”๋‹, ํŠธ๋žœ์žญ์…˜ ํŠœ๋‹, ์ธ๋ฑ์‹ฑ ์ „๋žต ๋“ฑ์—์„œ ์ˆ˜๋งŽ์€ ๊ฒฝํ—˜๊ณผ ๋‚ด์žฌ๋œ ์ง€์‹์ด ์ถ•์ ๋˜์–ด ์žˆ๊ณ , Postgres๋ฅผ ๋‹ค๋ฃจ๋Š” ์ „๋ฌธ๊ฐ€๋Š” ๋งŽ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ Kafka ์ธํ”„๋ผ๋ฅผ ์•ˆ์ •์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ , ํ† ํ”ฝ ์žฌ๊ตฌ์„ฑ, consumer group migration ๋“ฑ์„ ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ์Šค์ผ€์ผ์—์„œ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋Š” ์—”์ง€๋‹ˆ์–ด๋Š” ์†Œ์ˆ˜์˜€์Šต๋‹ˆ๋‹ค.

 

 

ํšŒ์‚ฌ๋Š” ์ด๋Ÿฌํ•œ ๊ธฐ์ˆ ์ ·๋„๋ฉ”์ธ์ ·์กฐ์ง์  ๋ฌธ์ œ๋ฅผ ์ข…ํ•ฉ์ ์œผ๋กœ ์ธ์‹ํ–ˆ๊ณ , ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์„ Kafka ์ค‘์‹ฌ์˜ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ๋ชจ๋ธ์—์„œ ์—…๋ฌด(Task) ์ค‘์‹ฌ์˜ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ๋ชจ๋ธ๋กœ ์ „ํ™˜ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ ์„ ํƒ๋œ ๊ฒƒ์ด ๋ฐ”๋กœ PGMQ(PostgreSQL Message Queue) ์ž…๋‹ˆ๋‹ค.

 


PGMQ๋Š” ์‚ฌ๋‚ด DB ์ „๋ฌธ๊ฐ€๋“ค์ด ๋ณด์œ ํ•œ ์—ญ๋Ÿ‰์„ ๊ทธ๋Œ€๋กœ ํ™œ์šฉํ•˜๋ฉด์„œ, ๋ฉ”์‹œ์ง€ ์ž์ฒด๋ฅผ PostgreSQL์˜ Row์™€ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋‹ค๋ฃจ๋Š” ๋ฐฉ์‹์„ ์ฑ„ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL Message Queue๋ฅผ ์ €์ˆ˜์ค€์—์„œ ์ง์ ‘ ๊ตฌํ˜„ํ•จ์œผ๋กœ์จ, ํšŒ์‚ฌ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋„๋ฉ”์ธ์— ๋งž๊ฒŒ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ปค์Šคํ…€ํ•˜๊ณ  ํ•„์š”ํ•œ ์˜ต์…˜์„ ์œ ์—ฐํ•˜๊ฒŒ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

 

์ด๋ฒˆ ๋ฉ”์‹œ์ง€ ํ ๊ธฐ์ˆ  ์Šคํƒ ์ „ํ™˜ ํ”„๋กœ์ ํŠธ์—์„œ ์ œ๊ฐ€ ๋‹ด๋‹นํ–ˆ๋˜ ํ•ต์‹ฌ ์ž‘์—…์€ ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€์˜€์Šต๋‹ˆ๋‹ค.
์ฒซ์งธ, FOR UPDATE SKIP LOCKED์™€ pg_notify() ๋“ฑ SQL ์ž ๊ธˆ ์ ˆ(Locking Clause)๊ณผ ๋‚ด์žฅ ํ•จ์ˆ˜๋ฅผ ์‚ฌ๋‚ด ORM ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ–ˆ์Šต๋‹ˆ๋‹ค. 


๋‘˜์งธ, ๊ธฐ์กด Kafka ์ค‘์‹ฌ์˜ ๋ฉ”์‹œ์ง• ์ธํ”„๋ผ๋ฅผ PostgreSQL Message Queue(PGMQ)๋กœ ์ „ํ™˜๋จ์— ๋”ฐ๋ผ, ๋ฉ”์‹œ์ง€๋ฅผ “๋กœ๊ทธ ์ŠคํŠธ๋ฆผ”์ด ์•„๋‹Œ ํŠธ๋žœ์žญ์…˜ ๋ฐ์ดํ„ฐ๋กœ ๋‹ค๋ฃจ๋„๋ก ๋ณ€๊ฒฝํ•˜์˜€์Šต๋‹ˆ๋‹ค.


์…‹์งธ, ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ๊ณผ์ •์—์„œ ์ฒด์ด๋‹(Chaining) ์˜ต์…˜์„ ํ™œ์šฉํ•ด Task chaining ์ž๋™ํ™”ํ–ˆ๊ณ , ์ด๋ฅผ ํ†ตํ•ด ๋ฉ”์‹œ์ง€ ํŽ˜์ด๋กœ๋“œ ํฌ๊ธฐ๋ฅผ ์ค„์ด๋Š” ๋™์‹œ์— ๋Œ€๊ทœ๋ชจ ์ž‘์—…์„ ์ž‘์€ Task ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌํ•ด ์ „์ฒด ์ฒ˜๋ฆฌ ์„ฑ๊ณต๋ฅ ์„ ํฌ๊ฒŒ ๊ฐœ์„ (67%)ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์ด๋Ÿฌํ•œ ์ „ํ™˜ ๊ณผ์ •์—์„œ ํ•„์š”ํ–ˆ๋˜ ํ•ต์‹ฌ SQL ํŒจํ„ด, PGMQ ๊ตฌ์กฐ, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ ์„œ๋น„์Šค ํ™˜๊ฒฝ์—์„œ์˜ ์„ฑ๋Šฅ ํ–ฅ์ƒ ์‚ฌ๋ก€๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ๊ณต์œ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

 

 

 

๐Ÿชˆ์นดํ”„์นด์™€ PGMQ ๋ž€?

 

Apache Kafka

Apache Kafka๋Š” ๋ณธ์งˆ์ ์œผ๋กœ ๋ถ„์‚ฐ ๋กœ๊ทธ(distributed commit log) ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. ๋ฉ”์‹œ์ง€ ๋ฐ์ดํ„ฐ๋Š” append-only ๊ตฌ์กฐ๋กœ ํŒŒํ‹ฐ์…˜์— ์ˆœ์ฐจ์ ์œผ๋กœ ๊ธฐ๋ก๋˜๊ณ , ์ปจ์Šˆ๋จธ๋Š” ์Šค์Šค๋กœ์˜ ์˜คํ”„์…‹์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฝ๊ธฐ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ตฌ์กฐ๋Š” ๋†’์€ ์“ฐ๊ธฐ ์ฒ˜๋ฆฌ๋Ÿ‰์„ ๋ณด์žฅํ•˜๋ฉฐ, ํŒŒํ‹ฐ์…˜์„ ํ†ตํ•œ ์ˆ˜ํ‰์  ํ™•์žฅ์„ฑ์ด ์šฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค.

 

์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ ๋ถ„์„·๋Œ€๊ทœ๋ชจ ๋กœ๊ทธ ์ฒ˜๋ฆฌ·CDC ์ฒ˜๋Ÿผ ๋‹จ์ผ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ๋Ÿ‰๋ณด๋‹ค “์ง€์†์ ์ธ ๋ฐ์ดํ„ฐ ํ๋ฆ„”์˜ ์ฒ˜๋ฆฌ์— ์ดˆ์ ์„ ๋‘” ๋„๋ฉ”์ธ์—์„œ Kafka๋Š” ์••๋„์ ์œผ๋กœ ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋งŽ์€ ๊ธฐ์—…๋“ค์˜ ๋ฐ์ดํ„ฐ ํ”Œ๋žซํผ์—์„œ Kafka๊ฐ€ ๊ธฐ๋ณธ ์ธํ”„๋ผ์ฒ˜๋Ÿผ ์‚ฌ์šฉ๋˜๋Š” ์ด์œ ๋„ ์—ฌ๊ธฐ์— ์žˆ์Šต๋‹ˆ๋‹ค.

 

์นดํ”„์นด์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋…ผ๋ฆฌ์ ์ธ ํ ํ˜•ํƒœ๋กœ ํ‘œํ˜„ํ•˜๋ฉด ์•„๋ž˜ ์ด๋ฏธ์ง€์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

 

ํ•˜์ง€๋งŒ ๋…ผ๋ฆฌ์ ์ธ ํ‘œํ˜„์ผ ๋ฟ ์‹ค์ œ๋กœ๋Š” ์ปจ์Šˆ๋จธ๊ฐ€ ๋ฉ”์„ธ์ง€๋ฅผ ๊ฐ€์ ธ๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฒŒ ์•„๋‹Œ, ์˜คํ”„์…‹์ด ์›€์ง์ด๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” ์ข€๋” ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๊ฐ€๊นŒ์šด ํ‘œํ˜„์ž…๋‹ˆ๋‹ค.

 

Topic Partition (.../CreateScrapingOriginAction)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  MSG-0  โ”‚  MSG-1  โ”‚  MSG-2  โ”‚  MSG-3  โ”‚  MSG-4  โ”‚  MSG-5  โ”‚ ...  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
 ↑
 Append Only (๋์—๋งŒ ์ถ”๊ฐ€)

 

  • ํ์ฒ˜๋Ÿผ ๊ฐ€์žฅ ์•ž์—์„œ ๊บผ๋‚ด๊ฐ€๋Š” ๊ตฌ์กฐ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.
  • ํŒŒ์ผ ๋์— ๊ณ„์† ๊ธฐ๋ก๋˜๋Š” ๋กœ๊ทธ
  • ๋ฉ”์‹œ์ง€๋Š” “์‚ญ์ œ”๊ฐ€ ์•„๋‹ˆ๋ผ retention ์ •์ฑ…์— ์˜ํ•ด ๋งŒ๋ฃŒ

 

Topic Partition (.../CreateScrapingOriginAction)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  MSG-0  โ”‚  MSG-1  โ”‚  MSG-2  โ”‚  MSG-3  โ”‚  MSG-4  โ”‚  MSG-5  โ”‚ ...  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
             โ–ฒ
             โ”‚
        Consumer Offset
  • Consumer๋Š” ๋ฐ์ดํ„ฐ๋ฅผ “๊ฐ€์ ธ๊ฐ€๋Š” ๊ฒƒ”์ด ์•„๋‹ˆ๋ผ “์ฝ๊ณ  offset์„ ์ด๋™”
  • Consumer๋Š” MSG-1๊นŒ์ง€ ์ฝ์—ˆ์Œ → offset=1 ์ด๋ผ๋Š” ์˜๋ฏธ (Consumer๋Š” ํŒŒ์ผ์„ ๋”ฐ๋ผ ์ฝ๋Š” ์ปค์„œ(offset))
  • ๋ฉ”์‹œ์ง€๋Š” ์ฝ์—ˆ๋‹ค๊ณ  ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค. (์‚ญ์ œ X)
  • ๋™์ผํ•œ Consumer Group ๋‚ด๋ถ€์—์„œ๋Š” offset์„ ๊ณต์œ 

 

Topic Partition (.../CreateScrapingOriginAction)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ MSG-0 โ”‚ MSG-1 โ”‚ MSG-2 โ”‚ MSG-3 โ”‚ MSG-4 โ”‚ MSG-5 โ”‚ MSG-6 โ”‚ MSG-7 โ”‚ ...        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ–ฒ                โ–ฒ                    โ–ฒ
         โ”‚                โ”‚                    โ”‚
      Group A         Group B              Group C
    Offset=1          Offset=3            Offset=5
  • Consumer Group ๊ด€์ ์—์„  “๊ฐ ๊ทธ๋ฃน์ด ๋…๋ฆฝ๋œ ์˜คํ”„์…‹(Offset)์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค”
  • ๋ฉ”์‹œ์ง€๋Š” ๋‹จ ํ•œ ๋ฒˆ๋งŒ ์กด์žฌ
  • ๊ฐ Consumer Group์€ ๋…๋ฆฝ์ ์ธ Offset ํฌ์ธํ„ฐ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
  • ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์ด ๋™์ผ ๋ฐ์ดํ„ฐ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ์žฌ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

PGMQ(PostgreSQL Message Queue)

PGMQ๋Š” ๋ง ๊ทธ๋Œ€๋กœ PostgreSQL ์œ„์— ์„ค๊ณ„๋œ ๋ฉ”์‹œ์ง€ ํ์ž…๋‹ˆ๋‹ค.
Kafka๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ log stream์œผ๋กœ ์ทจ๊ธ‰ํ•œ๋‹ค๋ฉด, PGMQ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ row ๋ฐ์ดํ„ฐ๋กœ ์ทจ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.
์ด ์ฐจ์ด๋Š” ๋‹จ์ˆœํ•œ ๊ตฌํ˜„ ๋ฐฉ์‹์˜ ์ฐจ์›์ด ์•„๋‹ˆ๋ผ, ๋„๋ฉ”์ธ ๊ตฌ์กฐ์—์„œ์˜ ๋ฉ”์‹œ์ง€ ์˜๋ฏธ ์ž์ฒด๋ฅผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

 


PGMQ์˜ ํ•ต์‹ฌ์€ ๋ฉ”์‹œ์ง€๋Š” ๋ฐ์ดํ„ฐ์ด๊ณ , ๋ฐ์ดํ„ฐ๋Š” ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๊ด€๋ฆฌ๋ผ์•ผ ํ•œ๋‹ค ์ž…๋‹ˆ๋‹ค.
Kafka์—์„œ๋Š” ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์ƒํƒœ๋ฅผ consumer๊ฐ€ ์ถ”์ ํ•˜์ง€๋งŒ, PGMQ์—์„œ๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ํ…Œ์ด๋ธ” row๋กœ ์กด์žฌํ•˜๋ฉฐ ์ƒํƒœ ์ „์ด๊ฐ€ ์™„์ „ํžˆ DB ๋‚ด๋ถ€์—์„œ ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

  • insert → ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ
  • fetch → ๋Œ€๊ธฐ์ค‘ ๋ฉ”์‹œ์ง€ ์ž ๊ธˆ(lease)
  • ack → ์ฒ˜๋ฆฌ ์™„๋ฃŒ
  • retry / dead → ์‹คํŒจ or lease timeout


DB๊ฐ€ ACID๋ฅผ ๋ณด์žฅํ•˜๋ฏ€๋กœ, ์ €์žฅ๊ณผ ์ฒ˜๋ฆฌ ์ƒํƒœ๊ฐ€ ๋™์ผ ์Šคํ† ๋ฆฌ์ง€ ๋‚ด์— ์ผ๊ด€์ ์œผ๋กœ ๋‚จ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
์ปจ์Šˆ๋จธ๊ฐ€ ์ฃฝ์–ด๋„, ์„œ๋ฒ„๊ฐ€ ๋กค๋ฐฑ๋˜์–ด๋„, worker๊ฐ€ restart๋˜์–ด๋„ ๋ฉ”์‹œ์ง€ ์ƒํƒœ๋Š” ๊ทธ๋Œ€๋กœ ์ž…๋‹ˆ๋‹ค.


PGMQ๋„ ๋…ผ๋ฆฌ์  ์ด๋ฏธ์ง€๋กœ๋Š” ํ ํ˜•์‹์œผ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

ํ•˜์ง€๋งŒ ๋” ์ €์ˆ˜์ค€์œผ๋กœ ํ‘œํ˜„ํ•˜์ž๋ฉด, ์ €๋ ‡๊ฒŒ ํ† ํ”ฝ๋ณ„๋กœ ๋‚˜๋ˆ„์–ด์ ธ ์žˆ๋Š” ํ๊ฐ€ ์•„๋‹Œ, ํ•œ ๊ณณ์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ค ๊ฐ™์ด ์žˆ๋Š” ํ…Œ์ด๋ธ” ํ˜•์‹์ž…๋‹ˆ๋‹ค.

 

 

PGMQ๋Š” ํ† ํ”ฝ(Queue)๋ณ„๋กœ ์ €์žฅ๋œ ๋ฉ”์‹œ์ง€ row๋ฅผ ์ง์ ‘ SELECT ํ•˜์—ฌ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
์ด๋•Œ ๋ฉ”์‹œ์ง€๋Š” ๋‹จ์ˆœ ์กฐํšŒ๊ฐ€ ์•„๋‹ˆ๋ผ ์ฒ˜๋ฆฌ ์ค‘์ธ row๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋ฉฐ ์ž ๊ธˆ(lock)์„ ํš๋“ํ•˜์—ฌ ์ด์ค‘ ์ฒ˜๋ฆฌ ๋˜์ง€ ์•Š๊ฒŒ ํ•ด์•ผํ•˜๋Š” ๋ฐ ์ด๋•Œ ํ•„์š”ํ•œ ์ ˆ์ด FOR UPDATE SKIP LOCKED ์ž…๋‹ˆ๋‹ค.

์ด ์ ˆ์„ ํ†ตํ•ด ๋™์ผ ํ๋ฅผ ์—ฌ๋Ÿฌ Worker๊ฐ€ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•˜๋”๋ผ๋„ ํ•˜๋‚˜์˜ ๋ฉ”์‹œ์ง€๊ฐ€ ๋‘ ๋ฒˆ ์ง‘ํ–‰๋˜์ง€ ์•Š๋„๋ก DB ๋ ˆ๋ฒจ์—์„œ ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

 

 

FOR UPDATE SKIP LOCKED

PostgreSQL์˜ FOR UPDATE SKIP LOCKED๋Š” ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ํ™˜๊ฒฝ์—์„œ ํŠน์ • ๋ ˆ์ฝ”๋“œ(row)๋ฅผ ์ž ๊ทธ๋˜, ์ด๋ฏธ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ์ž ๊ทผ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  “์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ๋ ˆ์ฝ”๋“œ๋งŒ” ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ์‹์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

"์ž ๊ฒจ ์žˆ์œผ๋ฉด ๊ทธ๋ƒฅ ๊ฑด๋„ˆ๋›ฐ๊ณ  ๋‹ค๋ฅธ row ๊ฐ€์ ธ์˜ฌ๊ฒŒ" ๋А๋‚Œ์œผ๋กœ Kafka๋‚˜ Queue์ฒ˜๋Ÿผ ๋ฉ”์‹œ์ง€๋ฅผ ๋นผ์™€ ์ฒ˜๋ฆฌํ•˜๋Š” ๋А๋‚Œ์„ DB row ์ž ๊ธˆ์œผ๋กœ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  • SELECT๋กœ row๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด์„œ ๋™์‹œ์— ์ž ๊ทผ๋‹ค (FOR UPDATE)
  • ๋‹ค์Œ ์กฐํšŒ์‹œ ์ด๋ฏธ ์ž ๊ธด row๋Š” ๊ฑด๋„ˆ๋›ด๋‹ค (SKIP LOCKED)
  • ์ด๋กœ ์ธํ•ด ๋‹ค๋ฅธ worker๊ฐ€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ค‘๋ณต ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • Deadlock ์—†์ด ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋Ÿ‰์„ ๊ทน๋Œ€ํ™”ํ•œ๋‹ค.

 

SQL์„ ์˜ˆ์‹œ๋กœ ๋ณด์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

SELECT *
FROM mq_message
WHERE status = 'wait'
ORDER BY process_available_dtm
FOR UPDATE SKIP LOCKED
LIMIT 1;

 

  • ๊ฐ€์žฅ ๋จผ์ € ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ์ž ๊ทธ๋ฉด์„œ ๊ฐ€์ ธ์˜ด
  • ์ด๋ฏธ ๋‹ค๋ฅธ worker๊ฐ€ ์ฒ˜๋ฆฌ ์ค‘์ธ row๋Š” ์ž๋™์œผ๋กœ ์ œ์™ธ

 

pg_notify()

pg_notify()๋Š” PostgreSQL์ด ์ œ๊ณตํ•˜๋Š” ๋น„๋™๊ธฐ ๋‚ด์žฅ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ํŠน์ • ์ฑ„๋„(channel)์— ๋ฌธ์ž์—ด(payload)์„ ๋ณด๋‚ด๋ฉด,
ํ•ด๋‹น ์ฑ„๋„์„ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ๋Š” ์„ธ์…˜(listener)์—๊ฒŒ ์ด๋ฒคํŠธ๋ฅผ pushํ•ฉ๋‹ˆ๋‹ค.
DB ๋‚ด๋ถ€์—์„œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๊ณ , ์ด๋ฅผ LISTEN ์ค‘์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ํ•ต์‹ฌ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

 

-- ์ด๋ฒคํŠธ ๊ตฌ๋… (LISTEN)
LISTEN mq_event;

-- ์ด๋ฒคํŠธ ๋ฐœํ–‰ (pg_notify)
SELECT pg_notify('message_id', 'meta_data');

 

pg_notify๋Š” ๋ฉ”์‹œ์ง€ ์ €์žฅ ์‹œ์Šคํ…œ์ด ์•„๋‹ˆ๋ผ ์•Œ๋ฆผ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ

  • ๋ฉ”์‹œ์ง€ ๋ณธ๋ฌธ = PGMQ ํ…Œ์ด๋ธ”
  • ์•Œ๋ฆผ signal = pg_notify()

์ด๋ผ๋Š” ๊ตฌ์กฐ๋กœ ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ์ด ์ •์„์ž…๋‹ˆ๋‹ค.

 

์ „์ฒด ํ๋ฆ„์„ ๋ณด์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋ฉ”์„ธ์ง€ Row ์ €์žฅ(INSERT) → pg_notify()๋กœ ์›Œ์ปค ๊นจ์šฐ๊ธฐ → ์›Œ์ปค๊ฐ€ SELECT ... FOR UPDATE SKIP LOCKED

 

 

 

 

 

๐Ÿ”„๏ธ Kafka์—์„œ PGMQ ๋กœ ์ „ํ™˜

 

PGMQ๋Š” ๋‚ด๋ถ€ ๊ตฌํ˜„์ฒด(Producer/Consumer)๋Š” ์™„์ „ํžˆ ๋‹ค๋ฅด์ง€๋งŒ, ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฐœ๋ฐœ ๊ฒฝํ—˜์€ Kafka์™€ ๊ฑฐ์˜ ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”์‹œ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ("์ฑ„๋„๋ช…", "ํ† ํ”ฝ๋ช…")์„ ์ง€์ •ํ•˜์—ฌ ๋ฐœํ–‰ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
Kafka์—์„œ๋„ ๋™์ผํ•˜๊ฒŒ ์„ ์–ธํ•˜๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์—, ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ๋Š” ํ•™์Šต ๋น„์šฉ ์—†์ด ์ „ํ™˜์ด ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‹จ, PGMQ๋Š” ๋‚ด๋ถ€ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์—”์ง„์„ ์ €์ˆ˜์ค€๋ถ€ํ„ฐ ์ง์ ‘ ๊ตฌํ˜„ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, Kafka์—์„œ๋Š” ์ œ๊ณต๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ๊ตฌํ˜„ ๋‚œ๋„๊ฐ€ ๋งค์šฐ ๋†’์€ ๋‹ค์–‘ํ•œ ์‹คํ–‰ ์˜ต์…˜์„ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ ˆ๋ฒจ์—์„œ ์ปค์Šคํ…€ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

 

๋Œ€ํ‘œ์ ์œผ๋กœ Retry ์ปค์Šคํ…€, Debounce Limit, Delay ์ปค์Šคํ…€, Serial Execution, Chaining, Execute Result  ๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

1๏ธโƒฃ Retry - ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„ (Worker ๋ ˆ๋ฒจ Retry)

  • ์—ญํ•  : Action ์‹คํ–‰ ์ค‘ Exception ๋ฐœ์ƒ ์‹œ ์ง€์ • ํšŸ์ˆ˜๋งŒํผ ์ž๋™ ์žฌ๋ฐœํ–‰.

 

2๏ธโƒฃ Debounce Limit - ๊ณผ๋„ํ•œ ๋ฐœํ–‰ ์ œ์–ด

  • ์—ญํ•  : ํŠน์ • workId ๊ธฐ์ค€ ์ผ์ • ๊ธฐ๊ฐ„ ๋‚ด ๋ฐœํ–‰ ํšŸ์ˆ˜๋ฅผ ์ œํ•œ.

 

3๏ธโƒฃ Delay - ์ง€์—ฐ ์‹คํ–‰ / ์˜ˆ์•ฝ ์‹คํ–‰

  • ์—ญํ•  : ์ž‘์—…์„ ํŠน์ • ์‹œ๊ฐ„ ์ดํ›„ ๋˜๋Š” ๋ช…์‹œ์  ์‹œ์ ์— ์‹คํ–‰.
             scheduleAfter: N์ดˆ ์ดํ›„ ์‹คํ–‰
             scheduleAt: ํŠน์ • ๋‚ ์งœ/์‹œ๊ฐ„์— ์‹คํ–‰

 

4๏ธโƒฃ Serial Execution - ๊ทธ๋ฃน ๋‹จ์œ„ ์ˆœ์ฐจ ์‹คํ–‰

  • ์—ญํ•  : ์—ฌ๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํ•˜๋‚˜์˜ ๊ทธ๋ฃน์œผ๋กœ ๋ฌถ์–ด ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰. “์ „์ฒด ๊ฐœ์ˆ˜๋ฅผ ์•Œ๊ณ  ์žˆ๊ณ , ์ˆœ์„œ๋Œ€๋กœ ์ฒ˜๋ฆฌ”

 

5๏ธโƒฃ Chaining - ์‹คํ–‰ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋‹ค์Œ Task ์—ฐ๊ฒฐ

  • ์—ญํ•  : ํŽ˜์ด์ง€๋„ค์ด์…˜ ์Šคํฌ๋ž˜ํ•‘ / API rate-limit ์ƒํ™ฉ ๋Œ€์‘ / ์žฅ๊ธฐ ์›Œํฌํ”Œ๋กœ์šฐ ๋ถ„ํ•ด

 

6๏ธโƒฃ Execute Result - ๋ฉ”์‹œ์ง€ ๊ฐ„ ๊ฒฐ๊ณผ ๊ณต์œ 

  • ์—ญํ•  : ์—ฐ๊ฒฐ๋œ ์ž‘์—… ๊ทธ๋ฃน ๋‚ด๋ถ€์—์„œ ๊ฒฐ๊ณผ ๊ฐ’์„ ์ €์žฅ/์กฐํšŒ.

 

 

 

๐Ÿช„Task Chaining ๊ธฐ๋ฐ˜ Payload ์ตœ์ ํ™”  - “๋ฐ์ดํ„ฐ๋ฅผ ์šด๋ฐ˜ํ•˜์ง€ ๋ง๊ณ , ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐธ์กฐํ•˜๊ธฐ”

 

์žฅ์‹œ๊ฐ„ ์‹คํ–‰๋˜๋Š” ๋Œ€์šฉ๋Ÿ‰ ์ž‘์—…(์˜ˆ: ์นด๋“œ ๋‚ด์—ญ 25~200๊ฑด Scraping, ERP ๋ฐ์ดํ„ฐ ๋ฐฐ์น˜ ๋ณ€ํ™˜ ๋“ฑ)์„ ์•ก์…˜ ํƒ€์ž„์•„์›ƒ ์—†์ด ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์ž‘์—…์„ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„(Chunk)๋กœ ๋‚˜๋ˆ„์–ด ์ฒด์ด๋‹ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋นˆ๋ฒˆํ•ฉ๋‹ˆ๋‹ค.

์ด๋•Œ ๋ฉ”์‹œ์ง€ ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ์—์„œ Task Chaining ์„ ์„ค๊ณ„ํ•  ๋•Œ ๊ฐ€์žฅ ์‰ฝ๊ฒŒ ๋น ์ง€๋Š” ํ•จ์ •์€ ์ด์ „ ๋ฉ”์‹œ์ง€์˜ ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ๋‹ค์Œ ๋ฉ”์‹œ์ง€ payload์— ์‹ฃ๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.


๊ธฐ์กด ์‚ฌ๋‚ด Kafka ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ด๋Ÿฐ ์ ‘๊ทผ ๋ฐฉ๋ฒ•์ด ํ•„์—ฐ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค.
Kafka๋Š” ๋ฉ”์‹œ์ง€๋ฅผ append-only log๋กœ ์ €์žฅํ•  ๋ฟ, ์ค‘๊ฐ„ ์ƒํƒœ๋ฅผ ์ž์ฒด์ ์œผ๋กœ ๋ณด๊ด€ํ•˜๊ฑฐ๋‚˜ ๋‹ค์Œ task๊ฐ€ ์ด์ „ ๋ฉ”์‹œ์ง€์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ ๊ฐœ๋ฐœ์ž๋Š” ์ฒด์ธ์ด ๊นŠ์–ด์งˆ์ˆ˜๋ก payload๋ฅผ ๊ณ„์† ๋ˆ„์ ์‹œํ‚ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  • ์ฒซ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€ ๊ฒฐ๊ณผ → ๋‹ค์Œ ๋ฉ”์‹œ์ง€ payload์— embed
  • ๋‘ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€ ๊ฒฐ๊ณผ + ์ด์ „ ๊ฒฐ๊ณผ → ๋‹ค์Œ ๋ฉ”์‹œ์ง€ payload embed
  • ์„ธ ๋ฒˆ์งธ ๋ฉ”์‹œ์ง€ ๊ฒฐ๊ณผ + ์ด์ „ 2๊ฐœ์˜ ๊ฒฐ๊ณผ → ๋‹ค์Œ ๋ฉ”์‹œ์ง€ payload embed

 

์ด ๋ฐฉ์‹์€ ๊ณฑ์…ˆ์  ์ฆ๊ฐ€๋ฅผ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค.
๊ฐ ๋‹จ๊ณ„์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒฐ๊ณผ๋ฅผ ๊ณ„์† payload์— ๋ˆ„์ ์‹œํ‚ค๋ฉด, ์ฒด์ธ์ด ๊ธธ์–ด์งˆ์ˆ˜๋ก ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ๊ฐ€ ๊ธฐํ•˜๊ธ‰์ˆ˜์ ์œผ๋กœ ์ฆ๊ฐ€ํ•˜๊ฒŒ ๋˜๊ณ , ์ด๋Š” ๋„คํŠธ์›Œํฌ ์ „์†ก ๋น„์šฉ · JSON deserialize ๋น„์šฉ · Worker ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ · GC ๋น„์šฉ ๋“ฑ ์‹œ์Šคํ…œ ์ „์ฒด์— ์•…์˜ํ–ฅ์„ ๋ฏธ์นฉ๋‹ˆ๋‹ค.

 

โ— ์˜ˆ์‹œ) ๊ธฐ์กด ๋ฐฉ์‹์˜ ์ฒด์ด๋‹ ๋ฉ”์„ธ์ง€ ๋ชจ์Šต

  • ์ฒซ๋ฒˆ์งธ ๋ฉ”์„ธ์ง€์˜ payload
{ card_no: 'xxxx-xxxx-xxxx-1234' }
  • ๋‘๋ฒˆ์งธ ๋ฉ”์„ธ์ง€์˜ payload
{
  card_no: 'xxxx-xxxx-xxxx-1234',
  result: [
    { date: '20250101', amount: 20100, store: 'GS25' },
    { date: '20250102', amount: 15000, store: 'CU' },
  ]
}
  • N๋ฒˆ์งธ ๋ฉ”์„ธ์ง€์˜ payload
{
  card_no: 'xxxx-xxxx-xxxx-1234',
  result: [
    { date: '20250101', amount: 20100, store: 'GS25' },
    { date: '20250102', amount: 15000, store: 'CU' },
    ... (N๊ฑด)
  ]
}

 

 

PGMQ ์˜  Chaining ์˜ต์…˜์„ ํ™œ์šฉํ•ด ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ”์‹œ์ง€๋Š” ์‹คํ–‰ ํ๋ฆ„๋งŒ ์ „๋‹ฌํ•˜๊ณ , ๊ฒฐ๊ณผ๋Š” ํ ๋‚ด๋ถ€ ์ƒํƒœ๋กœ ์ €์žฅํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
๊ฒฐ๊ตญ ๋ฉ”์‹œ์ง€๋ฅผ “๋ฐ์ดํ„ฐ ๋ฐฐ์†ก ์ˆ˜๋‹จ”์ด ์•„๋‹ˆ๋ผ “์‹คํ–‰ ํŠธ๋ฆฌ๊ฑฐ(Action)์™€ Context๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์ˆ˜๋‹จ”์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

Task Chaining์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค

  1. ์ปจ์Šˆ๋จธ๊ฐ€ ์ž‘์—…์„ ์ˆ˜ํ–‰
  2. ๊ฒฐ๊ณผ๋ฅผ ๋ฉ”์‹œ์ง€ state์— ์ €์žฅ (PGMQ ๋‚ด๋ถ€ result ์ €์žฅ)
  3. ๋‹ค์Œ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐœํ–‰๋  ๋•Œ payload์—๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋„ฃ์ง€ ์•Š๋Š”๋‹ค
  4. ์ปจ์Šˆ๋จธ์—์„œ ์‹คํ–‰๋˜๋Š” ๋งˆ์ง€๋ง‰ ํ† ํ”ฝ์—์„œ “์ €์žฅ๋œ ๊ฒฐ๊ณผ”๋ฅผ ์กฐํšŒ

 

 

๐Ÿš€์„ฑ๊ณผ

 

๊ตญ๋ฏผ ๊ฐœ์ธ ์นด๋“œ ์ˆ˜์ง‘์„ ์˜ˆ์‹œ๋กœ ๋“ค์—ˆ์„ ๋•Œ, Scraping ๊ฒฐ๊ณผ๊ฐ€ 25๊ฑด๋งŒ ๋˜์–ด๋„ Kafka ๋ฐฉ์‹์—์„œ๋Š” ๋‹ค์Œ ๋‹จ๊ณ„ ๋ฉ”์‹œ์ง€ payload๊ฐ€ 11,000 bytes๊นŒ์ง€ ์ฆ๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
์ด๋Š” ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ ์ „์ฒด(JSON array + ํ•„๋“œ ์ด๋ฆ„ + ์ค‘์ฒฉ ๊ตฌ์กฐ)๋ฅผ ๊ทธ๋Œ€๋กœ payload์— ์‹ฃ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

card scraping์ด 100๊ฑด, 300๊ฑด์ด ๋˜๋ฉด ํฌ๊ธฐ๋Š” ์„ ํ˜•์ด ์•„๋‹ˆ๋ผ ๊ฑฐ์˜ ๋ˆ„์  ํญ๋ฐœ ์ˆ˜์ค€์œผ๋กœ ์ปค์ง‘๋‹ˆ๋‹ค.

 

 

๋ฐ˜๋ฉด PGMQ Task Chaining ๋ฐฉ์‹์„ ์ ์šฉํ•˜๋ฉด, ๋™์ผ ์กฐ๊ฑด(๊ฐ™์€ ์นด๋“œ·๊ฐ™์€ ๊ธฐ๊ฐ„)์˜ ์ž‘์—…์—์„œ๋„ ๋‹ค์Œ ์ฒด์ด๋‹ ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ๋Š” 3,609 bytes์— ๋ถˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค.

 

payload ๋ฐ์ดํ„ฐ ํฌ๊ธฐ๋ฅผ ์•ฝ 67% ์ ˆ๊ฐํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.

 

์ด๋Ÿฐ ์„ฑ๋Šฅ ๊ฐœ์„ ์€ ๋‹จ์ˆœํ•œ ์ตœ์ ํ™”๊ฐ€ ์•„๋‹ˆ๋ผ,

  1. Worker ์ฒ˜๋ฆฌ์†๋„ ๊ฐœ์„ 
  2. JSON parse ๋น„์šฉ ๊ฐ์†Œ
  3. ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ ํฌ๊ธฐ ๊ฐ์†Œ
  4. CPU/GC ๊ฐ์†Œ
  5. ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ ํ–ฅ์ƒ

์œผ๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค. ๋”๋ถˆ์–ด ์ด ๋ชจ๋“  ๊ฒƒ์ด ์ฒด์ธ ๊ธธ์ด๊ฐ€ ๊ธธ์–ด์งˆ์ˆ˜๋ก ๋” ๋น›์„ ๋ฐœํ•ฉ๋‹ˆ๋‹ค.

 

 

 

โœจ๋งˆ๋ฌด๋ฆฌ

Kafka์—์„œ๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ “๋ฐ์ดํ„ฐ๋ฅผ ์šด๋ฐ˜ํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ”์˜€๊ธฐ ๋•Œ๋ฌธ์—, ์ฒด์ด๋‹์ด ๊นŠ์–ด์งˆ์ˆ˜๋ก payload๊ฐ€ ๋ˆ„์ ๋˜๊ณ  ์‹œ์Šคํ…œ ๋ฆฌ์†Œ์Šค ๋ถ€๋‹ด์ด ์ปค์งˆ ์ˆ˜๋ฐ–์— ์—†์—ˆ์Šต๋‹ˆ๋‹ค.
๋ฐ˜๋ฉด PGMQ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ “์ž‘์—… ๋‹จ์œ„(Task)”๋กœ ์žฌ์ •์˜ํ•˜๊ณ , ๊ฒฐ๊ณผ๋Š” ๋‚ด๋ถ€ ์ƒํƒœ๋กœ ์ €์žฅํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ์ทจํ•จ์œผ๋กœ์จ ์ฒด์ด๋‹ ๋‹จ๊ณ„๊ฐ€ ๋Š˜์–ด๋‚˜๋”๋ผ๋„ payload๋Š” ์ตœ์†Œ ์ˆ˜์ค€์œผ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

์ด๋Š” ๋‹จ์ง€ ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ๋ฅผ ์ค„์˜€๋‹ค๋Š” ์ˆ˜์ค€์˜ ๊ฐœ์„ ์ด ์•„๋‹ˆ๋ผ, ๋ฉ”์‹œ์ง• ๋ชจ๋ธ ์ž์ฒด๋ฅผ ‘๋ฐ์ดํ„ฐ ์šด๋ฐ˜’์—์„œ ‘์—…๋ฌด ์ƒํƒœ ์ €์žฅ’์œผ๋กœ ์ „ํ™˜ํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.
์ด ๊ตฌ์กฐ๊ฐ€ ์žฅ๊ธฐ์ ์œผ๋กœ ๋” ๋งŽ์€ ์ฒ˜๋ฆฌ๋Ÿ‰, ๋” ์•ˆ์ •์ ์ธ Worker, ๋” ๋‚ฎ์€ ์šด์˜ ๋น„์šฉ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๊ตญ PGMQ Task Chaining์€ “์‹คํ–‰์˜ ํ๋ฆ„๋งŒ ์ „๋‹ฌํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋Š” ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅํ•œ๋‹ค”๋Š” ์„ค๊ณ„๋ฅผ ์ค‘์‹ฌ์œผ๋กœ, ๋Œ€๊ทœ๋ชจ Scraping·Batch·ERP ์—ฐ๊ณ„ ์—…๋ฌด์—์„œ ์ง€์† ๊ฐ€๋Šฅํ•œ ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š” ํ•ต์‹ฌ ์ „๋žต์ž„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

'Message Queue' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

Kafka ์™€ RabbitMQ ์˜ ์ฐจ์ด์   (0) 2024.11.20

๋Œ“๊ธ€