Eliminating Waterfalls

date
2026-01-15
order
1

1. Eliminating Waterfalls

์ค‘์š”๋„ : CRITICAL (์›Œํ„ฐํด ํ˜„์ƒ์€ ๊ฐ€์žฅ ํฐ ์„ฑ๋Šฅ ์ €ํ•˜ ์š”์ธ์ด๋‹ค.)

[!faq] Waterfall ์ด๋ž€? ์ด์ „ ์š”์ฒญ์ด ์™„๋ฃŒ๋˜์–ด์•ผ ๋‹ค์Œ ์š”์ฒญ์ด ์ง„ํ–‰๋˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค. ์ฆ‰ ์—ฌ๋Ÿฌ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์žˆ์„๋•Œ ์ˆœ์ฐจ์ ์œผ๋กœ ํ•˜๋‚˜์”ฉ ์ง„ํ–‰๋˜๋Š” ํ˜„์ƒ์„ ์˜๋ฏธํ•˜๋Š”๋ฐ, await์„ ์‚ฌ์šฉํ•˜๋‹ค๋ณด๋ฉด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค. ๋ฌผ๋ก  ์˜๋„์ ์œผ๋กœ ์›Œํ„ฐํด์ด ์ผ์–ด๋‚˜๊ฒŒ ์„ค๊ณ„ํ•ด์•ผํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ๊ฐ€ ๋” ๋งŽ๋‹ค. ์ถœ์ฒ˜

1. Defer Await Until Needed

์ค‘์š”๋„ : HIGH

๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜์ง€๋งŒ ํšจ๊ณผ ๋“ฑ๊ธ‰์€ HIGH ์ด๋‹ค. ์Šคํ‚ต๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žฆ์€ ์„ค๊ณ„, ๋ฏธ๋ค„์ง„ ์ž‘์—…์ด ๋ฌด๊ฑฐ์šด ๊ฒฝ์šฐ์— ํŠนํžˆ ํšจ๊ณผ์ ์ด๋‹ค.

Incorrect

async function handleRequest(userId: string, skipProcessing: boolean) {
  const userData = await fetchUserData(userId)
  
  if (skipProcessing) {
    // Returns immediately but still waited for userData
    return { skipped: true }
  }
  
  // Only this branch uses userData
  return processUserData(userData)
}

Correct

async function handleRequest(userId: string, skipProcessing: boolean) {
  if (skipProcessing) {
    // Returns immediately without waiting
    return { skipped: true }
  }
  
  // Fetch only when needed
  const userData = await fetchUserData(userId)
  return processUserData(userData)
}

2. Dependency-Based Parallelization

์ค‘์š”๋„ : CRITICAL (2-10๋ฐฐ ์†๋„ ๊ฐœ์„ )

Partial Dependencies ๋ถ€๋ถ„์  ์˜์กด์„ฑ์ด๋ž€ ์—ฌ๋Ÿฌ ๋น„๋™๊ธฐ ์ž‘์—…์ด ์žˆ์„ ๋•Œ, ์–ด๋–ค ์ž‘์—…์€ ๋‹ค๋ฅธ ์ž‘์—…์˜ ๊ฒฐ๊ณผ๊ฐ€ ํ•„์š”ํ•˜์ง€๋งŒ, ์–ด๋–ค ์ž‘์—…์€ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ๋ฅผ ๋งํ•œ๋‹ค.

  • fetchUser() : 1์ดˆ ์†Œ์š”
  • fetchConfig() : 10์ดˆ ์†Œ์š”
  • fetchProfile(user.id) : 10์ดˆ ์†Œ์š”

์ด ์„ธ ์ž‘์—…์ด ์žˆ์„๋•Œ fetchProfile(user.id)๋Š” fetchUser()์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•„์š”๋กœ ํ•œ๋‹ค. ์ฆ‰ ์ด ๊ฒฝ์šฐ์—๋Š” ์›Œํ„ฐํด์ด ํ•„์š”ํ•œ ์ƒํ™ฉ.

Incorrect

๊ทธ๋ž˜์„œ ์œ„์˜ ๋‘ ์ž‘์—…์€ Promise.all()๋กœ ํ•œ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๊ณ ์„œ ๊ทธ ํ›„์— ์•„๋ž˜์˜ ์ž‘์—…์„ ์‹คํ–‰ํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š”๊ฒŒ ํ”ํ•œ ํŒจํ„ด์ด๋‹ค.

profile์ด ํ•„์š”๋กœํ•˜๋Š” user๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ 1์ดˆ๊ฐ€ ๊ฑธ๋ฆฌ๋Š”๋ฐ ์“ธ๋ฐ์—†์ด config ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋ฉด์„œ 10์ดˆ๋‚˜ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ƒํ™ฉ.

