๐Ÿ’พ 05. WebSocket, SSE ๋“ฑ ์‹ค์‹œ๊ฐ„ ํ†ต์‹ ๋ง๊ณผ ์บ์‹œ ๋™๊ธฐํ™”

2026๋…„ 4์›” 30์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

์„œ๋ฒ„๊ฐ€ ํ‘ธ์‹œํ•˜๋Š” ์–‘๋ฐฉํ–ฅ ์‹ค์‹œ๊ฐ„ ์ด๋ฒคํŠธ(Socket/SSE)๋ฅผ React Query์˜ QueryCache์— ๊ฐ€์žฅ ๊ฒฝ๋Ÿ‰ํ™”๋˜๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ์ฃผ์ž…ํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ๊ฐˆ๋ฆผ๊ธธ์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ

"์˜์ฒ  ๋‹˜, ์›น์†Œ์ผ“์œผ๋กœ '1๋ฒˆ ๊ฒŒ์‹œ๊ธ€ ์ง€์›Œ์ง' ์‹ ํ˜ธ ๋ฐ›์•˜๋‹ค๊ณ  ๊ตณ์ด ๋‹ค์‹œ API fetch ๋ฅผ ๋‚ ๋ฆฌ๋ฉด ๊ทธ ๋น ๋ฅธ ์›น์†Œ์ผ“ ์„œ๋ฒ„๋ฅผ ํŠผ ์ด์œ ๊ฐ€ ์—†์ž–์•„์š”!"

โ˜•๏ธ ์˜์ฒ ์ด์˜ ๊ณ ๋ฏผ: "ํ‘ธ์‹œ(Push)๊ฐ€ ์™”๋Š”๋ฐ ๋˜ ๋‹น๊ธฐ(Pull)๋‹ˆ๊นŒ ๋А๋ ค์š”"

(ํ™”์š”์ผ ์˜คํ›„, ์‹ค์‹œ๊ฐ„ ํŠธ๋ ˆ์ด๋”ฉ ๋Œ€์‹œ๋ณด๋“œ ํ™”๋ฉด ๊ฐ€๊ฒฉ์ด ํŠ€๋Š” ๊ฑธ ๋ณด๋ฉฐ ๋‹นํ™ฉํ•˜๋Š” ์˜์ฒ )

๐Ÿฃ ์˜์ฒ : ์ €๊ธฐ, ๋ฆฌ๋“œ ๋‹˜! ํŒ€์žฅ๋‹˜์ด "์ฆ๊ถŒ ์•ฑ์ฒ˜๋Ÿผ ๋น„ํŠธ์ฝ”์ธ ์‹œ์„ธ๋‚˜ ์‹ค์‹œ๊ฐ„ ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€์€ ์ƒˆ๋กœ๊ณ ์นจ ์•ˆ ํ•ด๋„ ํŒํŒ ๋ฐ”๋€Œ๊ฒŒ ํ•ด์ฃผ์„ธ์š”" ๋ผ๊ณ  ํ•˜์…”์„œ ์–ด์ œ ๋ฐค์ƒˆ WebSocket(์›น์†Œ์ผ“) ์—ฐ๊ฒฐ์„ ๋งˆ์ณค๊ฑฐ๋“ ์š”!

์†Œ์ผ“ ์ฑ„๋„์—์„œ {"event":"updated", "id":100} ์ด๋ ‡๊ฒŒ ๋ฉ”์‹œ์ง€๊ฐ€ ๋˜‘ ๋–จ์–ด์ง€๋ฉด,
์ œ๊ฐ€ ๋ƒ‰ํผ queryClient.invalidateQueries({ queryKey: ['posts'] }) ์œผ๋กœ ์บ์‹œ ํŒ ๋ฌดํšจํ™” ์ณ์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌํŒจ์นญ(GET /posts)์„ ๋บ‘๋บ‘ ๋Œ๊ฒŒ ์งฐ์Šต๋‹ˆ๋‹ค! ์–ด๋•Œ์š”? ์‹ค์‹œ๊ฐ„ ์ฉ”์ฃ ?

