캐싱이 중요한 이유 🤔
캐싱은 자주 접근하는 데이터를 빠르게 접근할 수 있는 임시 저장소에 보관하는 기술입니다. 데이터베이스 쿼리, API 호출, 파일 입출력 등 비용이 많이 드는 작업의 결과를 저장해두면
- ⚡ 응답 시간 대폭 단축
- 📉 데이터베이스 부하 감소
- 💰 서버 비용 절약
- 🌐 사용자 경험 향상
자, 이제 다양한 캐싱 전략에 대해 알아볼까요?
1. Cache Aside (Lazy Loading) 전략 😴
가장 흔히 사용되는 캐싱 패턴입니다. "필요할 때만 캐시에 데이터를 로드한다"는 개념이죠.
작동 방식:
- 데이터가 필요할 때 먼저 캐시를 확인 🔍
- 캐시에 있으면(캐시 히트) 바로 반환 ✅
- 캐시에 없으면(캐시 미스) 데이터베이스에서 조회 🔄
- 데이터베이스에서 가져온 데이터를 캐시에 저장하고 반환 💾
🍦 비유: 냉장고(캐시)에 아이스크림이 있는지 먼저 확인하고, 없으면 편의점(DB)에 가서 사 온 후 냉장고에 넣어두는 것과 같아요!
// Node.js에서 Cache Aside 패턴 예시
async function getUserData(userId) {
// 1. 캐시에서 먼저 확인
const cachedUser = await redisClient.get(`user:${userId}`);
if (cachedUser) {
return JSON.parse(cachedUser); // 캐시 히트!
}
// 2. 캐시에 없으면 DB에서 조회
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
// 3. DB에서 가져온 데이터를 캐시에 저장 (1시간 유효)
await redisClient.set(`user:${userId}`, JSON.stringify(user), 'EX', 3600);
return user;
}
장점
- 👍 실제 요청된 데이터만 캐시에 저장 (메모리 효율적)
- 👍 캐시가 다운되어도 서비스 계속 가능
- 👍 구현이 간단함
단점
- 👎 첫 요청은 항상 느림 (콜드 스타트 문제)
- 👎 데이터 불일치 가능성 있음
2. 캐시 불일치 해결을 위한 쓰기 전략 ✍️
데이터가 변경되면 캐시와 데이터베이스 간 불일치가 발생할 수 있습니다. 이를 해결하기 위한 전략들을 알아볼까요?
A. Write Through (동시 갱신) 전략 🔄
데이터를 변경할 때 데이터베이스와 캐시를 동시에 업데이트하는 전략입니다.
작동 방식
- 데이터 변경 요청이 들어옴 📝
- 데이터베이스 업데이트 ✅
- 캐시도 즉시 업데이트 ✅
🧁 비유: 레시피(데이터)를 수정할 때 요리책(DB)과 냉장고에 붙인 메모(캐시)를 동시에 업데이트하는 것과 같아요!
// Next.js API 라우트에서 Write Through 패턴 예시
export default async function updateUserHandler(req, res) {
const { userId, userData } = req.body;
// 1. DB 업데이트
await db.query('UPDATE users SET ? WHERE id = ?', [userData, userId]);
// 2. 캐시도 동시에 업데이트
await redisClient.set(`user:${userId}`, JSON.stringify(userData), 'EX', 3600);
res.status(200).json({ success: true });
}
장점
- 👍 캐시는 항상 최신 상태 유지
- 👍 읽기 작업이 빠름
단점
- 👎 쓰기 작업이 느림 (두 곳에 쓰기 때문)
- 👎 자주 변경되는 데이터에는 비효율적
B. Cache Invalidation (캐시 무효화) 전략 🗑️
데이터가 변경되면 해당 캐시를 삭제하는 전략입니다.
작동 방식
- 데이터 변경 요청이 들어옴 📝
- 데이터베이스 업데이트 ✅
- 관련 캐시 항목 삭제 🗑️
- 다음 요청 시 새로 캐시에 로드됨
🧼 비유: 물건이 변경되면 이전 사진(캐시)을 삭제하고, 필요할 때 새로 찍는 것과 같아요!
// Node.js에서 Cache Invalidation 패턴 예시
async function updateUserProfile(userId, newData) {
// 1. DB 업데이트
await db.query('UPDATE users SET ? WHERE id = ?', [newData, userId]);
// 2. 캐시 무효화 (삭제)
await redisClient.del(`user:${userId}`);
return { success: true };
}
장점:
- 👍 Write Through보다 간단하고 빠름
- 👍 불필요한 캐시 업데이트 없음
단점:
- 👎 캐시 미스 증가 가능성
- 👎 삭제 후 첫 요청은 느림
C. Write Behind (Write Back) 전략 📝
캐시를 먼저 업데이트하고, 나중에 데이터베이스에 반영하는 전략입니다.
작동 방식
- 데이터 변경 요청이 들어옴 📝
- 캐시 먼저 업데이트 ✅
- 나중에(일정 시간 후 또는 일괄) 데이터베이스에 반영 ⏱️
📝 비유: 메모장(캐시)에 먼저 기록하고, 나중에 한꺼번에 공식 문서(DB)에 정리하는 것과 같아요!
// Node.js에서 Write Behind 패턴 예시
async function updateSensorReading(sensorId, reading) {
// 1. 캐시 먼저 업데이트
await redisClient.set(`sensor:${sensorId}`, JSON.stringify(reading));
// 2. 쓰기 큐에 추가 (나중에 일괄 처리)
await writeQueue.add('updateSensor', { sensorId, reading });
return { success: true };
}
장점
- 👍 쓰기 작업이 매우 빠름
- 👍 데이터베이스 부하 분산
- 👍 빈번한 변경에 효율적 (DCIM 센서 데이터 등)
단점
- 👎 캐시 서버 장애 시 데이터 유실 위험
- 👎 구현이 복잡함
- 👎 일시적 데이터 불일치 발생
실제 적용 시나리오: DCIM/FMS 시스템 🏢
데이터센터 관리 시스템에서 각 전략의 활용 예
1. Cache Aside
- 서버/랙 상세 정보
- 사용자 프로필 데이터
- 자주 접근하지만 가끔 변경되는 설정값
2. Write Through
- 중요한 알람 설정
- 권한 정보
- 데이터 정확성이 중요한 자산 관리 정보
3. Cache Invalidation
- 사용자가 직접 수정한 데이터
- 서버 구성 변경 정보
- 주기적으로 업데이트되는 정보
4. Write Behind
- 센서 측정값 (온도, 전력 등)
- 로그 데이터
- 트래픽 통계
추가로 알면 좋은 캐싱 팁 💡
1. TTL(Time To Live) 설정 ⏱️
모든 캐시 데이터에 유효 기간을 설정하세요. 영원히 캐시되면 불일치 문제가 심각해집니다.
// Redis에서 TTL 설정 예시
await redisClient.set('key', 'value', 'EX', 3600); // 1시간 유효
2. 캐시 계층화 (Layered Caching) 🧅
여러 계층의 캐시를 사용하여 성능을 극대화할 수 있습니다:
- L1: 메모리 캐시 (Node.js 앱 내부)
- L2: Redis/Memcached (서버 간 공유)
- L3: CDN (정적 콘텐츠)
3. 캐시 프리페칭 (Prefetching) 🔮
사용자가 요청하기 전에 미리 캐시를 준비하는 전략입니다.
// Next.js에서 자주 접근하는 데이터 미리 캐싱
async function prefetchPopularData() {
const popularItems = await db.query('SELECT * FROM items ORDER BY views DESC LIMIT 20');
for (const item of popularItems) {
await redisClient.set(`item:${item.id}`, JSON.stringify(item), 'EX', 3600);
}
}
4. 캐시 버스팅 (Cache Busting) 💥
정적 리소스의 URL에 버전이나 해시를 추가하여 업데이트 시 새로운 버전을 강제로 로드합니다.
// Next.js에서 캐시 버스팅 예시
function MyApp() {
return (
<script src={`/js/main.js?v=${process.env.BUILD_ID}`}></script>
);
}
마무리 🎯
캐싱 전략은 서비스의 특성과 데이터 패턴에 맞게 선택해야 합니다. 읽기가 많은 서비스는 Cache Aside, 쓰기가 많은 서비스는 Write Behind, 정확성이 중요한 서비스는 Write Through가 적합할 수 있어요.
DCIM이나 FMS 시스템에서는 특히 센서 데이터와 같은 빈번한 업데이트가 있는 데이터는 Write Behind를, 중요한 구성 정보는 Write Through를 사용하는 하이브리드 접근이 효과적입니다!
올바른 캐싱 전략으로 여러분의 백엔드 시스템이 더 빠르고 안정적으로 동작하길 바랍니다! 😊
캐싱 전략에 대해서 설명해주세요.
캐시는 성능 향상과 부하 감소를 목표로 합니다. 이때 캐시를 사용하는 양상이 서비스에 큰 영향을 끼치기도 합니다. 따라서, 캐싱 전략을 이해하는 것은 중요합니다.
Cache Aside(Lazy Loading) 방식에 대해서 설명해주세요. 😀
Cache Aside 방식은 캐시 히트 시 캐시에서 데이터를 불러오며, 캐시 미스 발생 시 원본 데이터베이스에서 조회하여 반환합니다. 애플리케이션은 캐시 미스가 발생하면 해당 데이터를 캐시에 적재합니다.
해당 방식은 실제 요청된 데이터만 캐시에 저장되므로 불필요한 데이터 캐싱을 줄일 수 있습니다. 또한, 캐시에 문제가 발생해도 애플리케이션은 원본 데이터베이스에 직접 접근할 수 있기 때문에 서비스가 계속 작동할 수 있다는 장점이 있습니다. 하지만, 캐시 미스가 발생하는 경우에만 데이터를 캐시에 적재하기 때문에 원본 데이터베이스와 같은 데이터가 아닐 수도 있으며, 초기에는 대량의 캐시 미스로 인한 데이터베이스 부하가 발생할 수 있습니다.
캐시 불일치를 해소할 수 있는 쓰기 전략에 대해서 알고 계신가요? 🤔
캐시 불일치(Cache Inconsistency) 란 원본 데이터베이스에 저장된 데이터와 캐시에 저장된 데이터가 서로 다른 상황을 의미합니다. Write Through, Cache Invalidation, Write Behind 방식으로 이러한 캐시 불일치를 해소할 수 있습니다.
Write Through 방식은 원본 데이터에 대한 변경분이 생긴 경우, 매번 캐시에 해당 데이터를 찾아 함께 변경하는 방식입니다. 2번 쓰기가 발생하지만, 캐시는 항상 최신 데이터를 가지고 있습니다. 캐시는 다시 조회되는 경우에 빛을 발휘합니다. 무작정 데이터를 갱신하거나 저장하는 방식은 리소스 낭비가 될 수 있으니 해당 방식을 사용하는 경우, 만료 시간을 사용하는 것이 권장됩니다.
Cache Invalidation 방식은 원본 데이터에 대한 변경분이 생긴 경우, 캐시 데이터를 만료시키는 방식입니다. Write Through 방식의 단점을 보완한 방식이며 캐시에 데이터가 삭제되니 캐시 불일치에 대한 걱정을 하지 않아도 됩니다.
Write Behind(Write Back) 방식은 원본 데이터에 대한 변경분이 생긴 경우, 캐시를 먼저 업데이트한 이후 추후에 원본 데이터를 변경합니다. 디스크 쓰기 작업을 비동기 작업으로 수행하여 성능을 개선할 수 있습니다. 원본 데이터와 캐시가 일시적으로 일치하지 않을 수 있있기 때문에 쓰기 작업이 빈번하며 일시적인 캐시 불일치를 허용하는 서비스에서 유용하게 사용될 수 있습니다.
'1일 1CS(Computer Science)' 카테고리의 다른 글
REST란 무엇인가요? (0) | 2025.05.19 |
---|---|
CORS(Cross-Origin Resource Sharing)는 무엇이며 왜 필요한가요? (0) | 2025.05.19 |
Error Boundary가 무엇이며, 이를 사용하는 이유는 무엇인가요? (0) | 2025.05.19 |
동시성과 병렬성에 대해서 설명해주세요. (0) | 2025.05.19 |
로드 밸런싱에 대해서 설명해주세요. (0) | 2025.05.19 |