icon

로드 밸런싱과 수평적 확장


 로드 밸런싱은 네트워크 트래픽을 여러 서버에 분산시켜 시스템의 전반적인 성능, 가용성, 안정성을 향상시키는 기술입니다.

 NestJS 애플리케이션의 수평적 확장은 서버 인스턴스를 추가함으로써 시스템의 처리 능력을 증가시키는 방법으로 로드 밸런싱과 결합하여 대규모 트래픽을 효과적으로 처리할 수 있게 합니다.

로드 밸런싱 알고리즘

 1. 라운드 로빈 : 요청을 순차적으로 각 서버에 분배

  • 장점 : 구현이 간단하고 공평한 분배
  • 단점 : 서버 능력 차이를 고려하지 않음

 2. 최소 연결 : 현재 연결이 가장 적은 서버로 요청을 라우팅

  • 장점 : 서버 부하를 균등하게 유지
  • 단점 : 연결 수 추적에 따른 오버헤드

 3. IP 해시 : 클라이언트 IP를 기반으로 요청을 특정 서버에 할당

  • 장점 : 세션 유지에 유리
  • 단점 : 불균형한 분배 가능성

Nginx를 사용한 로드 밸런싱 구현

  1. Nginx 설치 및 설정
http {
    upstream nestjs_app {
        server 127.0.0.1:3000;
        server 127.0.0.1:3001;
        server 127.0.0.1:3002;
    }
 
    server {
        listen 80;
        location / {
            proxy_pass http://nestjs_app;
        }
    }
}
  1. NestJS 애플리케이션 시작
npm run start -- --port 3000
npm run start -- --port 3001
npm run start -- --port 3002

클라우드 환경에서의 로드 밸런싱

 AWS Elastic Load Balancer (ELB) 사용 예

  1. ELB 생성 및 설정
  2. Auto Scaling Group 구성
  3. NestJS 애플리케이션 배포
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT || 3000);
}
bootstrap();
  1. 환경 변수를 통한 설정 관리
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV}`,
    }),
  ],
})
export class AppModule {}

세션 관리와 상태 유지

 1. 세션 저장소 분리

  • Redis를 사용한 세션 관리
import * as session from 'express-session';
import * as connectRedis from 'connect-redis';
import * as Redis from 'ioredis';
 
const RedisStore = connectRedis(session);
const redisClient = new Redis({ host: 'your-redis-host' });
 
app.use(
  session({
    store: new RedisStore({ client: redisClient }),
    secret: 'your-secret-key',
    resave: false,
    saveUninitialized: false,
  }),
);

 2. 무상태(Stateless) 아키텍처 지향

  • JWT를 사용한 인증
import { JwtModule } from '@nestjs/jwt';
 
@Module({
  imports: [
    JwtModule.register({
      secret: 'your-secret-key',
      signOptions: { expiresIn: '1h' },
    }),
  ],
})
export class AuthModule {}

서버리스 아키텍처 활용

 AWS Lambda와 NestJS 통합

  1. @vendia/serverless-express 설치
  2. Lambda 핸들러 생성
import { Handler, Context } from 'aws-lambda';
import { Server } from 'http';
import { createServer, proxy } from '@vendia/serverless-express';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
 
let cachedServer: Server;
 
async function bootstrap(): Promise<Server> {
  const app = await NestFactory.create(AppModule);
  await app.init();
  const expressApp = app.getHttpAdapter().getInstance();
  return createServer(expressApp);
}
 
export const handler: Handler = async (event: any, context: Context) => {
  if (!cachedServer) {
    cachedServer = await bootstrap();
  }
  return proxy(cachedServer, event, context, 'PROMISE').promise;
};

데이터베이스 수평적 확장

 1. 읽기 복제본 구성

import { TypeOrmModule } from '@nestjs/typeorm';
 
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      replication: {
        master: {
          host: 'master.example.com',
          port: 3306,
          username: 'master_user',
          password: 'master_password',
          database: 'db_name',
        },
        slaves: [{
          host: 'slave1.example.com',
          port: 3306,
          username: 'slave_user',
          password: 'slave_password',
          database: 'db_name',
        }, {
          host: 'slave2.example.com',
          port: 3306,
          username: 'slave_user',
          password: 'slave_password',
          database: 'db_name',
        }],
      },
    }),
  ],
})
export class AppModule {}

 2. 데이터베이스 샤딩

  • 애플리케이션 레벨에서 샤딩 로직 구현
@Injectable()
export class UserService {
  private shards: DataSource[];
 
  constructor() {
    this.shards = [
      new DataSource({ /* shard1 config */ }),
      new DataSource({ /* shard2 config */ }),
    ];
  }
 
  async getUser(id: number) {
    const shardIndex = id % this.shards.length;
    return this.shards[shardIndex].getRepository(User).findOne(id);
  }
}

Kubernetes를 사용한 NestJS 스케일링

  1. Dockerfile 생성
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "run", "start:prod"]
  1. Kubernetes Deployment 설정
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nestjs-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nestjs-app
  template:
    metadata:
      labels:
        app: nestjs-app
    spec:
      containers:
      - name: nestjs-app
        image: your-docker-image:tag
        ports:
        - containerPort: 3000
  1. Kubernetes Service 설정
apiVersion: v1
kind: Service
metadata:
  name: nestjs-app-service
spec:
  selector:
    app: nestjs-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: LoadBalancer

Best Practices 및 주의사항

  1. 무상태 설계 : 세션 정보를 외부 저장소에 보관
  2. 캐싱 전략 : Redis 등을 사용한 분산 캐싱 구현
  3. 데이터베이스 최적화 : 인덱싱, 쿼리 최적화, 연결 풀링
  4. 비동기 처리 : 장기 실행 작업은 메시지 큐 사용
  5. 모니터링 및 로깅 : 중앙화된 로깅 시스템 구축
  6. 자동 스케일링 : 트래픽 패턴에 따른 자동 확장/축소 설정
  7. 보안 : SSL/TLS 적용, 적절한 인증/인가 메커니즘 구현
  8. 장애 대비 : 다중 가용 영역 배포, 장애 조치 전략 수립
  9. 컨테이너화 : Docker를 사용한 일관된 환경 제공
  10. CI/CD : 자동화된 배포 파이프라인 구축

 로드 밸런싱 알고리즘 선택 시에는 애플리케이션의 특성과 요구사항을 고려해야 하며, Nginx나 클라우드 제공 로드 밸런서를 활용하여 효과적으로 구현할 수 있습니다.

 클라우드 환경에서의 배포는 자동 스케일링과 고가용성을 쉽게 구현할 수 있게 해주며, 환경 변수를 통한 설정 관리로 유연성을 확보할 수 있습니다.

 세션 관리와 상태 유지가 필요한 애플리케이션의 경우, Redis와 같은 외부 저장소를 활용하거나 JWT를 사용한 무상태 아키텍처로 전환하는 것이 바람직합니다.

 서버리스 아키텍처는 자동 스케일링과 관리 오버헤드 감소의 이점을 제공하지만, 콜드 스타트와 실행 시간 제한 등의 제약사항을 고려해야 합니다.

 Kubernetes를 사용한 컨테이너 오케스트레이션은 복잡한 마이크로서비스 아키텍처의 배포와 관리를 단순화하고, 효율적인 리소스 활용을 가능하게 합니다.