๐Ÿฆ ์˜ํ˜ธ: (์ด๋งˆ๋ฅผ ์งš์œผ๋ฉฐ) ์˜์ฒ  ๋‹˜... ์ง€๊ธˆ 1์ดˆ์— ๋Œ“๊ธ€์ด 50๊ฐœ์”ฉ ๋‹ฌ๋ฆฌ๋Š” ํ•ซํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ธ€์— ๋งค์ดˆ๋งˆ๋‹ค ์›น์†Œ์ผ“ ์‹ ํ˜ธ(updated)๊ฐ€ ๋‚ด๋ ค์˜ฌ ํ…๋ฐ, ๊ทธ๋•Œ๋งˆ๋‹ค ๋ฐฑ๊ทธ๋ผ์šด๋“œ fetch /posts ๋ฅผ 50๋ฒˆ์”ฉ ๋‚ ๋ฆฌ์‹œ๊ฒ ๋‹ค๊ณ ์š”...? ์šฐ๋ฆฌ ์˜์ˆ˜(๋ฐฑ์—”๋“œ)๋‹˜์ด ์˜๋„์น˜ ์•Š์€ ์š”์ฒญ ๋ถ€ํ•˜๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค๊ณ  ๊ธ‰ํ•˜๊ฒŒ ์—ฐ๋ฝํ•˜๋Ÿฌ ์˜ต๋‹ˆ๋‹ค.
WebSocket์˜ ๋ชฉ์ ์€ "์„œ๋ฒ„๊ฐ€ ์ด๋ฏธ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ์ž…์— ๋ฌผ๊ณ  ํ‘ธ์‹œ(Push)ํ•ด ์คฌ์œผ๋‹ˆ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ API๋ฅผ ์š”์ฒญํ•˜์ง€ ๋ง๊ณ (Pull) ๊ทธ ๋ฐ์ดํ„ฐ๋กœ ๋„ค ์บ์‹œ ํ†ต๋งŒ ์‚ด์ง ์—…๋ฐ์ดํŠธ์ณ๋ผ" ์— ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌดํšจํ™”(invalidate)๋ฅผ ๋‚จ๋ฐœํ•˜๋ฉด ๊ฐ€์žฅ ์œ„ํ—˜ํ•œ ๋ณ‘๋ชฉ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.


๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: ์บ์‹œ ์„œ๋ฒ„ ๋™๊ธฐํ™”์˜ ๋”œ๋ ˆ๋งˆ

ํ˜„๋Œ€ ์›น ํŠธ๋ Œ๋“œ(ํŠนํžˆ ์ฑ„ํŒ…, ์•Œ๋ฆผ, ์ฃผ์‹, ํ˜‘์—… ํˆด) ์—์„œ๋Š” REST API (๋‚ด๊ฐ€ ๋‹น๊น€, Pull) ์™€ WebSocket/SSE (์„œ๋ฒ„๊ฐ€ ์ด์คŒ, Push) ๋‘ ๊ฐ€์ง€ ํŒŒ์ดํ”„๋ผ์ธ์ด ํ•˜๋‚˜์˜ ํ™”๋ฉด(UI) ์•ˆ์—์„œ ๊ฒฉ๋ ฌํ•˜๊ฒŒ ์ถฉ๋Œํ•ฉ๋‹ˆ๋‹ค.

์„œ๋ฒ„๊ฐ€ "์•ผ! 1๋ฒˆ ๊ธ€ ๋ฐ”๊ผˆ๋‹ค!" ๋ผ๊ณ  Push ์‹ ํ˜ธ๋ฅผ ์คฌ์„ ๋•Œ React Query๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ํ–‰๋™์€ ๋‘ ๊ฐ€์ง€ ๊ฐˆ๋ฆผ๊ธธ ์ž…๋‹ˆ๋‹ค.

  1. ์†Œ๊ทน์  ์ˆ˜๋น„ (Invalidation): "์˜ค ์‹ ํ˜ธ ๋•กํ. ๊ทผ๋ฐ ๋„ˆ๊ฐ€ ์ค€ ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ๋„ˆ๋ฌด ์ชผ๋งŒํ•ด์„œ ๋ชป ๋ฏฟ๊ฒ ์–ด. ๊ท€์ฐฎ์ง€๋งŒ ๋‚ด๊ฐ€ ์„œ๋ฒ„์— REST API ๋‹ค์‹œ ์š”์ฒญํ•ด์„œ ์ตœ์‹  ๋ฆฌ์ŠคํŠธ ํ†ต์งธ๋กœ ๋‹ค ๋Œ์–ด์˜ฌ๊ฒŒ."
  2. ๊ณต๊ฒฉ์  ์ˆ˜์šฉ (setQueryData): "์˜ค ์‹ ํ˜ธ ๋•กํ! ๋„ˆ๊ฐ€ ๋ณ€๊ฒฝ๋œ ๋ฐ์ดํ„ฐ({likes: 5}) ๊ตฌ์›Œ์คฌ์œผ๋‹ˆ๊นŒ, ๋‚ด๊ฐ€ ๋„คํŠธ์›Œํฌ ์•ˆ ์š”์ฒญํ•˜๊ณ  ๋‚ด ๋กœ์ปฌ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œํ†ต ์กฐ๋ฆฝํ•ด์„œ ์‹น ๊ฐ–๋‹ค ๋ถ™์ผ๊ฒŒ!"

