logo

Lazy loading modules

preview
August 13, 2024

이 글은 Nest 공식문서를 번역한 글입니다. 원문

기본적으로 모듈은 eagerly 로드되므로 애플리케이션이 로드되는 즉시 즉시 필요한지 여부에 관계없이 모든 모듈도 로드됩니다. 이는 대부분의 애플리케이션에는 괜찮지만, 시작 대기 시간("cold start")이 중요한 서버리스 환경에서 실행되는 앱/워커에는 병목 현상이 될 수 있습니다.

지연 로딩은 특정 서버리스 함수 호출에 필요한 모듈만 로드하여 부트스트랩 시간을 줄이는 데 도움이 될 수 있습니다. 또한 서버리스 함수가 "워밍업"되면 다른 모듈을 비동기적으로 로드하여 후속 호출에 대한 부트스트랩 시간을 더욱 단축할 수도 있습니다(지연된 모듈 등록).

🖋️

HINT

Angular 프레임워크에 익숙하다면 lazy-loading modules이라는 용어를 본 적이 있을 것입니다. 이 기술은 Nest에서 기능적으로 다르므로 유사한 명명 규칙을 공유하는 완전히 다른 기능으로 생각하세요.

🚨

WARNING

라이프사이클 훅 메서드는 지연 로드된 모듈과 서비스에서는 호출되지 않는다는 점에 유의하세요.

Getting started#

필요에 따라 모듈을 로드하기 위해 Nest는 일반적인 방법으로 클래스에 주입할 수 있는 LazyModuleLoader 클래스를 제공합니다:

cats.service.ts
@Injectable()
export class CatsService {
  constructor(private lazyModuleLoader: LazyModuleLoader) {}
}

또는 다음과 같이 애플리케이션 부트스트랩 파일(main.ts) 내에서 LazyModuleLoader 공급자에 대한 참조를 얻을 수 있습니다:

// "app"은 Nest 애플리케이션 인스턴스를 나타냅니다.
const lazyModuleLoader = app.get(LazyModuleLoader);

이제 다음 구성을 사용하여 모든 모듈을 로드할 수 있습니다:

const { LazyModule } = await import("./lazy.module");
const moduleRef = await this.lazyModuleLoader.load(() => LazyModule);
🖋️

HINT

"지연 로드된" 모듈은 첫 번째 LazyModuleLoader#load 메서드 호출 시 캐시됩니다. 즉, 연속적으로 LazyModule을 로드하려고 시도할 때마다 모듈을 다시 로드하는 대신 캐시된 인스턴스를 반환하는 속도가 매우 빨라집니다.

Load "LazyModule" attempt: 1
time: 2.379ms
Load "LazyModule" attempt: 2
time: 0.294ms
Load "LazyModule" attempt: 3
time: 0.303ms

또한 "lazy loaded" 모듈은 애플리케이션 부트스트랩에서 eagerly 로드된 모듈과 앱의 나중에 등록된 다른 지연 모듈과 동일한 모듈 그래프를 공유합니다.

여기서 lazy.module.ts일반 Nest 모듈(추가 변경이 필요하지 않음)을 내보내는 TypeScript 파일입니다.

LazyModuleLoader#load 메서드는 내부 공급자 목록을 탐색하고 해당 주입 토큰을 조회 키로 사용하여 모든 공급자에 대한 참조를 얻을 수 있는 모듈 참조(LazyModule)를 반환합니다.

예를 들어 다음 정의가 있는 LazyModule이 있다고 가정해 보겠습니다:

@Module({
  providers: [LazyService],
  exports: [LazyService],
})
export class LazyModule {}
🖋️

HINT

힌트 지연 로드된 모듈은 글로벌 모듈로 등록할 수 없습니다(정적으로 등록된 모든 모듈이 이미 인스턴스화된 상태에서 온디맨드로 지연 등록되기 때문에 의미가 없습니다). 마찬가지로 등록된 글로벌 인핸서(가드/인터셉터 등)도 제대로 작동하지 않습니다.

이를 통해 다음과 같이 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"로 설정하고 "node"를 값으로 하는 compilerOptions.moduleResolution 속성을 추가해야 합니다:

{
 "compilerOptions": {
   "module": "esnext",
   "moduleResolution": "node",
   ...
 }
}

이러한 옵션을 설정하면 코드 분할 기능을 활용할 수 있습니다.

Lazy loading controllers, gateways, and resolvers#

Nest의 컨트롤러(또는 GraphQL 애플리케이션의 리졸버)는 routes/paths/topics (queries/mutations)의 집합을 나타내므로 LazyModuleLoader 클래스를 사용하여 지연 로드할 수 없습니다.

🔥

WARNING

지연 로드된 모듈 내에 등록된 Controllers,  resolvers 및  gateways는 예상대로 작동하지 않습니다. 마찬가지로 미들웨어 함수(MiddlewareConsumer 인터페이스 구현)는 온디맨드 방식으로 등록할 수 없습니다.

예를 들어, 내부에 Fastify 드라이버를 사용하여 REST API(HTTP 애플리케이션)를 구축한다고 가정해 보겠습니다(@nestjs/platform-fastify 패키지 사용). Fastify는 애플리케이션이 준비되거나 메시지를 성공적으로 수신한 후에는 경로를 등록할 수 없습니다. 즉, 모듈의 컨트롤러에 등록된 경로 매핑을 분석하더라도 런타임에 등록할 방법이 없기 때문에 지연 로드된 모든 경로에 액세스할 수 없습니다.

마찬가지로, @nestjs/microservices 패키지의 일부로 제공되는 일부 전송 전략(Kafka, gRPC 또는 RabbitMQ 포함)은 연결이 설정되기 전에 특정  topics/channelssubscribe/listen해야 합니다. 애플리케이션이 메시지 수신을 시작하면 프레임워크는 새 토픽을 subscribe/listen할 수 없게 됩니다.

마지막으로, 코드 우선 접근 방식이 활성화된 @nestjs/graphql 패키지는 메타데이터를 기반으로 GraphQL 스키마를 즉석에서 자동으로 생성합니다. 즉, 모든 클래스를 미리 로드해야 합니다. 그렇지 않으면 적절하고 유효한 스키마를 생성할 수 없습니다.

Common use-cases#

가장 일반적으로, 작업자/크론 작업/람다 및 서버리스 함수/웹훅이 입력 인수(경로 경로/날짜/쿼리 매개변수 등)에 따라 다른 서비스(다른 로직)를 트리거해야 하는 상황에서 지연 로드된 모듈을 볼 수 있습니다. 반면에 지연 로딩 모듈은 시작 시간이 다소 무관한 모놀리식 애플리케이션에는 그다지 의미가 없을 수 있습니다.Í