const [user, config] = await Promise.all([
  fetchUser(),  // 1s
  fetchConfig() // 10s
]) // 10s
const profile = await fetchProfile(user.id) // 10s
// total : 20s

user๋งŒ ๋จผ์ € ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

const user = await fetchUser(); // 1s
const [profile, config] = await Promise.all([
  fetchProfile(user.id),  // 10s
  fetchConfig() // 10s
]) // 10s

// total : 11s

๊ทธ๋Ÿฐ๋ฐ ๋ฌธ์ œ๋Š” ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ ์ƒํƒœ ๋ณ€๋™๊ณผ ๊ฐ™์€ ์–ด๋–ค ์ด์œ ๋“ค๋กœ ์ธํ•ด ์œ„ ์ž‘์—…๋“ค์˜ ์†Œ์š” ์‹œ๊ฐ„์ด ๋ฐ”๋€๋‹ค๋ฉด? ์•„๋ž˜์˜ ์ฝ”๋“œ๊ฐ€ ์œ„์˜ ์ฝ”๋“œ๋ณด๋‹ค ์˜คํžˆ๋ ค ์‹œ๊ฐ„์ด ๋” ๊ฑธ๋ฆด์ˆ˜๋„ ์žˆ๊ฒŒ ๋œ๋‹ค.

๋”ฐ๋ผ์„œ ๋ชจ๋“  ์ƒํ™ฉ์—์„œ ํ•ญ์ƒ ์ตœ์†Œํ•œ์˜ ์‹œ๊ฐ„๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ง์ ‘ ์˜์กด ๊ด€๊ณ„๋ฅผ ํฌํ•จํ•ด ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

const [[user, profile], config] = await Promise.all([
  fetchUser().then(user=> fetchProfile(user.id).then(profile => [user, profile])),
  fetchConfig()
])

๋ญ๊ฐ€ ๋ฌธ์ œ์ธ๊ฐ€ ์‹ถ์ง€๋งŒ, (์—ญ์‹œ๋‚˜ ๋‹ค๋ฅธ ๋ชจ๋“  ๋ฌธ์ œ๋“ค์ฒ˜๋Ÿผ) ํ”„๋กœ์ ํŠธ์˜ ๊ทœ๋ชจ๊ฐ€ ๊ฑฐ๋Œ€ํ•ด์ง€๋ฉด ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•˜๋Š”๊ฑด ํž˜๋“ค๋‹ค๊ณ  ํ•œ๋‹ค. ๋งํฌ

์ด๋ ‡๊ฒŒ ์œ„์™€ ๋น„๊ตํ•ด ์กฐ๊ธˆ๋งŒ ๋ณต์žกํ•ด์ ธ๋„ ๊ฐ€๋…์„ฑ์ด ํฌ๊ฒŒ ๋–จ์–ด์ง„๋‹ค.

const promiseA = getA()
const promiseB = getB()
const promiseC = getC()
const promiseD = Promise.all([promiseA, promiseC]).then(([a, c]) => getD(a, c))
const promiseE = Promise.all([promiseB, promiseC]).then(([b, c]) => getE(b, c))
const [a, b, c, d, e] = await Promise.all([promiseA, promiseB, promiseC, promiseD, promiseE])

Correct

better-all ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ„๋‹จํžˆ ํ•ด๊ฒฐ๋œ๋‹ค.

import { all } from 'better-all'

const { user, config, profile } = await all({
  async user() { return fetchUser() },
  async config() { return fetchConfig() },
  async profile() {
    return fetchProfile((await this.$.user).id)
  }
})
const { a, b, c, d, e } = await all({
  async a() { return getA() },
  async b() { return getB() },
  async c() { return getC() },
  async d() { return getD(await this.$.a, await this.$.c) },
  async e() { return getE(await this.$.b, await this.$.c) }
})

better-all ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋งํฌ

3. Prevent Waterfall Chains in API Routes

์ค‘์š”๋„ : CRITICAL (2-10๋ฐฐ ์†๋„ ๊ฐœ์„ )

api ๋ฃจํŠธ๋‚˜ ์„œ๋ฒ„์•ก์…˜์„ ์‚ฌ์šฉํ•  ๋•Œ, await ์—†์ด Promise๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๋ฌผ๋ก  ๋ณต์žกํ•œ ๊ฒฝ์šฐ์—๋Š” ์œ„์—์„œ ์ œ์‹œํ•œ better-all ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ๋” ์ ํ•ฉํ•œ ํ•ด๊ฒฐ๋ฒ•.

Incorrect

์›Œํ„ฐํด์˜ ์ „ํ˜•์ ์ธ ์˜ˆ์‹œ. fetchConfig()๋Š” ์ž์‹ ๊ณผ ์•„๋ฌด ์ƒ๊ด€ ์—†๋Š” auth()์„ ๊ฐ€๋””๋ผ๊ธฐ๊ณ  fetchData()๋„ ์ž์‹ ๊ณผ ์ƒ๊ด€ ์—†๋Š” fetchConfig()๋ฅผ ๊ธฐ๋‹ค๋ฆฐ๋‹ค.

