[Nest.js] 고성능 글로벌 백엔드 시스템 설계 및 구현

궁금하면 500원·2024년 5월 30일

MSA&아키텍처

목록 보기
3/45

1. 시스템 설계의 중요성

시스템 설계는 웹 애플리케이션의 안정성, 확장성, 유지보수성을 결정짓는 중요한 부분입니다.

백엔드 시스템은 단순히 API를 제공하는 서버를 넘어서, 여러 개의 분산된 시스템을 잘 연결하여 하나의 일관된 서비스를 제공하는 역할을 합니다.

다양한 데이터베이스, 캐싱, 메시지 큐 등을 잘 설계해야 성능과 신뢰성을 높일 수 있습니다.

2. API 서버 설계

API 서버는 클라이언트와의 주요 인터페이스입니다.

API 서버는 데이터를 처리하고, 서비스 로직을 실행하며, 클라이언트에게 적절한 응답을 반환합니다.

시스템 설계에서 중요한 점은 API가 단순히 요청에 응답하는 것 외에도, API의 응답 시간이 짧고, 장애에 강하며, 쉽게 확장 가능해야 한다는 것입니다.

Nest.js로 API 서버 설계

Nest.js는 TypeScript 기반의 웹 프레임워크로, Angular에서 영감을 받았으며, 서버 사이드 애플리케이션을 쉽게 구축할 수 있도록 도와줍니다.

다음은 기본적인 API 서버 설계 예제입니다.

// app.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller('api')
export class AppController {
  @Get()
  getData(): string {
    return 'API 서버 작동 중';
  }
}

위 코드는 간단한 API 서버의 예시로, /api 엔드포인트에서 'API 서버 작동 중'이라는 문자열을 반환합니다.

3. 데이터베이스 설계 및 관리

백엔드 시스템의 데이터 저장은 가장 중요한 부분입니다.

관계형 데이터베이스(RDBMS)는 구조화된 데이터를 다루기에 적합하고, NoSQL 데이터베이스는 비구조화된 데이터나 대규모 분산 시스템에서 유용합니다.

이 두 가지를 어떻게 선택하고 조합할지 결정하는 것이 시스템 설계의 핵심입니다.

RDBMS와 NoSQL 설계

  • RDBMS (PostgreSQL, MySQL 등): ACID(Atomicity, Consistency, Isolation, Durability) 속성을 보장하며, 복잡한 쿼리와 관계형 데이터를 다룰 때 유리합니다.

  • NoSQL (MongoDB, Redis 등): 대규모 데이터를 수평적으로 확장할 수 있으며, 비정형 데이터와 높은 속도를 요구하는 애플리케이션에 적합합니다.

예제: PostgreSQL을 사용하는 간단한 데이터베이스 모델

// user.entity.ts (TypeORM 예시)
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;
}

4. 분산 트랜잭션 관리

분산 트랜잭션은 여러 서비스가 서로 다른 시스템에서 데이터를 처리하는 경우 트랜잭션을 일관되게 유지해야 하는 문제입니다.

이를 해결하기 위한 방법은 여러 가지가 있으며, 2PC (Two-Phase Commit), Saga 패턴 등이 일반적으로 사용됩니다.

예제: Nest.js에서 트랜잭션 관리

Nest.js에서 트랜잭션 관리는 TypeORM과 같은 ORM을 사용하여 구현할 수 있습니다.

다음은 트랜잭션을 사용하는 예제입니다.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async createUserWithTransaction() {
    const queryRunner = this.userRepository.manager.connection.createQueryRunner();
    await queryRunner.startTransaction();
    
    try {
      const user = await queryRunner.manager.save(User, { name: 'John', email: 'john@example.com' });
      await queryRunner.commitTransaction();
      return user;
    } catch (error) {
      await queryRunner.rollbackTransaction();
      throw error;
    } finally {
      await queryRunner.release();
    }
  }
}

5. 캐싱을 통한 성능 최적화

캐싱은 데이터베이스 부하를 줄이고 시스템의 응답 속도를 높이는 데 중요한 역할을 합니다. Redis와 같은 인메모리 캐시를 사용하면 성능을 크게 향상시킬 수 있습니다.

