NESTJS를 배워보자(18) - Lazy-loading modules

yoon·2023년 10월 5일
0

NESTJS를 배워보자

목록 보기
18/21

Lazy-loading modules

nest의 공식문서를 토대로 작성합니다.

기본적으로 모듈은 열심히 로딩되므로 애플리케이션이 로드되는 즉시 필요한지 여부에 관계없이 모든 모듈이 로드됩니다. 대부분의 애플리케이션은 문제가 없지만 시작을 위한 대기 시간(cold start)이 중요한 서버리스 환경에서 실행되는 apps/workers의 경우 병목 현상이 발생할 수 있습니다.

Lazy loading은 특정 서버리스 함수 호출에 필요한 모듈만 로드하여 부트스트랩 시간을 단축하는 데 도움이 될 수 있습니다. 또한 서버리스 함수가 준비되면 다른 모듈을 비동기적으로 로드하여 후속 호출에 대한 부트스트랩 시간을 더욱 단축할 수도 있습니다.

HINT
Angular 프레임워크에 익숙하다면 "lazy-loading modules"라는 용어를 본 적이 있을 수 있음. 이 기술은 Nest에서 기능적으로 다르므로 이름만 비슷하다고 생각.

WARNING
lifecycle hooks methods는 지연 로드된 모듈 및 서비스에서는 호출되지 않음.

Getting started

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

# cats.service.ts

@Injectable()
export class CatsService {
  constructor(private lazyModuleLoader: LazyModuleLoader) {}
}

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

// "app" represents a Nest application instance
const lazyModuleLoader = app.get(LazyModuleLoader);

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

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

HINT
"Lazy-loaded" 모듈은 첫 번째 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" 모듈은 애플리케이션 부트스트랩에 로드된 모듈과 앱에 나중에 등록된 다른 "lazy-loaded" 모듈과 동일한 모듈 그래프를 공유합니다.

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

LazyModuleLoader#load 메소드는 내부 provider 목록을 탐색하고 해당 주입 토큰을 조회 키로 사용하여 모든 provider에 대한 참조를 얻을 수 있는 module reference를 반환합니다.

예를 들어 다음과 같은 정의가 있는 LazyModule이 있다고 가정합니다:

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

HINT
Lazy-loaded 모듈은 전역 모듈로 등록할 수 없음(정적으로 등록된 모든 모듈이 이미 인스턴스화된 상태에서 온디맨드 방식으로 지연 등록되므로 의미가 없음). 마찬가지로 등록된 전역 enhancers(가드/인터셉터 등)도 제대로 작동하지 않음.

이를 통해 다음과 같이 LazyService provider에 대한 참조를 얻을 수 있습니다:

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 controller, gateways, and resolvers

Nest의 컨트롤러는 routes/path/topics의 집합을 나타내므로 LazyModuleLoader 클래스를 사용하여 지연 로드할 수 없습니다.

WARNING
지연 로드된 모듈 내부에 등록된 컨트롤러, 리졸버 및 게이트웨이는 예상대로 작동하지 않음. 마찬가지로 미들웨어 함수를 온디맨드 방식으로 등록할 수 없음.

예를 들어 Fastify 드라이버를 사용하여 REST API를 구축한다고 가정합니다. Fastify는 애플리케이션이 준비되거나 메시지를 성공적으로 수신한 후에는 routes를 등록할 수 없습니다. 즉 모듈의 컨트롤러에 등록된 경로 매핑을 분석하더라도 런타임에 등록할 방법이 없기 때문에 모든 지연 로드된 routes에 접근할 수 없습니다.

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

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

Common use-cases

대부분의 경우 worker/cron job/lambda serverless function/webhook이 입력 인수에 따라 다른 서비스를 트리거해야 하는 상황에서 지연 로드된 모듈을 볼 수 있습니다. 반면에 지연 로딩 모듈은 시작 시간이 다소 무관한 monolithic 애플리케이션에는 그다지 의미가 없을 수 있습니다.

monolithic?
이 아키텍쳐를 갖는 소프트웨어는 내부 요소간의 의존성이 강하다고 합니다. 이는 컴포넌트들이 하나의 강한 결합 구조를 지니고 통일성이 있다고 합니다. 그래서 비즈니스 로직이 서비스에 최적화된 코드를 만들어내는 데 좀 더 집중할 수 있는 반면 복합적인 예외를 만들 수 있는 위험성을 내포하게 된다고 합니다! 자세한 내용은 저도 여기서 확인했습니다
https://m.blog.naver.com/seek316/221863498991

저는 평소에 코드가 동작하면 되는 거지~라고 생각했었는데 이런 요소들까지 고려하여 큰 서비스 로직들이 짜이는군요..진짜 아직 멀었습니다..

고생하셨습니다!
다음 글에서 만나요~~😀


저도 아직 배우는 단계입니다. 지적 감사히 받겠습니다. 함께 열심히 공부해요!!

profile
백엔드 개발자 지망생

0개의 댓글