July 21, 2025

๐Ÿ“‰ Cascading Network Requests (Waterfall Model): ๋А๋ฆฐ ๋กœ๋”ฉ๊ณผ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ์ €ํ•˜

JavaScript
React
์„ฑ๋Šฅ
๋น„๋™๊ธฐ์ฒ˜๋ฆฌ
UX
๋ Œ๋”๋ง์ „๋žต

Summary

๋ฐ์ดํ„ฐ ์š”์ฒญ์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” Waterfall ๋ชจ๋ธ์€ ๋ถˆํ•„์š”ํ•œ ๋„คํŠธ์›Œํฌ ์ง€์—ฐ์„ ์œ ๋ฐœํ•˜์—ฌ ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ์ฆ๊ฐ€์‹œํ‚ค๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ €ํ•ดํ•ฉ๋‹ˆ๋‹ค. Promise.all์„ ํ†ตํ•œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ, ๋ฐฑ์—”๋“œ Aggregator API/GraphQL ํ™œ์šฉ, ๊ทธ๋ฆฌ๊ณ  SSR/SSG์™€ ๊ฐ™์€ ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์ „๋žต์„ ํ†ตํ•ด ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์„ ๊ตฌํ˜„ํ•˜์—ฌ ์›น ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Why Wrong?

๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ณผ์ •์—์„œ ์ด์ „ ์š”์ฒญ์ด ์™„๋ฃŒ๋˜์–ด์•ผ ๋‹ค์Œ ์š”์ฒญ์„ ์‹œ์ž‘ํ•˜๋Š” ์ข…์†์ ์ธ ๊ตฌ์กฐ(Waterfall Model)๋Š” ๋ถˆํ•„์š”ํ•œ ์ง€์—ฐ์„ ๋ฐœ์ƒ์‹œ์ผœ ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ๋Š˜๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ €ํ•ดํ•ฉ๋‹ˆ๋‹ค. ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•  ๋•Œ, ๊ฐ ๋ฐ์ดํ„ฐ ์š”์ฒญ์ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋ฉด ์ „์ฒด ์‘๋‹ต ์‹œ๊ฐ„์ด ๋ชจ๋“  ์š”์ฒญ์˜ ํ•ฉ์œผ๋กœ ๋Š˜์–ด๋‚ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํŠนํžˆ ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ์‹œ๊ฐ„(Latency)์ด ๋†’์€ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ธด ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ ์œ ๋ฐœํ•˜๋ฉฐ, ์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•ฉ๋‹ˆ๋‹ค:

  • ์ง๋ ฌํ™”๋œ ์ง€์—ฐ(Serialization Delay): ๊ฐ ์š”์ฒญ์€ ์ด์ „ ์š”์ฒญ์˜ ์™„๋ฃŒ๋ฅผ ๊ธฐ๋‹ค๋ ค์•ผ๋งŒ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž ํ”„๋กœํ•„์„ ๊ฐ€์ ธ์˜จ ํ›„ ํ•ด๋‹น ํ”„๋กœํ•„ ID๋กœ ๊ฒŒ์‹œ๋ฌผ์„ ๊ฐ€์ ธ์˜ค๋Š” ์‹์˜ ์ข…์†์  ํ˜ธ์ถœ์ด ๋ฐ˜๋ณต๋˜๋ฉด, ๊ฐ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์˜ ์™•๋ณต ์‹œ๊ฐ„(Round Trip Time)์ด ๋ˆ„์ ๋˜์–ด ์ „์ฒด ๋กœ๋”ฉ ์‹œ๊ฐ„์ด ๊ธฐํ•˜๊ธ‰์ˆ˜์ ์œผ๋กœ ๋Š˜์–ด๋‚ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ A ์š”์ฒญ์— 100ms, B ์š”์ฒญ์— 100ms๊ฐ€ ๊ฑธ๋ฆฐ๋‹ค๋ฉด, ์ˆœ์ฐจ์ ์œผ๋กœ๋Š” ์ด 200ms๊ฐ€ ์†Œ์š”๋˜์ง€๋งŒ, ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์‹œ์—๋Š” ์ตœ๋Œ€ 100ms(๊ฐ€์žฅ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์š”์ฒญ)๋กœ ๋‹จ์ถ•๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ธŒ๋ผ์šฐ์ € ์ž์› ํ™œ์šฉ ๋น„ํšจ์œจ์„ฑ: ํ˜„๋Œ€ ๋ธŒ๋ผ์šฐ์ €๋Š” ๋™์‹œ์— ์—ฌ๋Ÿฌ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Waterfall ๋ชจ๋ธ์€ ์ด๋Ÿฌํ•œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ๋Šฅ๋ ฅ์„ ์ œ๋Œ€๋กœ ํ™œ์šฉํ•˜์ง€ ๋ชปํ•ด ๋„คํŠธ์›Œํฌ ๋Œ€์—ญํญ์ด๋‚˜ CPU์™€ ๊ฐ™์€ ์ž์›์ด ์œ ํœด ์ƒํƒœ๋กœ ๋‚จ์•„์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „๋ฐ˜์ ์ธ ์„ฑ๋Šฅ์„ ์ €ํ•˜์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•ต์‹ฌ ์›น ์ง€ํ‘œ(Core Web Vitals) ์•…ํ™”: ํŠนํžˆ ์›น ์„ฑ๋Šฅ์˜ ์ค‘์š”ํ•œ ์ง€ํ‘œ์ธ Largest Contentful Paint (LCP)์— ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ ๋ฏธ์นฉ๋‹ˆ๋‹ค. ์ฃผ์š” ์ฝ˜ํ…์ธ ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ˆœ์ฐจ์ ์œผ๋กœ ๋กœ๋“œ๋˜๋ฉด LCP ์‹œ๊ฐ„์ด ๊ธธ์–ด์ ธ ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€ ๋กœ๋”ฉ์ด ๋А๋ฆฌ๋‹ค๊ณ  ๋А๋ผ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, Total Blocking Time (TBT)๊ณผ ๊ฐ™์€ ์ƒํ˜ธ์ž‘์šฉ ์ง€์—ฐ ์ง€ํ‘œ์—๋„ ๋ถ€์ •์ ์ธ ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ์ €ํ•˜ ๋ฐ ์ดํƒˆ๋ฅ  ์ฆ๊ฐ€: ์‚ฌ์šฉ์ž๋Š” ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ ๋นˆ ํ™”๋ฉด, ๋ถˆ์™„์ „ํ•œ ํ™”๋ฉด ๋˜๋Š” ๋ฌด์˜๋ฏธํ•œ ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ๋งŒ ๋ณด๊ฒŒ ๋˜์–ด ์ง€๋ฃจํ•จ์„ ๋А๋ผ๊ฑฐ๋‚˜, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋А๋ฆฌ๋‹ค๊ณ  ์ธ์‹ํ•˜์—ฌ ์ดํƒˆํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์ปค์ง‘๋‹ˆ๋‹ค. ์ด๋Š” ์„œ๋น„์Šค์˜ ๋งŒ์กฑ๋„์™€ ์ง๊ฒฐ๋˜๋Š” ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.

