# NestJS/Typescript : 캐시 메모리와 레디스(Redis)

- Author: @laetipark
- Published: 2024-02-19
- Updated: 2024-02-19
- Source: http://blex.me/@laetipark/nestjstypescript-%EC%BA%90%EC%8B%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%99%80-%EB%A0%88%EB%94%94%EC%8A%A4redis
- Tags: nodejs, nestjs, typescript, cache, redis

---

## 캐시 메모리(Cache Memory)

![](https://static.blex.me/images/content/2024/2/19/202421916_8SxCRYKYhjyLnhYNBol9.jpg)
- **캐시 메모리(이하 캐시)** : 데이터를 임시로 저장하는 `물리적 혹은 논리적 장소`
	- `어플리케이션(application)`의 경우 사용자들이 `서버(server)`에서 받는 요청에 따라 `데이터베이스(database)`에 접근하기 때문에, 사용자가 많아질 수록 발생하는 부하를 줄이기 위해 사용
	- `메모리 상 유지`되는 것으로, 서버가 종료될 시 캐시 메모리에 저장되어 있는 데이터도 `휘발`되는 특징이 있음
- **캐시 적중(cache hit)** : `액세스하려는 데이터가 캐시에 적재`되어 있는 상태
- **캐시 미스(cache miss)** : `액세스하려는 데이터가 캐시에 없어` 기억장치(Database)에서 가져와야하는 상태
- **캐시 적중률(cache hit rate)** : `원하는 데이터가 캐시에 있을 확률`
	- 캐시 적중 횟수 / 전체 기억장치 액세스 횟수
	- 캐시 적중률은 데이터의 지역성에 크게 의존함
	
### 지역성(locality)
- **지역성** : `기억장치 안의 특정 부분을 빈번하게 또는 집중적으로 액세스`하는 현상
	- **시간적 지역성(temporal locality)** : 최근 액세스된 데이터가 가까운 미래에 다시 액세스 될 가능성이 높아지는 특성
	- **공간적 지역성(spatial locality)** : 기억장치 내 서로 인접하여 저장되어 있는 데이터들이 연속적으로 액세스될 가능성이 높아지는 특성

### 캐싱 전략
##### 읽기 전략

![](https://static.blex.me/images/content/2024/2/19/202421916_3SCyiVAWDt88GkoWfG11.png)
- **Look Aside(Lazy Loading)** : 데이터를 찾을 때 캐시에 데이터가 존재하는 지 확인① 하고 없을 경우 데이터베이스로 확인해② 가져온 데이터를 캐시에 저장③ 하는 전략
	- 장점 : 캐시에 장애가 어플리케이션에 치명적인 영향을 주지 않음
	- 단점 : 캐시에 없는 데이터를 쿼리할 때 오랜 시간이 걸릴 수 있으며, 최신 데이터를 가지고 있다는 것을 보장할 수 없음

![](https://static.blex.me/images/content/2024/2/19/202421916_2jP4iFOP1H6YYYNFx0pD.png)
- **Read Through** : 캐시에서만 데이터를 읽어오는 전략① 으로, 없는 경우 캐시가 데이터베이스로부터 데이터를 가져오는② 전략
	- 장점 : 캐시-데이터베이스 간 데이터 정합성을 보장
	- 단점 : 데이터 조회에 전체적인 속도가 느리며, 캐시가 죽어있을 경우, 어플리케이션에 문제가 발생


##### 쓰기 전략

![](https://static.blex.me/images/content/2024/2/19/202421916_BzQmeh7oWjBSLZxes5Y2.png)
- **Write Around** : 모든 데이터를 데이터베이스에 저장
	- 장점 : 캐시 쓰기 성능이 빠름
	- 단점 : 캐시-데이터베이스 간 데이터 정합성 유지 어려움

![](https://static.blex.me/images/content/2024/2/19/202421916_BWL075MTQBUuUzuod3xX.png)
- **Write Through** : 데이터를 캐시에 먼저 저장하고① 바로 데이터베이스에 저장하는② 전략
	- 장점 : 자주 사용하지 않는 리소스에 대한 쓰기 횟수 비용을 줄일 수 있음
	- 단점 : 두 번의 쓰기로 인해 서비스에서 성능 이슈가 발생할 수 있음

![](https://static.blex.me/images/content/2024/2/19/202421916_zNoVH7sV6gvlLi0yMZ5u.png)
- **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` 관련 모듈 설치
```shell
npm install @nestjs/cache-manager cache-manager
npm install cache-manager-ioredis
npm install -D @types/cache-manager @types/cache-manager-ioredis
```

- `cache-manager 모듈` import
```typescript
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('키 이름', 저장 할 데이터)`)
```typescript
@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('키 이름')`)
```typescript
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
- [[ 여기가자 ] 지리기반 맛집 추천 서비스 RESTful API 서버, laetipark, github](https://github.com/laetipark/lets-go-here)

## 참고 자료
- [[REDIS] 📚 레디스 소개 & 사용처 (캐시 / 세션), 인파, Tistory](https://inpa.tistory.com/entry/REDIS-%F0%9F%93%9A-%EA%B0%9C%EB%85%90-%EC%86%8C%EA%B0%9C-%EC%82%AC%EC%9A%A9%EC%B2%98-%EC%BA%90%EC%8B%9C-%EC%84%B8%EC%85%98-%ED%95%9C%EB%88%88%EC%97%90-%EC%8F%99-%EC%A0%95%EB%A6%AC)
- [Redis란 무엇입니까?, Amazon Web Services(AWS)](https://aws.amazon.com/ko/elasticache/what-is-redis/)
- [[10분 테코톡] 저문, 라온의 Cache & Redis, 우아한테크, Youtube](https://youtu.be/tVZ15cCRAyE?si=LVt-Ml44l6SwdTEa)