export async function GET(request: Request) {
  const session = await auth() // 1s
  const config = await fetchConfig() // 10s
  const data = await fetchData(session.user.id) // 10s
  return Response.json({ data, config })
  
  // total 21s
}

Correct

export async function GET(request: Request) {
  const sessionPromise = auth()
  const configPromise = fetchConfig()
  const session = await sessionPromise // 1s
  const [config, data] = await Promise.all([
    configPromise, // 10s
    fetchData(session.user.id) // 10s
  ])
  return Response.json({ data, config })
}
// total 11s

4. Promise.all() for Independent Operations

์ค‘์š”๋„ : CRITICAL (2-10๋ฐฐ ์†๋„ ๊ฐœ์„ )

async ์ž‘์—…๋“ค ๋ผ๋ฆฌ ์˜์กดํ•˜๊ณ  ์žˆ์ง€ ์•Š์„๋•Œ(2๋ฒˆ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ ๋•Œ)๋Š” Promise.all()์„ ์‚ฌ์šฉํ•œ๋‹ค.

// Incorrect
const user = await fetchUser()
const posts = await fetchPosts()
const comments = await fetchComments()

// Correct
const [user, posts, comments] = await Promise.all([
  fetchUser(),
  fetchPosts(),
  fetchComments()
])

5. Strategic Suspense Boundaries

์ค‘์š”๋„ : HIGH

Suspense๋žฉํผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋Ÿฌ๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ์€ ๋ฏธ๋ฆฌ ์ •์˜ํ•ด๋‘” Skeleton ์ด ๋ณด์ด๊ฒŒ ๋˜๊ณ  ๋” ๋น ๋ฅธ ์ดˆ๊ธฐ ํŽ˜์ธํŒ…์„ ํ†ตํ•ด ํ›จ์”ฌ ๋‚˜์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•œ๋‹ค.

Incorrect

async function Page() {
  const data = await fetchData() // Blocks entire page
  
  return (
    <div>
      <div>Sidebar</div>
      <div>Header</div>
      <div>
        <DataDisplay data={data} />
      </div>
      <div>Footer</div>
    </div>
  )
}

Correct

function Page() {
  return (
    <div>
      <div>Sidebar</div>
      <div>Header</div>
      <div>
        <Suspense fallback={<Skeleton />}>
          <DataDisplay />
        </Suspense>
      </div>
      <div>Footer</div>
    </div>
  )
}

async function DataDisplay() {
  const data = await fetchData() // Only blocks this component
  return <div>{data.content}</div>
}

ํ•˜์ง€๋งŒ ๋ฌด์ž‘์ • ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋˜๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” Suspense ์‚ฌ์šฉ์„ ํ•˜๋ฉด ์•ˆ๋œ๋‹ค.

  • ๋ ˆ์ด์•„์›ƒ์„ ๊ฒฐ์ •ํ•  ๋•Œ ๊ผญ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ์ธ ๊ฒฝ์šฐ.
  • SEO์— ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ์ธ ๊ฒฝ์šฐ.
  • ๋ฐ์ดํ„ฐ ํฌ๊ธฐ๊ฐ€ ์ž‘์•„ ๊ตณ์ด ๋น„์šฉ์„ ์†Œ๋ชจํ•ด๊ฐ€๋ฉฐ ์Šค์ผˆ๋ ˆํ†ค์„ ๋ณด์—ฌ์ค„ ํ•„์š”๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ.
  • ์ˆœ๊ฐ„ ํŠ€๋Š” ๋ ˆ์ด์•„์›ƒ ๋ณ€๋™ layout shift, content jumping์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ : Trade-off - ๋น ๋ฅธ ํŽ˜์ด์ง€ ๋กœ๋”ฉ์„ ์ฑ™๊ธฐ์ง€๋งŒ potential layout shift๋ฅผ ๊ฐ์ˆ˜ํ• ๊ฒƒ์ธ๊ฐ€ ์•„๋‹ˆ๋ฉด ๋А๋ฆฌ๋”๋ผ๋„ ์•ˆ์ •์ ์ธ Ui๋ฅผ ๋งŒ๋“ค๊ฒƒ์ธ๊ฐ€

[!faq] ๊ฒฐํ•ฉ๋„ ๋ฌธ์ œ ์œ„์™€ ๊ฐ™์€ ์„ค๊ณ„๋Œ€๋กœ๋ฉด DataDisplay ์ปดํฌ๋„ŒํŠธ์˜ ํ…Œ์ŠคํŠธ ๋ณต์žก๋„๊ฐ€ ์˜ฌ๋ผ๊ฐ€์ง€ ์•Š์„๊นŒ?