์–ด๋–ค ์ƒํ™ฉ์— ๋ฌด์—‡์„ ํƒํ•˜๋Š”์ง€๊ฐ€ ์‹ค์‹œ๊ฐ„ ์•„ํ‚คํ…์ฒ˜์˜ ํผํฌ๋จผ์Šค๋ฅผ ๊ฐ€๋ฆ…๋‹ˆ๋‹ค. (TkDodo ์•„ํ‹ฐํด #7)


1. 1๋ฒˆ ์ฑ„๋„: ๋ถ€๋ถ„ ์ด๋ฒคํŠธ (์†Œ๊ทน์  ์ˆ˜๋น„ - Invalidate)

๋งŒ์•ฝ ์›น์†Œ์ผ“ ์ด๋ฒคํŠธ ๋ฉ์–ด๋ฆฌ๊ฐ€ ๋งค์šฐ ๋นˆ์•ฝํ•˜๊ฒŒ ๋„˜์–ด์˜ค๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค.
๊ฐ€๋ น Payload๊ฐ€ ๋‹จ์ง€ {"type": "POST_UPDATED", "postId": 5} ์ •๋„๋งŒ ์ฐํ˜€์„œ ์™”์Šต๋‹ˆ๋‹ค. "๋ฌด์—‡"์ด ๋ฐ”๋€Œ์—ˆ๋Š”์ง€๋Š” ์•ˆ ์•Œ๋ ค์ฃผ๊ณ  "๋ฐ”๋€Œ์—ˆ์Œ!" ์ด๋ผ๋Š” ํŒŒ๋‹ฅ๊ฑฐ๋ฆผ๋งŒ ์˜ค๋Š” ๊ฑฐ์ฃ .

์ด๋Ÿด ๋• ์–ด์ฉ” ์ˆ˜ ์—†์ด ๋ฌดํšจํ™”๋ฅผ ์ณ์•ผ ํ•˜์ง€๋งŒ, ๋””๋„์Šค(DDoS) ๋ฐฉ์–ด ๋งค์ปค๋‹ˆ์ฆ˜ ์„ ์„ธํŒ…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
 
function usePostSocketHandling() {
  const queryClient = useQueryClient();
 
  useEffect(() => {
    // ์†Œ์ผ“์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ํŒŒ๋ฐ”๋ฐ•- ์ˆ˜์‹ ๋  ๋•Œ
    socket.on('POST_UPDATED', (eventMessage) => {
      // โš ๏ธ ์˜์ฒ ์ด์˜ ์ˆœ์ง„ํ•œ ํญ๊ฒฉ
      // 1์ดˆ์— 10๋ฒˆ ์†Œ์ผ“์ด ์˜ค๋ฉด fetch GET ์š”์ฒญ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ 10๋ฒˆ ๋ฐœ์ƒํ•œ๋‹ค โš ๏ธ
      // queryClient.invalidateQueries({ queryKey: ['posts'] });
 
      // ๐Ÿš€ ์˜ํ˜ธ ๋ฆฌ๋“œ์˜ ๋””๋„์Šค ๋ฐฉ์–ด๋ง‰ (Throttling Invalidation)
      queryClient.invalidateQueries({
        queryKey: ['posts'],
        // "๋„คํŠธ์›Œํฌ ํ†ต์‹ ์ด ์—ฐ์‡„์ ์œผ๋กœ ๋“ค์–ด์™€๋„ ๋‹ค ๋ฌด์‹œํ•˜๊ณ , ๋”ฑ 1๋ฒˆ๋งŒ ์ทจํ•ฉํ•ด์„œ ๋‹ค์‹œ ๊ฐ€์ ธ์™€๋ผ!"
        // 1์ดˆ ๊ฐ„๊ฒฉ ์•ˆ์— ๋ฐœ์ƒํ•œ ๋ฌดํšจํ™”๋Š” ๋ณ‘ํ•ฉ(Batch) ์ฒ˜๋ฆฌ๋จ.
        cancelRefetch: false
      });
    });
 
    return () => socket.off('POST_UPDATED');
  }, [queryClient]);
}

2. 2๋ฒˆ ์ฑ„๋„: ์™„์ „ํ•œ ์ด๋ฒคํŠธ (๊ณต๊ฒฉ์  ์ˆ˜์šฉ - ๋‚™๊ด€์  ์บ์‹œ ์ฃผ์ž…)

์„œ๋ฒ„๊ฐ€ ๋„ˆ๋ฌด๋‚˜ ์นœ์ ˆํ•˜๊ฒŒ ๋ฐ”๋€ ๋ฐ์ดํ„ฐ ๊ป๋ฐ๊ธฐ๋ฅผ ํ†ต์งธ๋กœ ๋‹ค ๋ณด๋‚ด์ฃผ๋Š” ๊ฒฝ์šฐ(์˜ˆ: { "id": 5, "title": "์ˆ˜์ •๊ธ€", "likes": 12 })์—” ์ ˆ๋Œ€๋กœ fetch๋ฅผ ๋‹ค์‹œ ์š”์ฒญํ•  ์ด์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค!

๋ฉ”๋ชจ๋ฆฌ ์•ˆ์˜ QueryCache ์ฃผ๋จธ๋‹ˆ๋ฅผ ์ง์ ‘ ์—ด๊ณ , ํ•€์…‹์œผ๋กœ ํ•ด๋‹น ๊ฐ์ฒด๋งŒ ์ฐฉ! ๊ฐˆ์•„๋ผ์šฐ๋Š” setQueryData (ํ”„๋ก ํŠธ ์กฐ๋ฆฝ ๋ฐฉ์‹) ์ด ์„œ๋ฒ„ ๋น„์šฉ์„ 0์›์œผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

// ์†Œ์ผ“์ด ์˜จ์ „ํ•œ ์ตœ์‹  ๊ฒŒ์‹œ๊ธ€(Post) ๋ฐ์ดํ„ฐ๋ฅผ ํ‘ธ์‹œํ•˜๋Š” ๊ฒฝ์šฐ
type Post = { id: number; title: string; likes: number };
 
function useLivePostUpdates() {
  const queryClient = useQueryClient();
 
  useEffect(() => {
    socket.on('INCOMING_POST_UPDATE', (newPost: Post) => {
 
      // ๐Ÿš€ ๋ฐ์ดํ„ฐ ์กฐ๋ฆฝ์‹ ์ฃผ์ž… (๋„คํŠธ์›Œํฌ ์š”์ฒญ ๋ฐœ์ƒ๋ฅ  0%)
      queryClient.setQueryData<Post[]>(['posts'], (oldPosts) => {
        // ๋งŒ์•ฝ ๋‚ด ํ™”๋ฉด ์บ์‹œ ๊ธˆ๊ณ ๊ฐ€ ํ…… ๋น„์—ˆ๋‹ค๋ฉด? ๋ฌด์‹œํ•ด๋ฒ„๋ฆผ (ํ™”๋ฉด์— ์•ˆ ๋„์›Œ๋†จ์œผ๋‹ˆ ์•Œ ๋ฐ” ์•„๋‹˜)
        if (!oldPosts) return;
 
        // ์บ์‹œ ์†์— ์žˆ๋Š” 100๊ฐœ ๋ฆฌ์ŠคํŠธ ์ค‘, ๋ฐฉ๊ธˆ ์†Œ์ผ“์œผ๋กœ ๋‚ ์•„์˜จ ๋Œ€์ƒ๋งŒ ๊ฐˆ์•„๋ผ์šด๋‹ค!
        return oldPosts.map((post) =>
          post.id === newPost.id ? newPost : post
        );
      });
 
    });
 
    return () => socket.off('INCOMING_POST_UPDATE');
  }, [queryClient]);
}

์ด ํŒจํ„ด์ด ๋“ค์–ด๊ฐ€๋ฉด, ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๊ฒŒ์‹œํŒ์— ๊ธ€์„ ์˜ฌ๋ฆฌ๊ฑฐ๋‚˜ ๋Œ“๊ธ€์„ ๋‹ค๋Š” ์ฆ‰์‹œ ๋‚ด ํ™”๋ฉด์— 1ms์˜ ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ์˜ ํ”์ ๋„ ์—†์ด ์Šค๋ฌด์Šคํ•˜๊ฒŒ ์ตœ์‹  ๊ธ€์ด '์Šคํฐ(Spawn)' ๋ฉ๋‹ˆ๋‹ค. ์ง„์ •ํ•œ ์‹ค์‹œ๊ฐ„(Realtime) ํ†ต์‹ ์˜ ์œ„๋ ฅ์ด์ฃ !


3. SSE (Server-Sent Events) ์™€์˜ ๊ฒฐํ•ฉ ์•„ํ‚คํ…์ฒ˜

์ฑ„ํŒ…์ด ์•„๋‹ˆ๋ผ ๋‹จ์ˆœ ๋‹จ๋ฐฉํ–ฅ ์•Œ๋ฆผ(์•Œ๋ฆผ ์ข…๋‹ฌ๊ธฐ์žฅ ์ˆซ์ž ๋“ฑ) ์ด๋ผ๋ฉด ๋ฌด๊ฑฐ์šด WebSocket ๋Œ€์‹  HTTP ๊ธฐ๋ฐ˜์˜ SSE(Server-Sent Events) ๋กœ ๊ฐ€๋ณ๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ํŠธ๋ Œ๋“œ์ž…๋‹ˆ๋‹ค. React Query์— SSE๋ฅผ ๋ถ™์ผ ๋• ๋ณดํ†ต ์ปดํฌ๋„ŒํŠธ ์ƒ๋ช…์ฃผ๊ธฐ์™€ ์ „์—ญ ํ›…(QueryClient ํŒฉํ† ๋ฆฌ) ์ค‘ ํ›„์ž์— ๋ถ™์ด๋Š” ๊ฒƒ์ด ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „์„ฑ์— ์ข‹์Šต๋‹ˆ๋‹ค.

// ๐Ÿ“ providers/get-query-client.ts ์˜ ์ดˆ๊ธฐํ™” ๋ธ”๋ก
let sseConnection: EventSource | null = null;
 
export function getQueryClient() {
  const client = new QueryClient({ ... });
 
  // ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ €)์—์„œ ๋‹จ ์ตœ์ดˆ 1ํšŒ๋งŒ SSE ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ„ฐ๋„์„ ์˜๊ตฌ ๊ฐœํ†ต!
  if (typeof window !== 'undefined' && !sseConnection) {
    sseConnection = new EventSource('/api/stream/notifications');
 
    // ๋ฐ์ดํ„ฐ๊ฐ€ ์Ÿ์•„์งˆ ๋•Œ๋งˆ๋‹ค ์บ์‹œ ๊ธˆ๊ณ ์— ์ฆ‰๊ฐ ๊ฝ‚์•„๋ฒ„๋ฆผ
    sseConnection.onmessage = (event) => {
      const liveData = JSON.parse(event.data);
      client.setQueryData(['notifications'], liveData);
    };
  }
 
  return client;
}

์ด๋Ÿฌ๋ฉด ๋‹น์‹ ์˜ ์•ฑ์€ ์ ‘์†ํ•˜์ž๋งˆ์ž ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ์˜ ์ถ•๋ณต์„ ๋ฐ›๋Š” ๊ณ ๋„๋กœ ์ตœ์ ํ™”๋œ ์ŠคํŠธ๋ฆฌ๋ฐ ์•ฑ์ด ๋ฉ๋‹ˆ๋‹ค.


๐Ÿ“ ๋ฐฐ์šด ๋‚ด์šฉ ์ ๊ฒ€ํ•˜๊ธฐ (Quiz)

Q. ์˜์ฒ ์ด๋Š” ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…๋ฐฉ ํ™”๋ฉด์„ ๋งŒ๋“ค๋ฉด์„œ, "์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€(new_chat)" ์›น์†Œ์ผ“ ์ด๋ฒคํŠธ๊ฐ€ ์˜ค๋ฉด setQueryData๋กœ ๋ฌดํ•œ ์Šคํฌ๋กค(useInfiniteQuery)์˜ ๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด ๋์— ๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ์ฒด๋ฅผ ํ•˜๋‚˜ ํ‘ธ์‹œ(Push) ํ•ด์ฃผ๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์น˜๋ช…์ ์ธ ๋ฒ„๊ทธ๋ฅผ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์œ ์ €๊ฐ€ ์ฑ„ํŒ…๋ฐฉ ํ™”๋ฉด์„ ๋‹ซ์•˜๋‹ค๊ฐ€(๋‹ค๋ฅธ ๋ฉ”๋‰ด ์ด๋™ ํ›„ ์–ธ๋งˆ์šดํŠธ), ์ฆ‰์‹œ 1๋ถ„(staleTime ๊ธฐ๋ณธ๊ฐ’์ธ 0์ดˆ ์ƒํƒœ, gcTime ์ƒ์กด ์ƒํƒœ) ๋’ค ์ฑ„ํŒ…๋ฐฉ์— ๋‹ค์‹œ ์ง„์ž…ํ–ˆ๋”๋‹ˆ ํ™”๋ฉด์— ๋ฐฉ๊ธˆ ์›น์†Œ์ผ“์œผ๋กœ ์—ด์‹ฌํžˆ ์ถ”๊ฐ€ํ•ด ๋„ฃ์—ˆ๋˜ ๋ฉ”์„ธ์ง€๊ฐ€ ์‚ฌ๋ผ์กŒ๋‹ค๊ฐ€ ๊นœ๋นก ํ•˜๋ฉด์„œ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋กœ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค. ์™œ ์ด ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฑธ๊นŒ์š”?

  • A) ๋ธŒ๋ผ์šฐ์ € ํƒญ์„ ๋ฐ”๊ฟจ๋”๋‹ˆ ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ์ด ์Šค์Šค๋กœ ๋Š์–ด์ ธ ๋ฒ„๋ ค์„œ ์บ์‹œ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๊ฐ™์ด ์ฆ๋ฐœํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
  • B) ์†Œ์ผ“์ด ์•„๋ฌด๋ฆฌ ์บ์‹œ(QueryCache)๋ฅผ ์ •์„ฑ์Šค๋ ˆ ์ˆ˜์ž‘์—…์œผ๋กœ ์ฑ„์›Œ๋†จ์–ด๋„, staleTime: 0 (๊ธฐ๋ณธ๊ฐ’) ์ƒํƒœ์—์„œ ์œ ์ €๊ฐ€ ์ปดํฌ๋„ŒํŠธ์— ๋‹ค์‹œ ๋งˆ์šดํŠธ(์ง„์ž…)ํ•˜๋Š” ์ˆœ๊ฐ„ "์–ด? ์ด๊ฑฐ ์ƒํ•œ๋นต์ด๋„ค?" ํ•˜๊ณ  ์ฆ‰๊ฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌํŒจ์นญ(GET /chats) ์„ ์š”์ฒญํ•ด, ์ˆ˜์ž‘์—… ์ด๋ ฅ์ด ์„œ๋ฒ„ ์ดˆ๊นƒ๊ฐ’ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋‚ ์•„๊ฐ”๊ธฐ(๋ฎ์–ด์”Œ์›Œ์กŒ๊ธฐ) ๋•Œ๋ฌธ์ด๋‹ค.
  • C) setQueryData๋Š” ๋ฉ”๋ชจ๋ฆฌ ํœ˜๋ฐœ์„ฑ์ด๋ผ์„œ ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•˜๋Š” ์ˆœ๊ฐ„ ์ฆ‰์‹œ Garbage Collect(์‚ญ์ œ ๋จ) ์ฒ˜๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: TkDodo ์•„ํ‹ฐํด #7์—์„œ ์งš์–ด์ฃผ๋Š” ๊ฐ€์žฅ ํ”ํ•œ WebSocket ์—ฐ๋™ ํ•จ์ •์ž…๋‹ˆ๋‹ค. ๋‹น์‹ ์ด setQueryData ๋กœ ์•„๋ฌด๋ฆฌ ์—ด์‹ฌํžˆ ํ”„๋ก ํŠธ ์บ์‹œ์— ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ์šฑ์—ฌ๋„ฃ์–ด๋„... ํ•ด๋‹น ๋ฐ์ดํ„ฐ์˜ ์œ ํ†ต๊ธฐํ•œ(staleTime)์ด ์งง๊ฑฐ๋‚˜ 0์ด๋ฉด ์œ ์ €๊ฐ€ ๋‹ค๋ฅธ ํƒญ์„ ์ฐ๊ณ  ๋‹ค์‹œ ์ปดํฌ๋„ŒํŠธ์— ํฌ์ปค์‹ฑ(refetchOnWindowFocus ํ˜น์€ Mount) ํ•˜๋Š” ์ˆœ๊ฐ„ React Query๋Š” "์ƒํ•œ ๊ฑฐ๋‹ˆ๊นŒ ๋ฒ„๋ฆฌ๊ณ  ์„œ๋ฒ„์—์„œ ํ†ต์งธ๋กœ ์Ž„๋ฒผ์˜ค์ž!" ๋ฐœ๋™์„ ๊ฒ๋‹ˆ๋‹ค. ์ด๋•Œ ์•„์ฃผ ์ฐฐ๋‚˜์˜ ์ˆœ๊ฐ„ ๋™์•ˆ ๊นœ๋นก์ž„์ด๋‚˜ ๋ฐ์ดํ„ฐ ๋ฏธ์Šค๋งค์น˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: "์˜์ฒ  ๋‹˜, ์›น์†Œ์ผ“์œผ๋กœ ์—ด์‹ฌํžˆ ์บ์‹œ๋ฅผ ๋ฐฅ ๋จน์—ฌ ๋†จ์œผ๋ฉด, ์ ์–ด๋„ ๊ทธ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹น๋ถ„๊ฐ„์€ ์‹ ์„ ํ•˜๋‹ค๋Š” ๊ฑธ React Queryํ•œํ…Œ ์ธ์ง€์‹œ์ผœ์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. WebSocket ์—ฐ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•  ๋•Œ๋Š” ํ•ด๋‹น useQuery์˜ staleTime์„ Infinity ๋“ฑ์œผ๋กœ ๋„‰๋„‰ํžˆ ๊ฑธ์–ด ์‹ ์„ ๋„ ๊ด€๋ฆฌ๋ฅผ ์Šค์Šค๋กœ ํ•˜๊ฒŒ๋” ํ†ต์ œ๊ถŒ์„ ์™„์ „ํžˆ ๋„˜๊ฒจ๋ฐ›์œผ์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค!"
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ์›น์†Œ์ผ“ setQueryData ๋ฎ์–ด์“ฐ๊ธฐ ๋กœ์ง ์งค ๋•Œ๋Š”, ๋ฐ˜๋“œ์‹œ ์Œ๋‘ฅ์ด์ฒ˜๋Ÿผ staleTime: Infinity ์„ธํŒ…๋„ ์ฑ™๊ฒจ๋ผ!

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. WebSocket/SSE ์ด๋ฒคํŠธ๊ฐ€ โ€œ๊ฒŒ์‹œ๊ธ€ 42๋ฒˆ์ด ๋ณ€๊ฒฝ๋จโ€์ฒ˜๋Ÿผ ์ผ๋ถ€ ์ •๋ณด๋งŒ ์ค„ ๋•Œ ๊ฐ€์žฅ ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ ๋Œ€์‘์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