How to Fix?

๋ฐ์ดํ„ฐ ์š”์ฒญ ์‹œ ๋ถˆํ•„์š”ํ•œ ์ง๋ ฌํ™”๋ฅผ ํ”ผํ•˜๊ณ , ๊ฐ€๋Šฅํ•œ ํ•œ ๋งŽ์€ ์š”์ฒญ์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ „์ฒด ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ๋‹จ์ถ•ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ๋ฐฉ๋ฒ•๋“ค์ž…๋‹ˆ๋‹ค:

  • ์š”์ฒญ ์˜์กด์„ฑ ๋ถ„์„ ๋ฐ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฐ ๋ฐ์ดํ„ฐ ์š”์ฒญ์ด ์ •๋ง๋กœ ๋‹ค๋ฅธ ์š”์ฒญ์— ์˜์กด์ ์ธ์ง€ ๋ฉด๋ฐ€ํžˆ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์˜์กด์„ฑ์ด ์—†๋‹ค๋ฉด, JavaScript์˜ Promise.all ๋˜๋Š” Promise.allSettled๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์‹œ์— ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ์‹œ์ž‘ํ•˜๊ณ  ๋ชจ๋“  ์š”์ฒญ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐœ๋ณ„ ์š”์ฒญ์˜ ์ง€์—ฐ ์‹œ๊ฐ„์„ ์ˆจ๊ฒจ ์ „์ฒด ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ํฌ๊ฒŒ ๋‹จ์ถ•์‹œํ‚ต๋‹ˆ๋‹ค.
    • Promise.all: ๋ชจ๋“  Promise๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์–ด์•ผ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ํ•œ๊บผ๋ฒˆ์— ํ•„์š”ํ•˜๊ณ , ์–ด๋А ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•˜๋ฉด ์•ˆ ๋˜๋Š” ๊ฒฝ์šฐ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.
    • Promise.allSettled: ๋ชจ๋“  Promise์˜ ์„ฑ๊ณต/์‹คํŒจ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ๋ชจ๋“  Promise๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. ๊ฐœ๋ณ„ ์š”์ฒญ์˜ ์„ฑ๊ณต ์—ฌ๋ถ€๊ฐ€ ์ „์ฒด ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ง‰์ง€ ์•Š์•„์•ผ ํ•  ๋•Œ ์œ ์šฉํ•˜๋ฉฐ, ๊ฐ ์š”์ฒญ์˜ ์ƒํƒœ๋ฅผ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฐฑ์—”๋“œ API ์ตœ์ ํ™” ๋ฐ ํ†ตํ•ฉ: ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ์š”์ฒญ์„ ๋ณด๋‚ด์•ผ ํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ๋ฐฑ์—”๋“œ API๋ฅผ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค.
    • Aggregator API ๋„์ž…: ์—ฌ๋Ÿฌ ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ๋ฐฑ์—”๋“œ API ํ˜ธ์ถœ๋กœ ๋ฌถ์–ด ์ œ๊ณตํ•˜๋Š” Aggregator API ์—”๋“œํฌ์ธํŠธ๋ฅผ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ์™•๋ณต(Round Trip)์„ ์ค„์—ฌ ๋„คํŠธ์›Œํฌ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ตœ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค.
    • GraphQL ํ™œ์šฉ: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ํ•„๋“œ๋ฅผ ์ •ํ™•ํžˆ ๋ช…์‹œํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์š”์ฒญ์œผ๋กœ ์—ฌ๋Ÿฌ ๋ฆฌ์†Œ์Šค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” GraphQL์„ ๋„์ž…ํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ•๋ ฅํ•œ ํ•ด๊ฒฐ์ฑ…์ž…๋‹ˆ๋‹ค. ์ด๋Š” N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ์—๋„ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ ์„ ์ทจ(Prefetching) ๋ฐ ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์ „๋žต ํ™œ์šฉ: ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ํŽ˜์ด์ง€๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๊ฒŒ ๋  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค๊ณ  ์˜ˆ์ƒ๋  ๋•Œ, ๋ฏธ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ์บ์‹ฑํ•˜๊ฑฐ๋‚˜ ์ดˆ๊ธฐ HTML์— ํฌํ•จ์‹œํ‚ต๋‹ˆ๋‹ค.
    • Resource Hints (<link rel="preload">, <link rel="preconnect">): HTML์˜ <head> ํƒœ๊ทธ ๋‚ด์— preload๋‚˜ preconnect ํžŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฏธ๋ฆฌ ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค(์˜ˆ: ํŠน์ • API ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ ์—ฐ๊ฒฐ)๋ฅผ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ์—ฐ๊ฒฐ์„ ์‹œ์ž‘ํ•˜๋„๋ก ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์‹ค์ œ ์š”์ฒญ ์‹œ์˜ ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ ์ค„์—ฌ์ค๋‹ˆ๋‹ค.
    • ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(SSR) ๋˜๋Š” ์ •์  ์‚ฌ์ดํŠธ ์ƒ์„ฑ(SSG): ์ดˆ๊ธฐ ํŽ˜์ด์ง€ ๋กœ๋”ฉ์— ํ•„์ˆ˜์ ์ธ ๋ฐ์ดํ„ฐ๋Š” ์„œ๋ฒ„์—์„œ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์™€ HTML์— ํฌํ•จ์‹œ์ผœ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ถ”๊ฐ€์ ์ธ ๋„คํŠธ์›Œํฌ ์š”์ฒญ ์—†์ด ๋น ๋ฅด๊ฒŒ ์ฝ˜ํ…์ธ ๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ฒซ ํŽ˜์ธํŠธ(First Contentful Paint)์™€ LCP๋ฅผ ํฌ๊ฒŒ ๊ฐœ์„ ํ•˜๋ฉฐ, SEO์—๋„ ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Before Code (Bad)