예제: Redis 캐시 적용 (Nest.js)

import { Injectable } from '@nestjs/common';
import { InjectRedis } from '@nestjs/redis';
import { Redis } from 'ioredis';

@Injectable()
export class CacheService {
  constructor(@InjectRedis() private readonly redis: Redis) {}

  async getDataFromCache(key: string): Promise<string> {
    const data = await this.redis.get(key);
    return data || 'No data found';
  }

  async setDataToCache(key: string, value: string): Promise<void> {
    await this.redis.set(key, value);
  }
}

6. 메시지 큐를 이용한 비동기 처리

비동기 처리는 시스템의 성능을 높이고, 긴 작업을 백그라운드에서 처리할 수 있게 합니다.
메시지 큐(예: RabbitMQ, Kafka)를 사용하면 비동기 처리를 안전하고 효율적으로 관리할 수 있습니다.

예제: Nest.js에서 RabbitMQ 사용

import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class MessageQueueService {
  constructor(@InjectQueue('emailQueue') private readonly emailQueue: Queue) {}

  async sendEmailAsync(email: string) {
    await this.emailQueue.add('sendEmail', { email });
  }
}

7. 시스템 통합 (EAI)

EAI (Enterprise Application Integration)은 기업의 다양한 시스템과 애플리케이션을 통합하여 일관된 데이터 흐름을 보장하고, 시스템 간의 커뮤니케이션을 원활하게 하는 기술적 과정입니다.

EAI는 대규모 기업에서 중요한 역할을 합니다.

서로 다른 시스템들이 데이터를 교환하고 동기화할 수 있도록 하여, 정보의 일관성을 유지하고, 시스템 간의 결합도를 낮추며, 애플리케이션이 쉽게 확장되고 유지보수 가능하게 만듭니다.

EAI 구성 요소

1. ESB (Enterprise Service Bus)

ESB는 서로 다른 시스템 간의 통신을 관리하는 미들웨어입니다.
ESB를 사용하면 서로 다른 시스템 간의 통합을 쉽게 할 수 있으며, 데이터를 표준화하여 처리합니다.

2. API Gateway

API Gateway는 여러 마이크로서비스와 애플리케이션 간의 요청을 처리하고, 인증 및 라우팅 기능을 제공합니다.

이는 서비스 간의 요청을 처리하고, 복잡한 로직을 단순화하는 데 유용합니다.

3. 메시지 큐

시스템 간의 비동기 통신을 관리하고 메시지를 큐에 저장하여 후속 처리를 할 수 있도록 합니다.
이를 통해 서로 다른 시스템에서 데이터 처리 시간을 최적화하고 장애를 처리할 수 있습니다.

4. 데이터 변환기 (Data Transformation)

서로 다른 시스템에서 데이터를 주고받을 때 형식이나 구조가 다를 수 있기 때문에, 데이터를 적절한 형식으로 변환하는 기능이 필요합니다.
이를 통해 서로 다른 시스템 간의 호환성을 보장합니다.

EAI 구현 예시 (Nest.js + RabbitMQ)

EAI 시스템을 구현하는데 RabbitMQ와 Nest.js를 결합하여 예시 코드를 보여드리겠습니다. RabbitMQ는 메시지 큐로 비동기 메시지 전송을 관리하는 시스템입니다.

// 메시지 큐를 이용한 EAI 예시
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class EAIService {
  constructor(@InjectQueue('eaiQueue') private readonly eaiQueue: Queue) {}

  // 메시지 큐에 작업을 추가하는 메소드
  async addTaskToQueue(data: any) {
    await this.eaiQueue.add('processData', { data });
  }
}

위의 코드는 Nest.js에서 Bull 패키지를 사용하여 메시지 큐에 작업을 추가하는 예시입니다.
이 작업은 비동기적으로 처리될 수 있습니다.

8. 글로벌 스케일 시스템 디자인

글로벌 스케일 시스템 디자인은 전 세계적으로 높은 트래픽을 처리하고, 다양한 지역에서 빠르게 데이터를 제공할 수 있도록 설계하는 것입니다.