โœ… ์ •๋‹ต: ๊ด€๋ จ queryKey๋ฅผ ๋ฌดํšจํ™”ํ•ด ์„œ๋ฒ„์—์„œ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:
๋ถ€๋ถ„ ์ด๋ฒคํŠธ๋งŒ์œผ๋กœ๋Š” ์บ์‹œ๋ฅผ ์ •ํ™•ํžˆ ์žฌ๊ตฌ์„ฑํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ์ด๋•Œ invalidateQueries๋Š” ์„œ๋ฒ„๋ฅผ ์ง„์‹ค์˜ ์›์ฒœ์œผ๋กœ ๋‹ค์‹œ ํ™•์ธํ•˜๋Š” ์•ˆ์ „ํ•œ ์„ ํƒ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์ด๋ฒคํŠธ๊ฐ€ ๋งค์šฐ ์žฆ๋‹ค๋ฉด batch๋‚˜ throttle๋กœ ์„œ๋ฒ„ ๋ถ€ํ•˜๋ฅผ ํ•จ๊ป˜ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Q2. ์ด๋ฒคํŠธ payload๊ฐ€ ๋ณ€๊ฒฝ๋œ ์ „์ฒด ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•œ๋‹ค๋ฉด setQueryData๊ฐ€ ์œ ๋ฆฌํ•œ ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

