IT Knowledge/Development/images/성능-최적화-diagram.svg
📌 핵심 개념
쉬운 비유: 패스트푸드점 주문. 빨리 받으려면:
- 주방이 빨라야 함 (서버 성능)
- 줄이 짧아야 함 (네트워크)
- 자주 주문하는 메뉴는 미리 준비 (캐싱)
AI 요약 보고서
- 웹 성능 최적화를 위한 핵심 전략과 구체적 방법론을 제시한다.
- 프론트엔드 최적화에서는 이미지 압축(WebP 포맷, 반응형 이미지, Lazy Loading), 코드 스플리팅(라우트별 번들 분리), 메모이제이션을 통한 재계산 방지 등을 강조한다.
- 백엔드에서는 데이터베이스 쿼리 최적화(JOIN 활용), 캐싱(Redis 활용), CDN 도입이 성능 향상에 중요하다고 설명한다.
- 특히, N+1 문제 해결과 캐시 전략을 통해 쿼리 수와 응답 시간을 크게 단축할 수 있으며, CDN 활용으로 전송 속도를 개선
- 성능 측정 지표로는 웹 Vitals(LCP, FID, CLS)를 제시하며, 응답 시간 모니터링을 통해 병목 구간을 파악하는 방법도 포함
- 전반적으로 이미지 최적화, 코드 분할, 캐싱, 데이터베이스 쿼리 최적화, CDN 활용 등 다각적 접근이 필요하며, 이를 위한 체크리스트와 참고 자료도 제공한다. 이러한 전략들은 웹사이트의 로드 속도와 사용자 경험을 극대화하는 데 필수적이며, 실무 적용을 위한 구체적 예제와 권장 사항을 포함한다.
🎯 프론트엔드 최적화
1. 이미지 최적화
쉬운 비유: 전화로 사진 공유할 때 원본(10MB)이 아닌 압축본(100KB)을 보내기. 화질은 비슷한데 훨씬 빠릅니다.
Before:
<!-- ❌ 5MB 원본 이미지 -->
<img src="/images/product.jpg" />After:
<!-- ✅ 최적화 -->
<!-- 1. WebP 포맷 (50% 더 작음) -->
<picture>
<source srcset="/images/product.webp" type="image/webp" />
<source srcset="/images/product.jpg" type="image/jpeg" />
<img src="/images/product.jpg" alt="상품" />
</picture>
<!-- 2. 반응형 이미지 -->
<img
srcset="
/images/product-small.jpg 400w,
/images/product-medium.jpg 800w,
/images/product-large.jpg 1200w
"
sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"
src="/images/product-medium.jpg"
alt="상품"
/>
<!-- 3. Lazy Loading -->
<img src="/images/product.jpg" loading="lazy" />2. 코드 스플리팅
쉬운 비유: 책 전체를 다운로드하지 않고 필요한 챕터만 먼저 받기.
Before:
// ❌ 모든 컴포넌트 한 번에 로드 (번들 크기: 2MB)
import Dashboard from './Dashboard';
import Settings from './Settings';
import Analytics from './Analytics';
function App() {
return (
<Router>
<Route path="/dashboard" component={Dashboard} />
<Route path="/settings" component={Settings} />
<Route path="/analytics" component={Analytics} />
</Router>
);
}After:
// ✅ 라우트별 코드 스플리팅
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
const Analytics = lazy(() => import('./Analytics'));
function App() {
return (
<Router>
<Suspense fallback={<Loading />}>
<Route path="/dashboard" component={Dashboard} /> {/* 이 페이지만 로드 */}
<Route path="/settings" component={Settings} />
<Route path="/analytics" component={Analytics} />
</Suspense>
</Router>
);
}
// 초기 로딩: 500KB → 나머지는 필요할 때3. 메모이제이션
쉬운 비유: 계산기로 “123 × 456” 계산 결과를 메모장에 적어두기. 다음에 같은 계산하면 메모 보고 바로 답변.
Before:
// ❌ 매번 재계산
function ProductList({ products }) {
const expensiveProducts = products.filter(p => p.price > 100000); // 매 렌더링마다 실행
return (
<div>
{expensiveProducts.map(p => <ProductCard key={p.id} product={p} />)}
</div>
);
}After:
import { useMemo, memo } from 'react';
// ✅ 결과 캐싱
function ProductList({ products }) {
const expensiveProducts = useMemo(
() => products.filter(p => p.price > 100000),
[products] // products가 바뀔 때만 재계산
);
return (
<div>
{expensiveProducts.map(p => (
<MemoizedProductCard key={p.id} product={p} />
))}
</div>
);
}
// ProductCard도 메모이제이션
const MemoizedProductCard = memo(function ProductCard({ product }) {
return (
<div>
<h3>{product.name}</h3>
<p>{product.price}원</p>
</div>
);
});🔧 백엔드 최적화
1. 데이터베이스 쿼리 최적화
쉬운 비유: 슈퍼마켓에서 물건 하나씩 10번 사는 것(N+1 문제) vs 장바구니에 담아 한 번에 계산(JOIN).
Before:
// ❌ N+1 문제
app.get('/posts', async (req, res) => {
const posts = await db.posts.findAll(); // 쿼리 1번
// 각 게시글마다 작성자 조회 (쿼리 100번)
for (const post of posts) {
post.author = await db.users.findById(post.authorId);
}
res.json(posts);
});
// 총 쿼리: 101번 (1 + 100)After:
// ✅ JOIN으로 한 번에
app.get('/posts', async (req, res) => {
const posts = await db.query(`
SELECT
posts.*,
users.name as author_name,
users.email as author_email
FROM posts
JOIN users ON posts.author_id = users.id
`);
res.json(posts);
});
// 총 쿼리: 1번 (100배 빠름!)2. 캐싱
쉬운 비유: 자주 묻는 질문(FAQ)을 게시판에 올려두기. 매번 직원에게 물어보지 않아도 됨.
Before:
// ❌ 매번 DB 조회
app.get('/api/popular-products', async (req, res) => {
const products = await db.query(`
SELECT * FROM products
ORDER BY sales_count DESC
LIMIT 10
`);
res.json(products);
});After:
const redis = require('redis');
const client = redis.createClient();
// ✅ Redis 캐싱
app.get('/api/popular-products', async (req, res) => {
const cacheKey = 'popular_products';
// 1. 캐시 확인
const cached = await client.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached)); // 즉시 응답 (매우 빠름)
}
// 2. 캐시 없으면 DB 조회
const products = await db.query(`
SELECT * FROM products
ORDER BY sales_count DESC
LIMIT 10
`);
// 3. 캐시 저장 (10분)
await client.setex(cacheKey, 600, JSON.stringify(products));
res.json(products);
});
// 첫 요청: 500ms
// 이후 요청: 5ms (100배 빠름!)3. CDN 활용
쉬운 비유: 서울에 있는 서버에서 부산 사용자가 파일 받으면 느림. 부산에도 복사본 두면 빠름.
// ❌ 서버에서 직접 제공
app.get('/images/:filename', (req, res) => {
res.sendFile(`/uploads/${req.params.filename}`);
});
// ✅ CDN 사용
// S3 + CloudFront
const imageUrl = `https://cdn.myapp.com/images/${filename}`;
// 효과:
// - 서울→부산: 100ms → 10ms
// - 서버 부하 감소📊 성능 측정
웹 Vitals (중요 지표)
// 1. LCP (Largest Contentful Paint)
// 가장 큰 콘텐츠 표시 시간
// 목표: 2.5초 이내
// 2. FID (First Input Delay)
// 첫 입력 반응 시간
// 목표: 100ms 이내
// 3. CLS (Cumulative Layout Shift)
// 레이아웃 이동
// 목표: 0.1 이하
// 측정
import { getCLS, getFID, getLCP } from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);백엔드 성능
// 응답 시간 측정
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url}: ${duration}ms`);
if (duration > 1000) {
console.warn('Slow request detected!');
}
});
next();
});💡 성능 체크리스트
프론트엔드
- 이미지 최적화 (WebP, 압축)
- Lazy Loading
- 코드 스플리팅
- 번들 크기 확인 (webpack-bundle-analyzer)
- React.memo, useMemo 사용
백엔드
- 데이터베이스 인덱스
- N+1 쿼리 제거
- Redis 캐싱
- gzip 압축
- CDN 사용
공통
- HTTPS/2 사용
- 불필요한 라이브러리 제거
- 프로덕션 빌드 사용