캐시 메모리(Cache Memory)
- 캐시 메모리(이하 캐시) : 데이터를 임시로 저장하는
물리적 혹은 논리적 장소
어플리케이션(application)
의 경우 사용자들이 서버(server)
에서 받는 요청에 따라 데이터베이스(database)
에 접근하기 때문에, 사용자가 많아질 수록 발생하는 부하를 줄이기 위해 사용
메모리 상 유지
되는 것으로, 서버가 종료될 시 캐시 메모리에 저장되어 있는 데이터도 휘발
되는 특징이 있음
- 캐시 적중(cache hit) :
액세스하려는 데이터가 캐시에 적재
되어 있는 상태
- 캐시 미스(cache miss) :
액세스하려는 데이터가 캐시에 없어
기억장치(Database)에서 가져와야하는 상태
- 캐시 적중률(cache hit rate) :
원하는 데이터가 캐시에 있을 확률
- 캐시 적중 횟수 / 전체 기억장치 액세스 횟수
- 캐시 적중률은 데이터의 지역성에 크게 의존함
지역성(locality)
- 지역성 :
기억장치 안의 특정 부분을 빈번하게 또는 집중적으로 액세스
하는 현상
- 시간적 지역성(temporal locality) : 최근 액세스된 데이터가 가까운 미래에 다시 액세스 될 가능성이 높아지는 특성
- 공간적 지역성(spatial locality) : 기억장치 내 서로 인접하여 저장되어 있는 데이터들이 연속적으로 액세스될 가능성이 높아지는 특성
캐싱 전략
읽기 전략
- Look Aside(Lazy Loading) : 데이터를 찾을 때 캐시에 데이터가 존재하는 지 확인① 하고 없을 경우 데이터베이스로 확인해② 가져온 데이터를 캐시에 저장③ 하는 전략
- 장점 : 캐시에 장애가 어플리케이션에 치명적인 영향을 주지 않음
- 단점 : 캐시에 없는 데이터를 쿼리할 때 오랜 시간이 걸릴 수 있으며, 최신 데이터를 가지고 있다는 것을 보장할 수 없음
- Read Through : 캐시에서만 데이터를 읽어오는 전략① 으로, 없는 경우 캐시가 데이터베이스로부터 데이터를 가져오는② 전략
- 장점 : 캐시-데이터베이스 간 데이터 정합성을 보장
- 단점 : 데이터 조회에 전체적인 속도가 느리며, 캐시가 죽어있을 경우, 어플리케이션에 문제가 발생
쓰기 전략
- Write Around : 모든 데이터를 데이터베이스에 저장
- 장점 : 캐시 쓰기 성능이 빠름
- 단점 : 캐시-데이터베이스 간 데이터 정합성 유지 어려움
- Write Through : 데이터를 캐시에 먼저 저장하고① 바로 데이터베이스에 저장하는② 전략
- 장점 : 자주 사용하지 않는 리소스에 대한 쓰기 횟수 비용을 줄일 수 있음
- 단점 : 두 번의 쓰기로 인해 서비스에서 성능 이슈가 발생할 수 있음
- Write Back : 데이터를 캐시에 모아두었다가① 일정 주기마다 데이터베이스에 저장하는② 전략
- 장점 : 자주 사용하지 않는 리소스에 대한 쓰기 횟수 비용을 줄일 수 있음
- 단점 : 캐시에서 오류 등으로 인한 데이터가 유실될 수 있음
캐싱 전략의 조합
- Look Aside + Write Around : 일반적으로 자주 사용되는 조합
- Read Through + Write Around : 항상 데이터베이스에 쓰고 캐시에서 읽을 때 데이터베이스에서 먼저 읽어오기 때문에 데이터 정합성 문제를 대비할 수 있음
- Read Through + Write Through : 데이터를 쓸 때 항상 캐시에 먼저 쓰고 데이터베이스로 보내기 때문에 캐시에서 읽을 때 데이터의 최신 상태와 캐시-데이터베이스 간 데이터 정합성을 보장
REDIS(REmote DIctionary Server)
- Redis : 오픈 소스, 인 메모리 키-값 데이터 관리 서버, 데이터베이스, 캐시, 메시지 브로커(message broker) 등으로 사용합니다.
키-값(key-value)
로 저장하는 비관계형 구조 데이터베이스(NoSQL)
- 쿼리 연산은 없지만
데이터 고속 읽기와 쓰기에 최적화
되어있음
메모리(dram)
에서 데이터를 처리하기 때문에 작업 속도가 빠름
NoSQL(not only SQL)
: 전통적인 관계형 데이터베이스보다 덜 제한적인 일관성 모델을 이용하는 데이터의 저장 및 검색을 위한 매커니즘을 제공하는 데이터베이스
NestJS에서 Cache Module 사용
cache-manager
및 io-redis store
관련 모듈 설치
npm install @nestjs/cache-manager cache-manager
npm install cache-manager-ioredis
npm install -D @types/cache-manager @types/cache-manager-ioredis
import { CacheModule } from '@nestjs/cache-manager';
import * as redisStore from 'cache-manager-ioredis';
@Module({
imports: [
CacheModule.register({
isGlobal: true,
store: redisStore,
host: 'localhost',
port: 6379,
}),
...
],
providers: [],
})
export class AppModule {}
캐시 데이터 쓰기
(this.cacheManager.set('키 이름', 저장 할 데이터)
)
@Injectable()
export class ExternalApiLib {
constructor(
private readonly configService: ConfigService,
@Inject(CACHE_MANAGER) private cacheManager: Cache,
) {}
...
async updateCities() {
const filePath = path.join(__dirname, '../../../csv', 'sgg_lat_lon.csv');
const data = [];
fs.createReadStream(filePath)
.on('error', (error) => {
Logger.error(error);
})
.pipe(csv())
.on('data', (row) => {
data.push({
city: row['do-si'],
district: row['sgg'],
latitude: row['lat'],
longitude: row['lon'],
});
})
.on('end', () => {
this.cacheManager.set(`city`, data); // cacheManager 안에 `city`키 이름으로 데이터 저장
});
}
}
캐시 데이터 읽기
(this.cacheManager.get('키 이름')
)
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
@Injectable()
export class CityService {
constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache
) {}
/** 시군구 조회
* @return 시군구 목록 */
async getCities() {
// cacheManager 안에 `city`키 이름의 데이터 불러오기
const cachedData = await this.cacheManager.get(`city`);
...
return cachedData;
}
}
관련 Repository
참고 자료
Ghost