๐Ÿ“ก ๋ฐฑ์—”๋“œ ํ†ต์‹  ํŒจํ„ด ์ด์ •๋ฆฌ โ€” Short Polling, Long Polling, SSE, Pub/Sub

okorionยท2025๋…„ 8์›” 14์ผ

1. ๋“ค์–ด๊ฐ€๋ฉฐ

๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ โ†” ์„œ๋ฒ„ ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ๋ฐฉ์‹์ด ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ๊ฐ€์žฅ ๋Œ€ํ‘œ์ ์ธ ๋„ค ๊ฐ€์ง€ ํŒจํ„ด:

  1. Short Polling (์งง์€ ํด๋ง)
  2. Long Polling (๋กฑ ํด๋ง)
  3. SSE (Server-Sent Events)
  4. Pub/Sub (๊ฒŒ์‹œ-๊ตฌ๋…)

์„ ๊ตฌ์กฐ๋ถ€ํ„ฐ ์žฅ๋‹จ์ , ์‹ค์ „ ์˜ˆ์ œ๊นŒ์ง€ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


2. Short Polling (์งง์€ ํด๋ง)

๊ฐœ๋…

  • "ํด๋ง"์ด๋ž€ ์ผ์ • ๊ฐ„๊ฒฉ์œผ๋กœ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด ์ž‘์—… ์™„๋ฃŒ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” ๋ฐฉ์‹
  • ์งง์€ ํด๋ง์€ ์ฃผ๊ธฐ๊ฐ€ ๋งค์šฐ ์งง์•„, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋น ๋ฅด๊ฒŒ ์ƒํƒœ๋ฅผ ์žฌํ™•์ธํ•จ
  • ์ฃผ๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์ž‘์—… ์ƒํƒœ ํ™•์ธ์— ์‚ฌ์šฉ

๋™์ž‘ ํ๋ฆ„

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ž‘์—… ์š”์ฒญ โ†’ ์„œ๋ฒ„๋Š” ์ฆ‰์‹œ Job ID ๋ฐ˜ํ™˜
  2. ํด๋ผ์ด์–ธํŠธ๋Š” Job ID๋กœ ์ƒํƒœ ํ™•์ธ ์š”์ฒญ ๋ฐ˜๋ณต
  3. ์ž‘์—… ์™„๋ฃŒ ์‹œ ์„œ๋ฒ„๊ฐ€ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
[Client] POST /submit โ†’ { jobId }
[Client] GET /status?jobId=123 โ†’ { progress: 50% }
[Client] GET /status?jobId=123 โ†’ { progress: 100%, result: ... }

์‹ค์ „ ์˜ˆ์‹œ

  • YouTube ์˜์ƒ ์—…๋กœ๋“œ
    ์—…๋กœ๋“œ ์š”์ฒญ ํ›„, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฃผ๊ธฐ์ ์œผ๋กœ ์—…๋กœ๋“œ ์ง„ํ–‰๋ฅ  ํ™•์ธ
  • ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ, ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋“ฑ ์žฅ๊ธฐ ์ž‘์—… ๋ชจ๋‹ˆํ„ฐ๋ง

์žฅ์ 

  • ๊ตฌํ˜„์ด ๋งค์šฐ ๊ฐ„๋‹จ (๋ฐฑ์—”๋“œยทํ”„๋ก ํŠธ์—”๋“œ ๋ชจ๋‘)
  • ์žฅ๊ธฐ ์ž‘์—… ์ƒํƒœ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์กฐํšŒ ๊ฐ€๋Šฅ
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ์„ ๋Š์—ˆ๋‹ค๊ฐ€ ์žฌ์ ‘์†ํ•ด๋„ ์ด์–ด์„œ ์ƒํƒœ ํ™•์ธ ๊ฐ€๋Šฅ

๋‹จ์ 

  • ๋„คํŠธ์›Œํฌ ๋‚ญ๋น„: ๋Œ€๋ถ€๋ถ„ ์š”์ฒญ์ด "์•„์ง ์ค€๋น„ ์•ˆ ๋จ" ์‘๋‹ต
  • ํด๋ผ์ด์–ธํŠธ ์ˆ˜๊ฐ€ ๋งŽ์œผ๋ฉด ์„œ๋ฒ„ ๋ถ€ํ•˜ ๊ธ‰์ฆ
  • ์‘๋‹ต์ด ์ฆ‰์‹œ ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ์—๋„ ์š”์ฒญ ์ „์†ก โ†’ ๋น„์šฉ ์ฆ๊ฐ€

Node.js ์˜ˆ์ œ (Short Polling)

const express = require('express');
const app = express();
app.use(express.json());

let jobs = {};

app.post('/submit', (req, res) => {
  const jobId = Date.now().toString();
  jobs[jobId] = 0;
  updateJob(jobId);
  res.json({ jobId });
});

app.get('/status', (req, res) => {
  const jobId = req.query.jobId;
  res.json({ progress: jobs[jobId] });
});