글로벌 시스템은 단순히 서버를 여러 개 배치하는 것만으로 해결되지 않으며, 트래픽 관리, 데이터 일관성, 빠른 응답 속도를 보장하는 여러 기법들이 필요합니다.

주요 고려 사항

1. CDN (Content Delivery Network)

전 세계적으로 분산된 서버를 사용하여 사용자의 요청을 가장 가까운 서버에서 처리할 수 있도록 합니다.

이를 통해 웹사이트의 로딩 시간을 줄이고, 성능을 향상시킵니다.

2. 지역 분산 (Geographical Distribution)

서버와 데이터베이스를 여러 지역에 배치하여, 각 지역의 사용자가 가장 가까운 데이터 센터에서 서비스를 받을 수 있도록 합니다.

이는 데이터 처리 속도와 응답 시간을 줄이는 데 유효합니다.

3. Geo-Replication

데이터베이스나 파일을 여러 지역에 복제하여 각 지역에서 빠르게 데이터를 처리할 수 있도록 합니다.

예를 들어, AWS의 RDS는 다중 지역에서 데이터를 복제하는 기능을 제공합니다.

4. 장애 처리 전략 (Failover)

서버나 데이터베이스에 장애가 발생할 경우, 즉시 대체 서버나 데이터베이스로 전환하여 서비스의 중단을 최소화할 수 있도록 합니다.

예시: 글로벌 시스템을 위한 설계

  • 로드 밸런서: 여러 서버에 트래픽을 분산시켜 부하를 균등하게 분배합니다.
  • 데이터 복제 및 캐싱: Redis, DynamoDB 등을 사용하여 글로벌 분산 데이터베이스와 캐시 시스템을 구축합니다.

정리하기....

1. API Gateway 예시 (Nest.js)

API Gateway는 클라이언트와 여러 서비스 간의 요청을 라우팅하고, 인증을 처리하는 중요한 역할을 합니다.

// gateway.service.ts
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';

@Injectable()
export class GatewayService {
  constructor(private readonly httpService: HttpService) {}

  // 외부 API 호출 예시
  async fetchDataFromOtherService(endpoint: string) {
    const response = await this.httpService.get(endpoint).toPromise();
    return response.data;
  }
}

2. Geo-Replication을 위한 데이터베이스 구성 (Nest.js + TypeORM)

글로벌 시스템을 위한 데이터베이스는 여러 지역에 데이터를 복제하여 빠르게 처리할 수 있어야 합니다.
TypeORM과 PostgreSQL을 사용하는 예시입니다.

// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;

  @Column()
  country: string;  // 데이터를 지역별로 관리
}

// app.module.ts
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'database-server',
      port: 5432,
      username: 'test',
      password: 'test',
      database: 'global_db',
      entities: [User],
      synchronize: true,
      logging: true,
    }),
    TypeOrmModule.forFeature([User]),
  ],
  providers: [UserService],
})
export class AppModule {}

3. 메시지 큐를 이용한 비동기 처리 (Nest.js + RabbitMQ)

메시지 큐를 사용하여 비동기적으로 데이터를 처리하고 시스템의 성능을 최적화합니다.

// async-processor.service.ts
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class AsyncProcessorService {
  constructor(@InjectQueue('taskQueue') private readonly taskQueue: Queue) {}

  async processTask(taskData: any) {
    // 비동기 작업을 메시지 큐에 추가
    await this.taskQueue.add('processTask', { taskData });
  }
}

결론

시스템 통합(EAI)과 글로벌 스케일 시스템 디자인은 대규모 애플리케이션을 설계할 때 핵심적인 부분입니다.

Nest.js는 이러한 시스템을 구축하는 데 필요한 강력한 도구들을 제공하며, API Gateway, 메시지 큐, 데이터 복제 및 캐싱 전략을 활용하여 시스템의 확장성과 성능을 최적화할 수 있습니다.

각 예제 코드에서 제공한 내용들은 실제 프로젝트에서 쉽게 적용할 수 있는 기본적인 방법들을 다루고 있으며, 이를 바탕으로 더 복잡한 시스템을 설계할 수 있습니다.

profile
에러가 나도 괜찮아 — 그건 내가 배우고 있다는 증거야.

0개의 댓글