๋ฐ์ดํฐ ์์ฒญ์ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ Waterfall ๋ชจ๋ธ์ ๋ถํ์ํ ๋คํธ์ํฌ ์ง์ฐ์ ์ ๋ฐํ์ฌ ๋ก๋ฉ ์๊ฐ์ ์ฆ๊ฐ์ํค๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ํดํฉ๋๋ค. Promise.all์ ํตํ ๋ณ๋ ฌ ์ฒ๋ฆฌ, ๋ฐฑ์๋ Aggregator API/GraphQL ํ์ฉ, ๊ทธ๋ฆฌ๊ณ SSR/SSG์ ๊ฐ์ ์ด๊ธฐ ๋ ๋๋ง ์ ๋ต์ ํตํด ํจ์จ์ ์ธ ๋ฐ์ดํฐ ๋ก๋ฉ์ ๊ตฌํํ์ฌ ์น ์ฑ๋ฅ์ ์ต์ ํํด์ผ ํฉ๋๋ค.
๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ณผ์ ์์ ์ด์ ์์ฒญ์ด ์๋ฃ๋์ด์ผ ๋ค์ ์์ฒญ์ ์์ํ๋ ์ข ์์ ์ธ ๊ตฌ์กฐ(Waterfall Model)๋ ๋ถํ์ํ ์ง์ฐ์ ๋ฐ์์์ผ ๋ก๋ฉ ์๊ฐ์ ๋๋ฆฌ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ํดํฉ๋๋ค. ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ๋ฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ผ ํ ๋, ๊ฐ ๋ฐ์ดํฐ ์์ฒญ์ด ์์ฐจ์ ์ผ๋ก ์ด๋ฃจ์ด์ง๋ฉด ์ ์ฒด ์๋ต ์๊ฐ์ด ๋ชจ๋ ์์ฒญ์ ํฉ์ผ๋ก ๋์ด๋ฉ๋๋ค. ์ด๋ ํนํ ๋คํธ์ํฌ ์ง์ฐ ์๊ฐ(Latency)์ด ๋์ ํ๊ฒฝ์์ ์ฌ์ฉ์์๊ฒ ๊ธด ๋๊ธฐ ์๊ฐ์ ์ ๋ฐํ๋ฉฐ, ์ด๋ ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํฉ๋๋ค:
๋ฐ์ดํฐ ์์ฒญ ์ ๋ถํ์ํ ์ง๋ ฌํ๋ฅผ ํผํ๊ณ , ๊ฐ๋ฅํ ํ ๋ง์ ์์ฒญ์ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ์ฌ ์ ์ฒด ๋ก๋ฉ ์๊ฐ์ ๋จ์ถํด์ผ ํฉ๋๋ค. ๋ค์์ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๊ตฌ์ฒด์ ์ธ ๋ฐฉ๋ฒ๋ค์ ๋๋ค:
Promise.all ๋๋ Promise.allSettled๋ฅผ ์ฌ์ฉํ์ฌ ๋์์ ์ฌ๋ฌ ์์ฒญ์ ์์ํ๊ณ ๋ชจ๋ ์์ฒญ์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. ์ด๋ ๊ฐ๋ณ ์์ฒญ์ ์ง์ฐ ์๊ฐ์ ์จ๊ฒจ ์ ์ฒด ๋ก๋ฉ ์๊ฐ์ ํฌ๊ฒ ๋จ์ถ์ํต๋๋ค.
Promise.all: ๋ชจ๋ Promise๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋์ด์ผ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค. ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ํ๊บผ๋ฒ์ ํ์ํ๊ณ , ์ด๋ ํ๋๋ผ๋ ์คํจํ๋ฉด ์ ๋๋ ๊ฒฝ์ฐ์ ์ ํฉํฉ๋๋ค.Promise.allSettled: ๋ชจ๋ Promise์ ์ฑ๊ณต/์คํจ ์ฌ๋ถ์ ๊ด๊ณ์์ด ๋ชจ๋ Promise๊ฐ ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. ๊ฐ๋ณ ์์ฒญ์ ์ฑ๊ณต ์ฌ๋ถ๊ฐ ์ ์ฒด ํ๋ก์ธ์ค๋ฅผ ๋ง์ง ์์์ผ ํ ๋ ์ ์ฉํ๋ฉฐ, ๊ฐ ์์ฒญ์ ์ํ๋ฅผ ์ ์ ์์ต๋๋ค.<link rel="preload">, <link rel="preconnect">): HTML์ <head> ํ๊ทธ ๋ด์ preload๋ preconnect ํํธ๋ฅผ ์ฌ์ฉํ์ฌ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ฏธ๋ฆฌ ํ์ํ ๋ฆฌ์์ค(์: ํน์ API ์๋ํฌ์ธํธ์ ๋ํ ์ฐ๊ฒฐ)๋ฅผ ๋ก๋ํ๊ฑฐ๋ ์ฐ๊ฒฐ์ ์์ํ๋๋ก ์ง์ํฉ๋๋ค. ์ด๋ ์ค์ ์์ฒญ ์์ ๋๊ธฐ ์๊ฐ์ ์ค์ฌ์ค๋๋ค.// 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: 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๋จ๊ณ ์์ฒญ)๋ง ์์