NestJS 공식문서 Lazy-loading modules

GGAE99·2023년 7월 18일
0

NestJS 공식 문서

목록 보기
10/33

Lazy-loading modules

기본적으로 모듈은 즉시 필요한지 여부와 상관없이 애플리케이션이 로드되는 대로 즉시 로드됩니다. 대부분의 애플리케이션에는 이게 괜찮지만, 서버리스 환경에서는 시작 지연 시간("콜드 스타트")이 중요한 요소가 될 수 있습니다.

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

Hint!
Angular 프레임워크에 익숙하다면 "Lazy-loading modules" 용어를 이전에 본 적이 있을 수 있습니다. 그러나 Nest에서 이 기술은 기능적으로 다른 방식으로 작동하므로 완전히 다른 기능으로 생각하시면 됩니다.

Warning!!
Lazy-loading modules 및 서비스에서는 lifecycle hooks methods가 호출되지 않는다는 점에 유의하십시오.

Getting started

모듈을 필요할 때 로딩하기 위해 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"를 추가하는 것입니다.

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

Lazy-loading controllers, gateways, and resolvers

Nest에서 컨트롤러는 routes/paths/topics(또는 GraphQL 애플리케이션의 경우 쿼리/뮤테이션) 집합을 나타내기 때문에, LazyModuleLoader 클래스를 사용하여 컨트롤러를 지연 로드할 수는 없습니다.

Warning!
레지스터된 컨트롤러, 리졸버 및 게이트웨이는 지연로드된 모듈 내에서 예상대로 동작하지 않을 것입니다. 마찬가지로, 미들웨어 함수(미들웨어 컨슈머 인터페이스를 구현함으로써)를 필요에 따라 등록할 수도 없습니다.

예를 들어, Fastify 기반의 REST API(HTTP 애플리케이션)를 개발하고 있다고 가정해보겠습니다 (@nestjs/platform-fastify 패키지를 사용). Fastify는 애플리케이션이 준비되고 메시지 수신을 성공적으로 수행한 후에는 경로를 등록할 수 없습니다. 따라서 지연 로드된 경로는 애플리케이션이 실행 중인 동안에는 접근할 수 없습니다.

마찬가지로, @nestjs/microservices 패키지의 일부인 Kafka, gRPC 또는 RabbitMQ와 같은 전송 전략은 연결이 설정되기 전에 특정 토픽/채널을 구독/수신해야 합니다. 애플리케이션이 메시지 수신을 시작하면 프레임워크는 새로운 토픽을 구독/수신할 수 없습니다.

마지막으로, 코드 기반 접근 방식을 사용하는 @nestjs/graphql 패키지는 메타데이터를 기반으로 GraphQL 스키마를 동적으로 생성합니다. 이는 모든 클래스가 사전에 로드되어야 한다는 것을 의미합니다. 그렇지 않으면 적합하고 유효한 스키마를 생성할 수 없습니다.

Common use-cases

가장 일반적으로, 지연 로드된 모듈은 워커/크론 작업/람다 및 서버리스 함수/웹훅과 같은 상황에서 입력 인수(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와의 차이점은 무엇일까?

  • module reference
    미리 import 하고, 필요할 때 마다 이니셜라이즈
  • lazy-loading module
    import와 이니셜라이즈 모두 생성 시점

즉 import 하는 시간을 줄일 수 있지 않을까??

추가 수정 예정 있습니당.

1개의 댓글

comment-user-thumbnail
2023년 7월 18일

가치 있는 정보 공유해주셔서 감사합니다.

답글 달기