기본적으로 모듈은 즉시 필요한지 여부와 상관없이 애플리케이션이 로드되는 대로 즉시 로드됩니다. 대부분의 애플리케이션에는 이게 괜찮지만, 서버리스 환경에서는 시작 지연 시간("콜드 스타트")이 중요한 요소가 될 수 있습니다.
lazy-loading은 특정 서버리스 함수 호출에서 필요한 모듈만 로드하여 부트스트랩 시간을 줄이는 데 도움이 될 수 있습니다. 또한 서버리스 함수가 "워밍업"된 후 비동기적으로 다른 모듈을 로드하여 이후 호출의 부트스트랩 시간을 더욱 빠르게 할 수도 있습니다 (지연된 모듈 등록).
Hint!
Angular 프레임워크에 익숙하다면 "Lazy-loading modules" 용어를 이전에 본 적이 있을 수 있습니다. 그러나 Nest에서 이 기술은 기능적으로 다른 방식으로 작동하므로 완전히 다른 기능으로 생각하시면 됩니다.
Warning!!
Lazy-loading modules 및 서비스에서는 lifecycle hooks methods가 호출되지 않는다는 점에 유의하십시오.
모듈을 필요할 때 로딩하기 위해 Nest는 LazyModuleLoader 클래스를 제공합니다.
이 클래스는 일반적인 방법으로 클래스에 주입할 수 있습니다.
// cats.service.ts
@Injectable()
export class CatsService {
constructor(private lazyModuleLoader: LazyModuleLoader) {}
}
LazyModuleLoader
클래스는@nestjs/core
패키지에서 가져옵니다.
또는 애플리케이션 부트스트랩 파일 (main.ts
)에서 LazyModuleLoader
공급자에 대한 참조를 얻을 수도 있습니다.
// "app"은 Nest 애플리케이션 인스턴스를 나타냅니다.
const lazyModuleLoader = app.get(LazyModuleLoader);
이렇게 설정하면 다음 구조를 사용하여 모든 모듈을 로드할 수 있습니다.
const { LazyModule } = await import('./lazy.module');
const moduleRef = await this.lazyModuleLoader.load(() => LazyModule);
Hint!
"lazy-loading"된 모듈은LazyModuleLoader#load
메서드가 처음 호출될 때 캐시됩니다. 즉,LazyModule
을 로드하는 연속적인 시도마다 모듈이 다시 로드되는 대신 캐시된 인스턴스가 매우 빠르게 반환됩니다."LazyModule" 로드 시도: 1 시간: 2.379ms "LazyModule" 로드 시도: 2 시간: 0.294ms "LazyModule" 로드 시도: 3 시간: 0.303ms
//이건 내가 해본 것 [Nest] 5132 - 2023. 07. 18. 오후 5:04:02 LOG [LazyModuleLoader] CatModule dependencies initialized Load "LazyModule" attempt: 1 Time: 1.2419ms [Nest] 5132 - 2023. 07. 18. 오후 5:04:02 LOG [LazyModuleLoader] CatModule dependencies initialized Load "LazyModule" attempt: 2 Time: 1.7251ms [Nest] 5132 - 2023. 07. 18. 오후 5:04:02 LOG [LazyModuleLoader] CatModule dependencies initialized Load "LazyModule" attempt: 3 Time: 1.0523ms [Nest] 5132 - 2023. 07. 18. 오후 5:04:02 LOG [LazyModuleLoader] CatModule dependencies initialized Load "LazyModule" attempt: 4 Time: 1.3416ms // 난 왜 안됨 ㅜㅜ // Cache가 안되나??
또한 "lazy-loading"된 모듈은 애플리케이션 부트스트랩에서 즉시 로드되는 모듈과 나중에 앱에 등록된 다른 게으른 모듈과 동일한 모듈 그래프를 공유합니다.
여기서 lazy.module.ts
는 별도의 변경 없이 일반적인 Nest 모듈을 내보내는 TypeScript
파일입니다.
LazyModuleLoader#load
메서드는 모듈 참조(LazyModule
)를 반환하여 내부 제공자 목록을 탐색하고 주입 토큰을 사용하여 어떤 provider에 대한 참조를 얻을 수 있습니다.
예를 들어, 다음과 같이 LazyModule
을 가지고 있는 경우를 생각해보겠습니다.
@Module({
providers: [LazyService],
exports: [LazyService],
})
export class LazyModule {}
Hint!
lazy-loading-module은 전역 모듈로 등록할 수 없습니다. 이는 단순히 의미가 없기 때문입니다(게으르게 등록되기 때문에 정적으로 등록된 모듈이 이미 인스턴스화된 후에 요청에 따라 등록됩니다). 마찬가지로 등록된 글로벌 엔한서(guards/interceptors 등)도 제대로 작동하지 않습니다.
이제 LazyService 제공자에 대한 참조를 다음과 같이 얻을 수 있습니다.
const { LazyModule } = await import('./lazy.module');
const moduleRef = await this.lazyModuleLoader.load(() => LazyModule);
const { LazyService } = await import('./lazy.service');
const lazyService = moduleRef.get(LazyService);
Warning!!
Webpack을 사용하는 경우tsconfig.json
파일을 업데이트해야 합니다.compilerOptions.module
을 "esnext"로 설정하고compilerOptions.moduleResolution
속성을 값으로"node"
를 추가하는 것입니다.
이러한 옵션을 설정하면 코드 분할 기능을 활용할 수 있습니다.
Nest에서 컨트롤러는 routes/paths/topics(또는 GraphQL 애플리케이션의 경우 쿼리/뮤테이션) 집합을 나타내기 때문에, LazyModuleLoader
클래스를 사용하여 컨트롤러를 지연 로드할 수는 없습니다.
Warning!
레지스터된 컨트롤러, 리졸버 및 게이트웨이는 지연로드된 모듈 내에서 예상대로 동작하지 않을 것입니다. 마찬가지로, 미들웨어 함수(미들웨어 컨슈머 인터페이스를 구현함으로써)를 필요에 따라 등록할 수도 없습니다.
예를 들어, Fastify 기반의 REST API(HTTP 애플리케이션)를 개발하고 있다고 가정해보겠습니다 (@nestjs/platform-fastify
패키지를 사용). Fastify는 애플리케이션이 준비되고 메시지 수신을 성공적으로 수행한 후에는 경로를 등록할 수 없습니다. 따라서 지연 로드된 경로는 애플리케이션이 실행 중인 동안에는 접근할 수 없습니다.
마찬가지로, @nestjs/microservices
패키지의 일부인 Kafka, gRPC 또는 RabbitMQ와 같은 전송 전략은 연결이 설정되기 전에 특정 토픽/채널을 구독/수신해야 합니다. 애플리케이션이 메시지 수신을 시작하면 프레임워크는 새로운 토픽을 구독/수신할 수 없습니다.
마지막으로, 코드 기반 접근 방식을 사용하는 @nestjs/graphql
패키지는 메타데이터를 기반으로 GraphQL 스키마를 동적으로 생성합니다. 이는 모든 클래스가 사전에 로드되어야 한다는 것을 의미합니다. 그렇지 않으면 적합하고 유효한 스키마를 생성할 수 없습니다.
가장 일반적으로, 지연 로드된 모듈은 워커/크론 작업/람다 및 서버리스 함수/웹훅과 같은 상황에서 입력 인수(route path/date/query parameters, etc.)에 따라 다른 서비스(다른 로직)를 트리거해야 할 때 사용됩니다. 반면, 지연 로드된 모듈은 시작 시간이 상관 없는 단일체 애플리케이션에는 그다지 의미가 없을 수 있습니다.
cold start?
함수가 warm 된다는게 어떤거야?
라이프 사이클 후크 메서드?
서버리스란?
서버리스는 어떻게 구현되나?
webpack은 뭐야?
코드 분할이란?
왜 서버리스 환경에서는 콜드 스타트가 중요할까? 왜 써야할까?
임시 출간
결국 lazy-loading module은 서버리스나
//lazy-test.service.ts
import { Injectable } from '@nestjs/common';
import { LazyModuleLoader } from '@nestjs/core';
import { CatsService } from 'src/cat/cat.service';
@Injectable()
export class LazyTestService {
constructor( private lazyModuleLoader: LazyModuleLoader){}
async loadLazyModule(): Promise<void> {
console.log('loadLazyModule');
const { CatModule } = await import('../cat/cat.module');
const lazyCatModule = await this.lazyModuleLoader.load(() => CatModule);
console.log(lazyCatModule);
// const lazyCatService = lazyCatModule.get('CatsService'); <= 안됨ㅠㅠ 왜 안되는지 모르겠어서 찝찝함
const lazyCatService = lazyCatModule.get(CatsService);
// lazyService 작업 수행
console.log('lazyCatService');
console.log(lazyCatService);
console.log('lazyCatService.findAll()');
console.log(await lazyCatService.findOne());
}
}
이렇게 코드를 불러오면
loadLazyModule
[Nest] 5284 - 2023. 07. 18. 오후 4:04:19 LOG [LazyModuleLoader] CatModule dependencies initialized
이렇게 늦게 이니셜라이즈 하는 것을 볼 수 있습니다.
더 해보니 확실히 캐쉬가 되기는 함
되는 것 과 안되는 것의 차이점을 알 수 있음 좋겟다.
그렇다면 module reference와의 차이점은 무엇일까?
즉 import 하는 시간을 줄일 수 있지 않을까??
추가 수정 예정 있습니당.
가치 있는 정보 공유해주셔서 감사합니다.