-
Notifications
You must be signed in to change notification settings - Fork 1
[BE] 크롤러 캐싱
외부 사이트를 크롤링하는 코드가 존재해요.
매 요청마다 사이트를 크롤링하면 속도도 느리고 해당 사이트의 서버에 부하를 줄 수 있어요.
이를 해소하고자 캐싱을 적용해요
-
NestJS에서
caching
을 지원해요! -
제공하는 라이브러리를 설치해요
-
cache-manager
- V4의 경우 TTL이 초 단위이지만, 최신 버전(V5)의 경우 TTL이 밀리초 단위입니다..!
@nestjs/cache-manager
-
-
AppModule에 CacheModule을 등록해요
import { Module } from '@nestjs/common'; import { CacheModule } from '@nestjs/cache-manager'; import { AppController } from './app.controller'; @Module({ imports: [CacheModule.register()], controllers: [AppController], })
- 등록할 때 여러 옵션을 넣을 수 있어요.
- store: 저장소 설정
- ttl: 저장하고 있을 시간
- max: 최대로 저장할 데이터 수
- isCacheableValue: 저장 가능한 데이터 타입을 지정
(value: any) => boolean;
- isGlobal: 전역 등록 가능하도록 설정
- 등록할 때 여러 옵션을 넣을 수 있어요.
-
캐시 저장소 가져오기
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
-
캐시에 저장하기
this.cacheManager.set(key, value);
-
캐시에서 데이터를 가져오기
this.cacheManager.get(key);
- 없으면 null이 반환돼요
-
Controller 전체에 적용하기
@Controller() @UseInterceptors(CacheInterceptor) export class AppController { @Get() findAll(): string[] { return []; } }
-
특정 API에만 적용하기
@Controller() export class AppController { @Get() @UseInterceptors(CacheInterceptor) findAll(): string[] { return []; } }
-
글로벌 설정 변경하기
- 위에서 설정한
ttl
을 매 요청별로 커스텀 할 수 있어요.@CacheTTL(20)
- 위에서 설정한
⇒ 현재 서비스는 해당 방식을 채택했어요. 우리 서버는 단일 서버이기에 클러스터링도 필요없고, 단순히 특정 요청에 대한 캐싱으로도 충분해요.
-
캐시 저장소 변경
현재는 메모리에 캐시하고 있어요.
심지어 Redis와 같이 클러스터링을 지원하는 캐시메모리를 사용하지 않아요.
-
캐시 최대 사이즈 계산
현재 응답 데이터의 크기는 약 400B 정도로 나와요.
-
GET /api/v1/themes/1/timetable?date=2023-12-11T15%3A00%3A00.000Z
-
response
[ { "target_time": "10:30", "possible": true }, { "target_time": "11:30", "possible": true }, { "target_time": "12:30", "possible": true }, { "target_time": "13:30", "possible": true }, { "target_time": "14:30", "possible": true }, { "target_time": "15:30", "possible": true }, { "target_time": "16:30", "possible": true }, { "target_time": "17:30", "possible": true }, { "target_time": "18:30", "possible": true }, { "target_time": "19:30", "possible": true } ]
현재 제공 중인 테마의 수는 약 400개이고, 대부분의 지점에서 제공하는 예약은 14일 정도에요.
그러면 아래와 같은 사이즈를 유추할 수 있어요!
캐시 데이터의 최대 크기 = 응답 데이터 사이즈 * 테마 수 * 지원하는 날짜 수 = 400B * 400 * 14 = 2,240,000B = 2.24MB
모든 데이터가 저장된다 하더라도 매우 적은 양이에요.
데이터베이스로 저장이 필요할까 고민했지만 상관없어 보여요.
추후 데이터 분석이 필요하다 판단되면 수정해요.
-
-
-
캐시의 key 값 변경
Request를 기준으로 캐싱하고 있어서 당일 데이터의 경우 캐시가 적용되지 않아요.
당일이 아닌 경우 해당 날짜의 00:00:00.000 으로 들어오지만, 당일의 경우
date
값이 현재 시간으로 들어오고 있어요.이것도 캐싱되기 위해서 dateString(YYYY-MM-DD)으로 변환된 key로 캐싱을 적용해요.
GET /api/v1/themes/1/timetable?date=2023-12-11T15%3A00%3A00.000Z
-
수집한 정보를 모두 캐싱하기
특정 사이트의 경우 한 테마만 조회하는 것이 불가능해서 해당 지점의 모든 테마 정보를 조회하고 있어요. 이 경우 요청으로 들어온 테마가 아니더라도 미리 캐시에 저장시켜요.
앞으로 더 고려할 점의 2,3번 수행 #421
현재 대부분의 크롤러가 단일 테마의 조회만 지원하고 있어요.
유일하게 지원하는 넥스트에디션
에 대해서만 한 번의 요청으로 지점 내 테마들을 모두 캐싱하도록 처리했어요.
테스트 결과 실제로 전송되는 요청 수를 줄일 수 있었습니다.
하지만, 부하테스트 결과 개선된 코드가 오히려 성능이 안나오는 이슈가 있었습니다.
추측하기론, 현재 코드는 캐시의 key를 변경하면서 데이터베이스를 무조건 한 번 접근한 후 반환하고 있습니다.
이로 인해서 데이터베이스 접근 오버헤드가 발생해서 시간이 되려 늦어진 것으로 판단됩니다.
Reference
Caching
https://docs.nestjs.com/techniques/caching
https://github.com/boostcampwm2023/web05-AlgoITNi/pull/113
- [FE] 성능 최적화(디바운스와 쓰로틀링)
- [FE] 채팅-1 채팅을 어떻게 저장할까?
- [FE] 채팅-2 읽지 않은 사람 수를 어떻게 계산할까?
- [FE] 채팅-3 프로필을 보여주는 경우
- [FE] 채팅-4 프로필을 보여주는 경우
- [FE] 채팅-5 프로필을 보여주는 경우
- [FE] 무한스크롤과 IntersectionObserver hook 만들기
- [FE] recoil의 atomFamily 사용하기
- [FE] 반응형 스켈레톤 UI 만들기
- [FE] svg파일을 React에서 컴포넌트처럼 사용하기
- [BE] 채팅방 이벤트 정리
- [BE] 안읽은 사람수 계산하기
- [BE] 크롤러 캐싱
- [BE] 네이버 소셜 로그인
- [BE] 테마 관련 API 캐싱적용
- [BE] S3을 사용해보았어요
- [BE] 성능테스트 환경 구축
- [BE] 채팅 아키텍처 구성하기
- [BE] swagger가 작성한 코드보다 길어질 때
- [BE] @OptionalGuard 데코레이터