โœ… ์ •๋‹ต: ์ด๋ฏธ ์ตœ์‹  ๋ฐ์ดํ„ฐ๊ฐ€ ๋„์ฐฉํ–ˆ์œผ๋ฏ€๋กœ ์ถ”๊ฐ€ ๋„คํŠธ์›Œํฌ ์š”์ฒญ ์—†์ด ์บ์‹œ๋ฅผ ์ง์ ‘ ๊ฐฑ์‹ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:
์„œ๋ฒ„๊ฐ€ Push๋กœ ์™„์ „ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์คฌ๋Š”๋ฐ ๋‹ค์‹œ Pull์„ ๋ณด๋‚ด๋ฉด ๋น„์šฉ์ด ์ค‘๋ณต๋ฉ๋‹ˆ๋‹ค. setQueryData๋Š” ๋„คํŠธ์›Œํฌ๋ฅผ ์•„๋ผ๊ณ  ํ™”๋ฉด ๋ฐ˜์‘์„ ๋น ๋ฅด๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ, ๋ฆฌ์ŠคํŠธ์™€ ์ƒ์„ธ์ฒ˜๋Ÿผ ๊ฐ™์€ ๊ฐ์ฒด๊ฐ€ ๋“ค์–ด๊ฐ„ ์บ์‹œ๋“ค์„ ์–ด๋””๊นŒ์ง€ ๊ฐฑ์‹ ํ• ์ง€ ํ‚ค ์„ค๊ณ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์ด๋ฒคํŠธ๊ฐ€ ์ดˆ๋‹น ์—ฌ๋Ÿฌ ๋ฒˆ ์˜ค๊ณ , ์˜์ˆ˜๋„ค ์„œ๋ฒ„ ๋ถ€ํ•˜๊ฐ€ ํŠ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ํŒ๋‹จ ๊ธฐ์ค€์„ ์„ธ์›Œ์•ผ ํ•˜๋‚˜์š”?