function updateJob(jobId) {
  if (jobs[jobId] < 100) {
    setTimeout(() => {
      jobs[jobId] += 10;
      updateJob(jobId);
    }, 5000);
  }
}

app.listen(3000);

3. Long Polling (๋กฑ ํด๋ง)

๊ฐœ๋…

  • ํด๋ง์˜ ๋ณ€ํ˜•
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ๊ฒฐ๊ณผ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ์„œ๋ฒ„๊ฐ€ ์‘๋‹ต์„ ์ง€์—ฐ
  • Kafka, ์ฑ„ํŒ… ์„œ๋ฒ„ ๋“ฑ์—์„œ ์‚ฌ์šฉ

๋™์ž‘ ํ๋ฆ„

  1. ํด๋ผ์ด์–ธํŠธ โ†’ "์ž‘์—… ์ƒํƒœ ์•Œ๋ ค์ค˜" ์š”์ฒญ
  2. ์„œ๋ฒ„ โ†’ ์ž‘์—… ์™„๋ฃŒ ์ „๊นŒ์ง€ ์‘๋‹ต ๋ณด๋ฅ˜
  3. ์™„๋ฃŒ ์‹œ ์ฆ‰์‹œ ์‘๋‹ต ๋ฐ˜ํ™˜
[Client] GET /status?jobId=123 โ†’ (๋Œ€๊ธฐ ์ค‘)
[Server] ์ž‘์—… ์™„๋ฃŒ ์‹œ ์‘๋‹ต ์ „์†ก โ†’ { progress: 100%, result: ... }

์žฅ์ 

  • ๋„คํŠธ์›Œํฌ ๋‚ญ๋น„ ๊ฐ์†Œ (์žก๋‹ด ์ค„์–ด๋“ฆ)
  • ์„œ๋ฒ„ ์นœํ™”์  โ€” ๋ถˆํ•„์š”ํ•œ "์•„์ง ์•ˆ ๋จ" ํŠธ๋ž˜ํ”ฝ ์—†์Œ

๋‹จ์ 

  • ์‹ค์‹œ๊ฐ„์„ฑ์ด 100% ๋ณด์žฅ๋˜์ง„ ์•Š์Œ (์‘๋‹ต ์งํ›„ ์ƒˆ ๋ฐ์ดํ„ฐ ๋ฐœ์ƒ ๊ฐ€๋Šฅ)
  • ์—ฐ๊ฒฐ ์œ ์ง€ ์‹œ๊ฐ„ ๊ธธ์–ด์ง โ†’ ์„œ๋ฒ„ ์†Œ์ผ“ ์ ์œ 

Kafka ์˜ˆ์‹œ

  • ์†Œ๋น„์ž(Consumer)๊ฐ€ ๋กฑ ํด๋ง์œผ๋กœ ํ† ํ”ฝ ๊ตฌ๋…
  • ํ† ํ”ฝ์— ๋ฉ”์‹œ์ง€๊ฐ€ ์—†์œผ๋ฉด ์‘๋‹ต ๋Œ€๊ธฐ โ†’ ์ƒˆ ๋ฉ”์‹œ์ง€ ๋„์ฐฉ ์‹œ ์‘๋‹ต

4. SSE (Server-Sent Events)

๊ฐœ๋…

  • HTTP ๊ธฐ๋ฐ˜ ๋‹จ๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ 1ํšŒ ์š”์ฒญ โ†’ ์„œ๋ฒ„๊ฐ€ ์—ฐ๊ฒฐ ์œ ์ง€ํ•˜๋ฉฐ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ ์ „์†ก
  • Content-Type: text/event-stream

๋™์ž‘ ํ๋ฆ„

[Client] GET /stream
[Server] data: "msg1"\n\n
[Server] data: "msg2"\n\n
  • ๊ฐ ์ด๋ฒคํŠธ๋Š” data:๋กœ ์‹œ์ž‘, ๋‘ ์ค„ ๋ฐ”๊ฟˆ์œผ๋กœ ๊ตฌ๋ถ„
  • ๋ธŒ๋ผ์šฐ์ € EventSource ๊ฐ์ฒด๋กœ ์ž๋™ ํŒŒ์‹ฑ ๊ฐ€๋Šฅ

์žฅ์ 

  • ์‹ค์‹œ๊ฐ„์„ฑ โ†‘ (ํ‘ธ์‹œ ๋ชจ๋ธ๊ณผ ์œ ์‚ฌ)
  • HTTP ๊ธฐ๋ฐ˜์ด๋ผ ๋ฐฉํ™”๋ฒฝยทํ”„๋ก์‹œ ํ†ต๊ณผ ์šฉ์ด
  • WebSocket๋ณด๋‹ค ๊ตฌํ˜„ ๋‹จ์ˆœ

