(AWS) AWS Lightsail 메모리 이슈

최건·2025년 9월 4일

1) 문제 상황 설명

  • 증상: nest build 중 아래와 같은 OOM 오류로 프로세스 종료

    FATAL ERROR: Ineffective mark-compacts near heap limit
    Allocation failed - JavaScript heap out of memory
  • 환경: AWS Lightsail (RAM 512MB / vCPU 2 / SSD 20GB)

  • 원인: TypeScript 타입체크+트랜스파일 단계에서 Node.js 힙 사용량이 시스템 메모리 한도(≈512MB)와 스왑을 넘어섬 → GC가 메모리 회수 불가 → heap out of memory 발생.


2) 내가 사용한 해결 방법

2-1. 인스턴스 업그레이드

  • Lightsail 사양을 상향(≥2GB RAM 권장)하여 빌드 메모리 여유를 확보.
  • 업그레이드 후 빌드가 정상 완료됨.

2-2. SWC 기반 빌드로 전환(속도/메모리 개선)

  • 목적: 빌드 속도와 메모리 사용량을 줄여 개발 및 CI 안정화.

  • 적용 사항:

    1. 패키지 설치

      npm i -D @swc/cli @swc/core
    2. nest-cli.json

      { "compilerOptions": { "builder": "swc" } }

      (또는 nest build --builder swc, nest start -b swc)

    3. 타입체크 필요 시 병렬 수행

      • CLI: nest start -b swc --type-check

      • 설정:

        { "compilerOptions": { "builder": "swc", "typeCheck": true } }
    4. .swcrc(선택)

      {
        "sourceMaps": true,
        "jsc": { "parser": { "syntax": "typescript", "decorators": true, "dynamicImport": true } },
        "minify": false
      }
    5. TS 옵션 권장: skipLibCheck: true, incremental: true


3) (SWC 빌드 시) 새로 발생했던 의존성 문제

  • 에러:

    ReferenceError: Cannot access 'Product' before initialization
  • 상황: 엔티티 간 양방향 정적 import(순환 참조) + 데코레이터 메타데이터 조합.

  • 원인 분석:

    • tsc는 CommonJS 변환에서 순환 참조를 비교적 “관대하게” 처리하는 반면,
    • swc는 초기화 순서에 엄격하여 아직 초기화 전 클래스에 접근하면 ReferenceError 발생.
    • TypeORM의 엔티티 양방향 관계(예: ProductProductMapping)에서 자주 노출.

예)

// product.entity.ts
import { ProductMapping } from './product_mapping.entity';
@OneToMany(() => ProductMapping, m => m.product) mappings: ProductMapping[];

// product_mapping.entity.ts
import { Product } from './product.entity';
@ManyToOne(() => Product, p => p.mappings) product: Product;

4) 문제 해결 방법 (SWC 의존성/초기화 이슈)

4-1. 지연 평가(Lazy)로 순환 절단

  • 정적 import 제거런타임 require + 람다로 참조 지연:
// BEFORE
@ManyToOne(() => Product, p => p.mappings)

// AFTER
@ManyToOne(() => (require('./product.entity').Product), p => p.mappings)
  • 동일 패턴을 모든 순환 관계에 적용(Shop, User, Review, Image 등).

4-2. 양방향 축소 & 조인 엔티티로 단순화

  • ProductShopManyToMany 제거.

  • **명시적 조인 엔티티(product_mapping)**로만 연결:

    • 중복 관계 제거, 순환 위험 해제, 확장 속성(예: 가격/상태) 부여 용이.

4-3. 타입 의존성 최소화

  • type-only import로 런타임 의존성 제거:

    import type { Product } from './product.entity';
  • 관계 필드 타입은 필요 시 any/any[]로 완화
    (런타임에는 데코레이터 메타데이터가 타입 정보를 제공하므로 동작에 문제 없음. 서비스/DTO 레이어에서 타입 안전성 보완).

4-4. TS/빌드 설정 점검

  • tsconfig.json

    { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
  • .swcrc에서 decorators: true 확인.

  • 경로 별칭(tsconfig paths)을 사용 중이면 해결 순서가 순환을 강화하지 않도록 import 정리.

4-5. 파일별 핵심 변경 요약

  • product.entity.ts: 다른 엔티티에 대한 직접 관계 제거(필요 최소 컬럼만 유지).

  • product_mapping.entity.ts:

    @ManyToOne(() => (require('./product.entity').Product), ...) 
    @ManyToOne(() => (require('./shop.entity').Shop), ...)
    // FK 컬럼 유지, onDelete: 'CASCADE'
  • shop.entity.ts: Product와의 직접 ManyToMany 제거. 기존 OneToMany(운영시간/리뷰/매핑)는 유지.

  • operating-hours.entity.ts:
    @ManyToOne(() => (require('./shop.entity').Shop), ...)

  • review.entity.ts:
    User, Shop, Image에 대해 require 지연 + 관계 타입 완화.

  • image.entity.ts, user.entity.ts, wishlist.entity.ts, region.entity.ts, submit-user.entity.ts:
    모두 동일하게 require 지연 참조타입 완화 적용.

결론

  • 메모리 부족 문제는 인스턴스 업그레이드로 즉시 해소.
  • SWC 전환으로 빌드 성능이 향상되었으나, 엔티티 순환 참조로 인한 초기화 에러가 발생.
  • 이를 지연 평가(require) 패턴, 양방향 관계 축소/조인 엔티티화, type-only import/타입 완화, 데코레이터/메타데이터 설정 유지로 해결하여 SWC 빌드 안정화를 달성했습니다.
profile
개발이 즐거운 백엔드 개발자

0개의 댓글