// Before: Waterfall Requests (์ˆœ์ฐจ์  ์š”์ฒญ) - ๋ถˆํ•„์š”ํ•œ ์ง€์—ฐ ๋ฐœ์ƒ
async function fetchUserProfileAndPostsSequential(userId) {
  try {
    console.log('--- ์ˆœ์ฐจ์  ์š”์ฒญ ์‹œ์ž‘ ---');
    // 1. ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ •๋ณด ์š”์ฒญ
    const userResponse = await fetch(`/api/users/${userId}`);
    const user = await userResponse.json();
    console.log('User Profile fetched:', user.name);

    // 2. ์‚ฌ์šฉ์ž ํ”„๋กœํ•„์ด ๋กœ๋“œ๋œ ํ›„ ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ๊ฒŒ์‹œ๋ฌผ ์š”์ฒญ (ํ”„๋กœํ•„ ๋ฐ์ดํ„ฐ์— ์˜์กดํ•˜์ง€ ์•Š์•„๋„ ๋จ)
    const postsResponse = await fetch(`/api/users/${userId}/posts`);
    const posts = await postsResponse.json();
    console.log('User Posts fetched:', posts.length, 'posts');

    // 3. ์‚ฌ์šฉ์ž ๊ฒŒ์‹œ๋ฌผ์ด ๋กœ๋“œ๋œ ํ›„ ๊ฐ ๊ฒŒ์‹œ๋ฌผ์˜ ๋Œ“๊ธ€ ์ˆ˜ ์š”์ฒญ (์—ฌ๊ธฐ์„œ ๋˜ ๋‹ค๋ฅธ ์ž ์žฌ์  N+1 ๋ฌธ์ œ ๋ฐœ์ƒ ๊ฐ€๋Šฅ)
    const commentsCounts = {};
    for (const post of posts) {
      const commentsResponse = await fetch(`/api/posts/${post.id}/comments/count`);
      const countData = await commentsResponse.json();
      commentsCounts[post.id] = countData.count;
      console.log(`Comments for post ${post.id}:`, countData.count);
    }

    console.log('--- ์ˆœ์ฐจ์  ์š”์ฒญ ์™„๋ฃŒ ---');
    return { user, posts, commentsCounts };
  } catch (error) {
    console.error('Error fetching data sequentially:', error);
    throw error;
  }
}