๋‹จ์ 

  • ๋ธŒ๋ผ์šฐ์ € ๋™์‹œ ์—ฐ๊ฒฐ ์ œํ•œ(HTTP/1.1 โ†’ ๋„๋ฉ”์ธ๋‹น 6๊ฐœ)
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์˜คํ”„๋ผ์ธ์ด๋ฉด ์ด๋ฒคํŠธ ์ˆ˜์‹  ๋ถˆ๊ฐ€
  • ํด๋ผ์ด์–ธํŠธ ์ฒ˜๋ฆฌ ๋Šฅ๋ ฅ ์ดˆ๊ณผ ์‹œ ๋ฉ”์‹œ์ง€ ์†์‹ค ๊ฐ€๋Šฅ

Node.js SSE ์˜ˆ์ œ

app.get('/stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  let counter = 0;
  setInterval(() => {
    res.write(`data: ${counter++}\n\n`);
  }, 1000);
});

5. Pub/Sub (๊ฒŒ์‹œ-๊ตฌ๋…)

๊ฐœ๋…

  • ๋ฐœํ–‰์ž(Publisher)๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ํ† ํ”ฝ์— ๊ฒŒ์‹œ
  • ๊ตฌ๋…์ž(Subscriber)๋Š” ๊ด€์‹ฌ ์žˆ๋Š” ํ† ํ”ฝ๋งŒ ์†Œ๋น„
  • ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค(Kafka, RabbitMQ, Redis Pub/Sub) ์‚ฌ์šฉ

๋™์ž‘ ํ๋ฆ„

[Publisher] โ†’ [Broker: topic A] โ†’ [Subscriber1]
                                 โ†’ [Subscriber2]

์žฅ์ 

  • ๋А์Šจํ•œ ๊ฒฐํ•ฉ (์„œ๋น„์Šค ๊ฐ„ ์ง์ ‘ ํ˜ธ์ถœ ๋ถˆํ•„์š”)
  • ์—ฌ๋Ÿฌ ์†Œ๋น„์ž ๋™์‹œ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
  • ๋น„๋™๊ธฐยทํ™•์žฅ์„ฑ ์šฐ์ˆ˜

๋‹จ์ 

  • ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ ๋ณด์žฅ ๋ฌธ์ œ(์ค‘๋ณตยท์œ ์‹ค)
  • ๋ธŒ๋กœ์ปค ์šด์˜ยทํ™•์žฅ ๋ณต์žก
  • ์†Œ๋น„ ์†๋„ ์ œ์–ด ํ•„์š” (ํ‘ธ์‹œ vs ํด๋ง)

RabbitMQ ์˜ˆ์ œ

// publisher.js
channel.assertQueue('jobs');
channel.sendToQueue('jobs', Buffer.from(JSON.stringify({ num: 107 })));

// consumer.js
channel.assertQueue('jobs');
channel.consume('jobs', (msg) => {
  console.log('Received:', msg.content.toString());
});

6. ๋น„๊ต ํ‘œ

ํŒจํ„ด์‹ค์‹œ๊ฐ„์„ฑ๋„คํŠธ์›Œํฌ ํšจ์œจ๊ตฌํ˜„ ๋‚œ์ด๋„๋Œ€ํ‘œ ์‚ฌ๋ก€
Short Polling์ค‘๊ฐ„๋‚ฎ์Œ๋งค์šฐ ์‰ฌ์›€์ง„ํ–‰๋ฅ  ์กฐํšŒ
Long Polling์ค‘๊ฐ„โ†‘๋†’์Œ๋ณดํ†ตKafka ์†Œ๋น„์ž
SSE๋†’์Œ๋†’์Œ์‰ฌ์›€์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ
Pub/Sub๋†’์Œโ†‘๋†’์Œโ†‘๋†’์Œ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์ด๋ฒคํŠธ

7. ๋งˆ์น˜๋ฉฐ

  • ์งง์€ ํด๋ง: ๊ฐ„๋‹จํ•˜์ง€๋งŒ ๋„คํŠธ์›Œํฌ ๋‚ญ๋น„ ์‹ฌํ•จ
  • ๋กฑ ํด๋ง: ํšจ์œจ์ ์ด์ง€๋งŒ ์™„์ „ ์‹ค์‹œ๊ฐ„์€ ์•„๋‹˜
  • SSE: HTTP ๊ธฐ๋ฐ˜ ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ, ๋ธŒ๋ผ์šฐ์ € ์นœํ™”์ 
  • Pub/Sub: ๋Œ€๊ทœ๋ชจ ํ™•์žฅยท๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์— ํ•„์ˆ˜

๐Ÿ“Œ ์ƒํ™ฉยท์š”๊ตฌ์‚ฌํ•ญ์— ๋งž์ถฐ ํ˜ผํ•ฉ ์‚ฌ์šฉ์ด ์ผ๋ฐ˜์ 
์˜ˆ: ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ โ†’ Pub/Sub๋กœ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์—ฐ๊ฒฐ,
ํด๋ผ์ด์–ธํŠธ ์—…๋ฐ์ดํŠธ๋Š” SSE๋กœ ์‹ค์‹œ๊ฐ„ ์ „๋‹ฌ

profile
okorion's Tech Study Blog.

0๊ฐœ์˜ ๋Œ“๊ธ€