✅오늘학습 Keyword
2023.06.05 - [개념정리, 유용한팁] - Redis란 무엇인가? 윈도우에서 Redis 설치하기
이전 글에서 Redis의 개념, 기본 작동원리를 익히고 project에 적용해보기로 했다.
✅오늘 한 일
@Injectable()
export class StoresService {
constructor(
@InjectRepository(StoresRepository)
private storesRepository: StoresRepository,
private reviewsRepository: ReviewsRepository,
) {}
async searchRestaurants(
southWestLatitude: number,
southWestLongitude: number,
northEastLatitude: number,
northEastLongitude: number,
sortBy?: 'distance' | 'name' | 'waitingCnt' | 'waitingCnt2' | 'rating',
): Promise<{ 근처식당목록: Stores[] }> {
const restaurants = await this.storesRepository.findAll();
const restaurantsWithinRadius = restaurants.filter((restaurant) => {
const withinLatitudeRange =
Number(restaurant.La) >= southWestLatitude &&
Number(restaurant.La) <= northEastLatitude;
const withinLongitudeRange =
Number(restaurant.Ma) >= southWestLongitude &&
Number(restaurant.Ma) <= northEastLongitude;
// console.log(withinLatitudeRange, withinLongitudeRange);
return withinLatitudeRange && withinLongitudeRange;
});
// 거리 계산 로직
const calculateDistance = (
source: { latitude: number; longitude: number },
target: { latitude: number; longitude: number },
): number => {
const latDiff = Math.abs(source.latitude - target.latitude);
const lngDiff = Math.abs(source.longitude - target.longitude);
const approximateDistance = Math.floor(
latDiff * 111000 + lngDiff * 111000,
);
return approximateDistance;
};
//user위치에 따른 거리값을 모든 sort조건에 포함시켜준다
const userLocation = {
latitude: southWestLatitude,
longitude: southWestLongitude,
};
restaurantsWithinRadius.forEach((restaurant) => {
const distance = calculateDistance(userLocation, {
latitude: Number(restaurant.La),
longitude: Number(restaurant.Ma),
});
restaurant.distance = distance;
});
//정렬로직모음
restaurantsWithinRadius.sort((a, b) => {
if (sortBy === 'distance') {
return (a.distance || 0) - (b.distance || 0);
} else if (sortBy === 'name') {
const nameA = a.storeName.toUpperCase();
const nameB = b.storeName.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
} else if (sortBy === 'waitingCnt') {
return a.currentWaitingCnt - b.currentWaitingCnt;
} else if (sortBy === 'waitingCnt2') {
return b.currentWaitingCnt - a.currentWaitingCnt;
} else if (sortBy === 'rating') {
return b.rating - a.rating;
}
return 0;
});
return { 근처식당목록: restaurantsWithinRadius };
}
위의 식당 목록 조회 기능에 Redis를 적용하여 쿼리결과를 캐시에 저장하면, 같은 요청이 반복될 때 마다 db에 access 하는 대신 캐시에서 결과를 가져올 수 있다.
✅오늘 겪은 문제 및 해결
1. stores.service에서 getcurrentwaitingcnt 함수를 가져오려했다.
아래와같은 오류
[Nest] 21292 - 2023. 06. 07. 오후 7:23:13 ERROR [ExceptionHandler] Nest can't resolve dependencies of the WaitingsRepository (?). Please make sure that the argument WaitingsRepository at index [0] is available in the StoresModule context.
Potential solutions:
- Is StoresModule a valid NestJS module?
- If WaitingsRepository is a provider, is it part of the current StoresModule?
- If WaitingsRepository is exported from a separate @Module, is that module imported within StoresModule?
@Module({
imports: [ /* the Module containing WaitingsRepository */ ]
})
Error: Nest can't resolve dependencies of the WaitingsRepository (?). Please make sure that the argument WaitingsRepository at index [0] is available in the StoresModule context.
Potential solutions:
- Is StoresModule a valid NestJS module?
- If WaitingsRepository is a provider, is it part of the current StoresModule?
- If WaitingsRepository is exported from a separate @Module, is that module imported within StoresModule?
@Module({
imports: [ /* the Module containing WaitingsRepository */ ]
})
at Injector.lookupComponentInParentModules (C:\Users\cryst\OneDrive\바탕 화면\sparta\NODE JS\주특기주차\실전프로젝트\matwaiting\backend\backend-1\node_modules\@nestjs\core\injector\injector.js:247:19)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at Injector.resolveComponentInstance (C:\Users\cryst\OneDrive\바탕 화면\sparta\NODE JS\주특기주차\실전프로젝트\matwaiting\backend\backend-1\node_modules\@nestjs\core\injector\injector.js:200:33)
at resolveParam (C:\Users\cryst\OneDrive\바탕 화면\sparta\NODE JS\주특기주차\실전프로젝트\matwaiting\backend\backend-1\node_modules\@nestjs\core\injector\injector.js:120:38)
at async Promise.all (index 0)
at Injector.resolveConstructorParams (C:\Users\cryst\OneDrive\바탕 화면\sparta\NODE JS\주특기주차\실전프로젝트\matwaiting\backend\backend-1\node_modules\@nestjs\core\injector\injector.js:135:27)
at Injector.loadInstance (C:\Users\cryst\OneDrive\바탕 화면\sparta\NODE JS\주특기주차\실전프로젝트\matwaiting\backend\backend-1\node_modules\@nestjs\core\injector\injector.js:61:13)
at Injector.loadProvider (C:\Users\cryst\OneDrive\바탕 화면\sparta\NODE JS\주특기주차\실전프로젝트\matwaiting\backend\backend-1\node_modules\@nestjs\core\injector\injector.js:88:9)
at C:\Users\cryst\OneDrive\바탕 화면\sparta\NODE JS\주특기주차\실전프로젝트\matwaiting\backend\backend-1\node_modules\@nestjs\core\injector\instance-loader.js:56:13
at async Promise.all (index 9)
해결 stores.module에 의존성을 주입, 처음엔 waitingsrepository, waitings 테이블이 주입되어있지 않았음.
나는 waiting service의 api를 끌어오려한거라 repo는 추가 안해도되는줄 알았음.
nest.js에서 service와 repo에서 뭘 쓰든 module에 반드시!! 의존성을 주입해줘야한다는 것, 결과적으론 stores.module에 의존성을 주입해줬더니 해결됐다.
import { ElasticsearchModule } from '@nestjs/elasticsearch';
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { StoresController } from './stores.controller';
import { StoresService } from './stores.service';
import { Stores } from './stores.entity';
import { StoresRepository } from './stores.repository';
import { LocationService } from 'src/location/location.service';
import { Tables } from 'src/tables/tables.entity';
import { TablesRepository } from 'src/tables/tables.repository';
import { ReviewsRepository } from 'src/reviews/reviews.repository';
import { Reviews } from 'src/reviews/reviews.entity';
import * as dotenv from 'dotenv';
import { WaitingsService } from 'src/waitings/waitings.service';
import { BullModule } from '@nestjs/bull';
import { WaitingsRepository } from 'src/waitings/waitings.repository';
import { Waitings } from 'src/waitings/waitings.entity';
// import { RedisCacheModule } from 'src/cache/redis.module';
// import { RedisCacheService } from 'src/cache/redis.service';
dotenv.config();
@Module({
imports: [
BullModule.registerQueue({
name: 'waitingQueue',
}),
TypeOrmModule.forFeature([Stores, Tables, Reviews, Waitings]),
ElasticsearchModule.register({
node: 'http://localhost:9200',
maxRetries: 10,
requestTimeout: 60000,
pingTimeout: 60000,
sniffOnStart: true,
}),
],
controllers: [StoresController],
providers: [
StoresService,
LocationService,
StoresRepository,
TablesRepository,
ReviewsRepository,
WaitingsService,
WaitingsRepository,
],
})
export class StoresModule {}
2. 식당목록조회에 redis 적용하기(에러지옥)
A. redis client에 접속하는 명령어로 접속을 하려했으나 실패
redis-cli -h redis-10555.c262.us-east-1-3.ec2.cloud.redislabs.com -p 10555 -a zxXBZoDFEW3cGnFA0tYMj0edC673xHhU
redis-client는 분명히 설치했는데 redis-cli 명령어가 인식되지 않는다는 에러가 발생하는 경우에는 Redis 클라이언트가 시스템의 PATH 환경 변수에 등록되어 있지 않을 수 있고, 이를 해결하기 위해 다음과 같은 단계를 추천했다.
- 다운로드한 Redis 클라이언트 MSI 파일을 실행하여 설치 프로세스를 시작합니다.
- 설치 중 "설정 추가 옵션" 단계에서 "PATH에 Redis 바이너리 추가" 옵션을 선택합니다. 이 옵션을 선택하면 Redis 클라이언트가 시스템의 PATH 환경 변수에 자동으로 등록됩니다. 만약 이미 설치가 완료되었다면 Redis 클라이언트를 다시 설치하고 해당 옵션을 선택해 주세요.
- 설치를 완료하고 시스템을 재시작합니다. 이렇게 하면 환경 변수 변경이 적용됩니다.
다시 VSCode를 열고 터미널을 실행한 후 redis-cli 명령어를 실행해 보세요.
다행히 위와 같은 방법으로 해결했다.
B.우선 nest.js에 redis를 어떻게 연결할 것인가 부터 차근차근 알아봤다.
- src/cache/redis.modules.ts에 redis module을 연결한다.
- src/cache/redis.service.ts에 redis set,get 등 로직을 설정한다.
- src/cache/redis.service.ts에 test 해볼 함수를 작성한다.
- controller로 함수를 호출한다.
식당목록조회에 redis 적용이 절대로! 안됐기때문에 ..
아주 기본부터 시작해보기로했다, test data를 저장하는 함수를 만들어서 내 local redis에 연결 실험해보기로 했다.
redis-cli -h localhost -p 6379 -a your_password
KEYS "searchRestaurants*"
// src/cache/redis.module.ts
import { Module } from '@nestjs/common';
import * as redisStore from 'cache-manager-redis-store';
import * as dotenv from 'dotenv';
import { RedisCacheService } from './redis.service';
import { CacheModule } from '@nestjs/cache-manager';
import { RedisController } from './redis.controller';
dotenv.config();
@Module({
imports: [
CacheModule.register({
useFactory: async () => ({
store: redisStore,
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
auth_pass: process.env.REDIS_PASSWORD,
ttl: 86400,
}),
}),
],
providers: [RedisCacheService],
controllers: [RedisController],
})
export class RedisCacheModule {}
// src/cache/redis.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { Cache } from 'cache-manager';
import { CACHE_MANAGER } from '@nestjs/cache-manager/dist';
@Injectable()
export class RedisCacheService {
constructor(@Inject(CACHE_MANAGER) private cache: Cache) {}
// async getCache() {
// const savedTime = await this.cache.get<number>('time');
// if (savedTime) {
// return 'saved time : ' + savedTime;
// }
// const now = new Date().getTime();
// await this.cache.set('time', now);
// return 'save new time : ' + now;
// }
async set(key: string, value: any, option?: any) {
await this.cache.set(key, value, option);
}
async get(key: string): Promise<any> {
return await this.cache.get(key);
}
async reset() {
await this.cache.reset();
}
async del(key: string) {
await this.cache.del(key);
}
async injectTestData() {
const testData = {
key1: 'value1',
key2: 'value2',
key3: 'value3',
key4: 'value4',
key5: 'value5',
key6: 'value6',
};
for (const key in testData) {
if (testData.hasOwnProperty(key)) {
await this.set(key, testData[key]);
}
}
for (const key in testData) {
if (testData.hasOwnProperty(key)) {
const value = await this.get(key);
console.log(`Key: ${key}, Value: ${value}`);
}
}
}
}
// example.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { RedisCacheService } from './../cache/redis.service';
import { Public } from 'src/auth/common/decorators';
@Controller('example')
export class RedisController {
constructor(private readonly redisCacheService: RedisCacheService) {}
@Public()
@Get('inject-test-data')
async injectTestData(): Promise<string> {
await this.redisCacheService.injectTestData();
return 'Test data injected successfully.';
}
@Public()
@Get('get-key')
async getData(): Promise<any> {
const key = 'key1';
const value = await this.redisCacheService.get(key);
const data = { [key]: value };
console.log(data);
return data;
}
@Public()
@Get('get-data')
async getAllData(): Promise<any> {
const keys = ['key1', 'key2', 'key3', 'key4', 'key5', 'key6'];
const data = {};
for (const key of keys) {
const value = await this.redisCacheService.get(key);
data[key] = value;
}
console.log(data);
return data;
}
// @Public()
// @Get('cache')
// getCache() {
// return this.redisCacheService.getCache();
}
위 코드를 실행했을 때 아래와 같이 data가 잘 생성, 저장 되는 것을 볼 수 있다.
그런데.. 저장된 db를 조회하면 .. 이렇게 undefined가 뜨고,
redis-cli로 검색해봐도 db는 저장되어있지 않다.
더 이상한건 db를 저장한 후 몇초안에 바로 데이터 조회 요청을 날리면, db조회가 되고, 몇초가 지나면 사라진다는 것이다.
처음에는 db에 제대로 data가 들어갔다가 삭제되는줄 알았는데 알고보니 이건 CacheModule에서 제공하는 빌트인 된 in-memory 스토리지 기능이라고 한다.
**
$ npm install cache-manager
$ npm install -D @types/cache-manager
주어진 코드에서 Redis 서버 설정이 없는 경우 Nest.js는 기본적으로 인메모리 캐시를 사용한다고 한다. 따라서 Redis 서버에 연결하지 않고도 애플리케이션 자체는 실행할 수 있는 것이다. (●'◡'●) 신기 신기 ..
✅오늘 알게된 점 및 추후 학습 계획
이틀 내내 Redis 적용에 매달리면서 처음엔 맨 땅에 헤딩같고 정말 답이 안보였는데,
오늘 드디어 어느 부분에서 문제인지를 알게되었다!
실제로 서버와 local server, 혹은 redislab server와 연동이 되지 않고 있는 것이다.
구글링해봤는데 내 생각엔 version 문제가 아닐까 싶다.. library의 import module들도 이름이 다 바뀌고 형식이 계속 바뀌는 것 같다. 어디가 문제인지 알았으니 내일은 그 문제를 반드시 해결해야겠다 ..☺
'개발 > 프로젝트-식당 웨이팅 앱 FOOD LINE' 카테고리의 다른 글
230610 실전프로젝트11 [Node.js/Nest.js_redis 적용하기3] (0) | 2023.06.10 |
---|---|
230609 실전프로젝트10 [Node.js/Nest.js_redis 적용하기2] (1) | 2023.06.09 |
230603 실전프로젝트8 [Node.js/Nest.js_sort별 목록조회 api 보완] (0) | 2023.06.05 |
230602 실전프로젝트7 [Node.js/Nest.js_sort별 목록조회, 에러] (0) | 2023.06.03 |
230601 실전프로젝트6 [Node.js/Nest.js 공공 data저장, 좌표주입하기] (0) | 2023.06.01 |