// ๊ฐ€์ƒ์˜ API ํ˜ธ์ถœ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (๊ฐ 100ms)
// fetch ํ•จ์ˆ˜๋ฅผ ๋ž˜ํ•‘ํ•˜์—ฌ ์ง€์—ฐ์„ ์ถ”๊ฐ€ํ•˜๋Š” ์˜ˆ์‹œ
const originalFetch = global.fetch;
global.fetch = async (...args) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(originalFetch(...args));
    }, 100); // ๊ฐ fetch์— 100ms ์ง€์—ฐ ์ถ”๊ฐ€
  });
};

// Example usage
// fetchUserProfileAndPostsSequential(123); // ์ด 300ms + (N * 100ms) ์ด์ƒ ์†Œ์š”

After Code (Good)

// After: Parallel Requests (๋ณ‘๋ ฌ ์š”์ฒญ) - ์„ฑ๋Šฅ ์ตœ์ ํ™”
async function fetchUserProfileAndPostsParallel(userId) {
  try {
    console.log('--- ๋ณ‘๋ ฌ ์š”์ฒญ ์‹œ์ž‘ ---');
    // 1. ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ •๋ณด์™€ ๊ฒŒ์‹œ๋ฌผ ์ •๋ณด๋ฅผ ๋™์‹œ์— ์š”์ฒญ (์„œ๋กœ ์˜์กด์„ฑ ์—†๋Š” ๊ฒฝ์šฐ)
    // Promise.all์„ ์‚ฌ์šฉํ•˜์—ฌ ๋™์‹œ์— ์—ฌ๋Ÿฌ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์‹œ์ž‘ํ•˜๊ณ  ๋ชจ๋“  ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆผ
    const [userResponse, postsResponse] = await Promise.all([
      fetch(`/api/users/${userId}`),
      fetch(`/api/users/${userId}/posts`)
    ]);

    const user = await userResponse.json();
    const posts = await postsResponse.json();
    console.log('User Profile fetched:', user.name);
    console.log('User Posts fetched:', posts.length, 'posts');

    // 2. ๋ชจ๋“  ๊ฒŒ์‹œ๋ฌผ์˜ ๋Œ“๊ธ€ ์ˆ˜ ์š”์ฒญ์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌ
    // N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ฐฑ์—”๋“œ์—์„œ Aggregator API๋ฅผ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜,
    // ํ”„๋ก ํŠธ์—”๋“œ์—์„œ Promise.all๋กœ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ. ์—ฌ๊ธฐ์„œ๋Š” ํ›„์ž์˜ ์˜ˆ์‹œ.
    const commentsCountPromises = posts.map(post =>
      fetch(`/api/posts/${post.id}/comments/count`).then(res => res.json())
    );

    const commentsCountsArray = await Promise.all(commentsCountPromises);
    const commentsCounts = commentsCountsArray.reduce((acc, current, index) => {
      acc[posts[index].id] = current.count;
      return acc;
    }, {});
    console.log('All Comments Counts fetched:', Object.keys(commentsCounts).length);

    console.log('--- ๋ณ‘๋ ฌ ์š”์ฒญ ์™„๋ฃŒ ---');
    return { user, posts, commentsCounts };
  } catch (error) {
    console.error('Error fetching data in parallel:', error);
    throw error;
  }
}

// ๊ฐ€์ƒ์˜ API ํ˜ธ์ถœ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (๊ฐ 100ms)
// (์œ„ Before ์ฝ”๋“œ์—์„œ ์ด๋ฏธ ์„ค์ •๋˜์—ˆ๋‹ค๊ณ  ๊ฐ€์ •)

// Example usage
// fetchUserProfileAndPostsParallel(123); // ์ด 100ms + (100ms * (N=1์ผ๋•Œ)) + ... ์†Œ์š”
// ์ฆ‰, (๊ฐ€์žฅ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” 1๋‹จ๊ณ„ ์š”์ฒญ) + (๊ฐ€์žฅ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” 2๋‹จ๊ณ„ ์š”์ฒญ)๋งŒ ์†Œ์š”