프랙티스 실전 가이드
프랙티스의 핵심 개념과 실무 활용
학습 항목
이미지 로딩 중...
Caching 베스트 프랙티스 완벽 가이드
애플리케이션 성능을 극대적으로 향상시키는 캐싱 전략과 구현 방법을 다룹니다. 메모리 캐시, HTTP 캐시, 분산 캐시 등 실무에서 바로 적용 가능한 패턴을 학습합니다.
들어가며
이 글에서는 Caching 베스트 프랙티스 완벽 가이드에 대해 상세히 알아보겠습니다. 총 10가지 주요 개념을 다루며, 각각의 개념에 대한 설명과 실제 코드 예제를 함께 제공합니다.
목차
1. 메모리_캐시_구현
개요
간단한 인메모리 캐시를 구현하여 반복적인 연산 결과를 저장하고 재사용합니다. TTL(Time To Live)을 설정하여 캐시 만료를 관리합니다.
코드 예제
class MemoryCache {
constructor() {
this.cache = new Map();
}
set(key, value, ttl = 60000) {
const expiry = Date.now() + ttl;
this.cache.set(key, { value, expiry });
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
this.cache.delete(key);
return null;
}
return item.value;
}
}
설명
Map을 사용하여 키-값 쌍을 저장하고, 각 항목에 만료 시간을 설정합니다. 조회 시 만료 여부를 확인하여 유효한 데이터만 반환합니다.
2. LRU_캐시_전략
개요
LRU(Least Recently Used) 알고리즘으로 캐시 크기를 제한하고 가장 오래 사용되지 않은 항목을 자동으로 제거합니다.
코드 예제
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return null;
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key, value) {
this.cache.delete(key);
this.cache.set(key, value);
if (this.cache.size > this.capacity) {
this.cache.delete(this.cache.keys().next().value);
}
}
}
설명
Map의 삽입 순서 특성을 활용하여 최근 사용된 항목을 뒤로 이동시킵니다. 용량 초과 시 가장 앞의 항목(가장 오래된 항목)을 제거합니다.
3. 함수_메모이제이션
개요
함수의 결과를 캐싱하여 동일한 인자로 호출 시 재계산 없이 저장된 결과를 반환합니다. 비용이 큰 연산에 효과적입니다.
코드 예제
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('캐시에서 반환');
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const expensiveCalc = memoize((n) => {
return n * n;
});
설명
함수 인자를 문자열로 변환하여 캐시 키로 사용합니다. 동일한 인자로 호출 시 이전 결과를 즉시 반환하여 성능을 개선합니다.
4. HTTP_캐시_헤더
개요
HTTP 응답 헤더를 설정하여 브라우저와 CDN에서 리소스를 캐싱합니다. 불필요한 네트워크 요청을 줄입니다.
코드 예제
const express = require('express');
const app = express();
app.get('/api/data', (req, res) => {
res.set({
'Cache-Control': 'public, max-age=3600',
'ETag': '"abc123"',
'Last-Modified': new Date().toUTCString()
});
res.json({ data: 'cached response' });
});
설명
Cache-Control로 캐시 유효 기간을 설정하고, ETag와 Last-Modified로 조건부 요청을 가능하게 합니다. 클라이언트는 변경되지 않은 리소스를 재사용합니다.
5. Redis_캐시_구현
개요
Redis를 사용한 분산 캐시 시스템으로 여러 서버 간 캐시를 공유합니다. 확장성이 뛰어난 캐싱 솔루션입니다.
코드 예제
const redis = require('redis');
const client = redis.createClient();
async function getCachedData(key) {
const cached = await client.get(key);
if (cached) return JSON.parse(cached);
const data = await fetchFromDB(key);
await client.setEx(key, 3600, JSON.stringify(data));
return data;
}
async function fetchFromDB(key) {
return { id: key, name: 'User Data' };
}
설명
Redis에서 데이터를 먼저 조회하고, 없으면 DB에서 가져와 Redis에 저장합니다. setEx로 만료 시간을 함께 설정하여 자동 정리됩니다.
6. 캐시_무효화_전략
개요
데이터 변경 시 관련 캐시를 제거하여 최신 데이터를 보장합니다. 캐시 일관성을 유지하는 핵심 패턴입니다.
코드 예제
class CacheManager {
constructor() {
this.cache = new Map();
}
async getData(id) {
if (this.cache.has(id)) return this.cache.get(id);
const data = await fetchData(id);
this.cache.set(id, data);
return data;
}
async updateData(id, newData) {
await saveData(id, newData);
this.cache.delete(id);
}
}
설명
데이터 업데이트 시 해당 캐시를 명시적으로 제거합니다. 다음 조회 시 최신 데이터를 가져와 캐시를 새로 갱신합니다.
7. 캐시_워밍업
개요
애플리케이션 시작 시 자주 사용되는 데이터를 미리 캐시에 로드합니다. 초기 요청의 응답 속도를 개선합니다.
코드 예제
class CacheWarmer {
constructor(cache) {
this.cache = cache;
}
async warmUp(keys) {
console.log('캐시 워밍업 시작...');
const promises = keys.map(async (key) => {
const data = await fetchData(key);
this.cache.set(key, data);
});
await Promise.all(promises);
console.log('캐시 워밍업 완료');
}
}
설명
애플리케이션 시작 시 중요한 데이터를 병렬로 조회하여 캐시에 미리 저장합니다. 사용자의 첫 요청부터 빠른 응답이 가능합니다.
8. 캐시_관통_방지
개요
존재하지 않는 데이터 요청으로 인한 반복적인 DB 조회를 방지합니다. null 값도 캐싱하여 불필요한 부하를 줄입니다.
코드 예제
async function safeGetData(key) {
const cached = cache.get(key);
if (cached !== undefined) {
return cached === null ? null : cached;
}
const data = await fetchFromDB(key);
if (data === null) {
cache.set(key, null, 300000);
return null;
}
cache.set(key, data);
return data;
}
설명
데이터가 없는 경우에도 null을 캐싱하여 동일한 요청이 반복되지 않도록 합니다. 짧은 TTL을 설정하여 나중에 추가될 수 있는 데이터를 고려합니다.
9. 다층_캐시_전략
개요
메모리 캐시와 분산 캐시를 함께 사용하는 2단계 캐싱 전략입니다. 빠른 로컬 캐시와 공유 가능한 분산 캐시의 장점을 결합합니다.
코드 예제
class TieredCache {
constructor(localCache, redisCache) {
this.local = localCache;
this.redis = redisCache;
}
async get(key) {
let value = this.local.get(key);
if (value) return value;
value = await this.redis.get(key);
if (value) {
this.local.set(key, value);
return value;
}
return null;
}
}
설명
로컬 캐시를 먼저 확인하고, 없으면 Redis를 조회합니다. Redis에서 찾은 데이터는 로컬 캐시에도 저장하여 다음 접근을 더 빠르게 합니다.
10. 캐시_압축_저장
개요
큰 데이터를 압축하여 저장함으로써 메모리 사용량을 줄입니다. 저장 공간과 네트워크 대역폭을 절약합니다.
코드 예제
const zlib = require('zlib');
const { promisify } = require('util');
const gzip = promisify(zlib.gzip);
const gunzip = promisify(zlib.gunzip);
async function setCached(key, data) {
const json = JSON.stringify(data);
const compressed = await gzip(json);
cache.set(key, compressed);
}
async function getCached(key) {
const compressed = cache.get(key);
if (!compressed) return null;
const json = await gunzip(compressed);
return JSON.parse(json.toString());
}
설명
JSON 문자열을 gzip으로 압축하여 저장하고, 조회 시 압축을 해제합니다. 큰 객체나 배열을 캐싱할 때 메모리를 효율적으로 사용합니다.
마치며
이번 글에서는 Caching 베스트 프랙티스 완벽 가이드에 대해 알아보았습니다. 총 10가지 개념을 다루었으며, 각각의 사용법과 예제를 살펴보았습니다.
관련 태그
#JavaScript #Caching #Performance #Optimization #Redis