โœ… ์ •๋‹ต: ์ด๋ฒคํŠธ๊ฐ€ partial์ธ์ง€ full์ธ์ง€ ๊ตฌ๋ถ„ํ•˜๊ณ , partial์€ ๋ฌถ์–ด์„œ ๋ฌดํšจํ™”ํ•˜๋ฉฐ full์€ ํ•„์š”ํ•œ ์บ์‹œ์— ์ง์ ‘ ์ฃผ์ž…ํ•˜๋Š” ์ „๋žต์„ ๋‚˜๋ˆ•๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:
์‹ค์‹œ๊ฐ„์ด๋ผ๊ณ  ๋ฐ˜๋“œ์‹œ invalidate๋ฅผ ๋‚ ๋ฆฌ๋ฉด HTTP ์š”์ฒญ ๊ธ‰์ฆ์ด ๋ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ ๋ถˆ์™„์ „ํ•œ ์ด๋ฒคํŠธ๋ฅผ ๋ฌด๋ฆฌํ•˜๊ฒŒ setQueryData์— ๋„ฃ์œผ๋ฉด ์บ์‹œ๊ฐ€ ๊ฑฐ์ง“์ด ๋ฉ๋‹ˆ๋‹ค. ์˜์ฒ ์ด ์ด์ œ ์ด๋ฒคํŠธ ํ’ˆ์งˆ์— ๋”ฐ๋ผ ๋™๊ธฐํ™” ์ „๋žต์„ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์™€, ๋‚˜ ๊ทธ๋™์•ˆ ์›น์†Œ์ผ“ ์ด๋ฒคํŠธ ๋–จ์–ด์งˆ ๋•Œ๋งˆ๋‹ค ์ƒ๊ฐ์—†์ด Invalidate ๋‚ ๋ ธ์—ˆ๋Š”๋ฐ, ์˜์ˆ˜ ๋‹˜์ด ๋‚˜ํ•œํ…Œ ๊ฟ€๋ฐค ๋•Œ๋ฆฌ๋Ÿฌ ์˜จ ์ด์œ ๋ฅผ ์•Œ๊ฒ ๋‹ค...
์ดˆ๋‹น 10๋ฒˆ ๋ฐ”๋€Œ๋Š” ๋น„ํŠธ์ฝ”์ธ ์‹œ์„ธ ๋ฐ์ดํ„ฐ๋ฅผ 10๋ฒˆ GET ์š”์ฒญ์œผ๋กœ ๋‹ค์‹œ ์š”์ฒญํ•˜๊ณ  ์žˆ์—ˆ์œผ๋‹ˆ ์„œ๋ฒ„ CPU ์‚ฌ์šฉ๋Ÿ‰์ด ํฌ๊ฒŒ ์˜ฌ๋ผ๊ฐ‘๋‹ˆ๋‹ค

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "์„œ๋ฒ„๊ฐ€ ํ‘ธ์‹œ(Push) ํ•˜๋Š” ์ด๋ฒคํŠธ๊ฐ€ Partial(์ผ๋ถ€) ์ด๋ผ๋ฉด invalidate ๋น”์„ ์˜๋˜ Batch/๋””๋„์Šค ๋ฐฉ์–ด๋ฅผ ๊ผญ ๊ฑธ์–ด๋‘๊ณ ! ์„œ๋ฒ„๊ฐ€ ํ‘ธ์‹œ ํ•˜๋Š” ์ด๋ฒคํŠธ๊ฐ€ Full(์ „์ฒด) ์ด๋ผ๋ฉด ์ ˆ๋Œ€ ๋„คํŠธ์›Œํฌ๋ฅผ ๋‹ค์‹œ ์š”์ฒญํ•˜์ง€ ๋ง๊ณ  setQueryData ํ•€์…‹ ์กฐ๋ฆฝ์œผ๋กœ ์šฐ์•„ํ•˜๊ฒŒ ์บ์‹œ๋ฅผ ๊ฐˆ์•„ ๋ผ์šฐ์ž!"

์‚ฌ์‹ค setQueryData ์จ์„œ ๋กœ์ปฌ ๋ฉ”๋ชจ๋ฆฌ ๋ฐฐ์—ด map ๋Œ๋ฆฌ๋Š” ๋กœ์ง ์งœ๋Š” ๊ฒŒ ์กฐ๊ธˆ ๊ท€์ฐฎ๊ธด ํ•œ๋ฐ, ์ด๊ฑฐ ๋ฐ˜์˜ํ•˜๊ณ  ๋‚˜๋‹ˆ๊นŒ ๋„คํŠธ์›Œํฌ ํƒญ์— ๋ถˆํ•„์š”ํ•œ HTTP ํ†ต์‹ ์ด ๋‹จ 1๊ฑด๋„ ์•ˆ ์ฐํžˆ๊ณ  ํด๋ผ์ด์–ธํŠธ ์ž์ฒด์ ์œผ๋กœ ์Šค๋ฌด์Šคํ•˜๊ฒŒ ํ™”๋ฉด์ด ๊นœ๋นก์ด๋Š” ๊ฑฐ ๋ณด๊ณ  ์พŒ๊ฐ ์˜ค์กŒ๋‹ค... ๋‚ด์ผ์€ ์œ ์ € ์ธํ’‹ ๋‹ค๋ฃจ๋Š” ํผ(Form) ์ž‘์—… ํ•ด๋ด์•ผ์ง€! ๐Ÿ’ป