⚡ Serverless Full Stack

18 min read
aws-lambdadynamodbapi-gatewaycloudfronts3cognitosqseventbridgecdktypescriptreactserverlesspay-per-useauto-scalingzero-ops

운영 부담 없이 무한 확장 가능한 서버리스 아키텍처 - AWS Lambda, DynamoDB, API Gateway로 구축하는 현대적 풀스택

⚡ Serverless Full Stack

서버 관리 없이 무한 확장 가능한 현대적 풀스택 아키텍처
"Zero ops, infinite scale, pay only for what you use" - 진정한 클라우드 네이티브


🎯 이 스택이 적합한 경우

✅ 추천하는 경우

  • 불규칙한 트래픽 패턴 (이벤트성, 계절성)
  • 빠른 프로토타이핑과 실험
  • 운영 인력이 없는 소규모 팀
  • 글로벌 배포가 필요한 서비스
  • 비용 최적화가 중요한 프로젝트

❌ 다른 스택을 고려해야 할 경우


🧩 핵심 구성요소

Serverless 컴퓨팅

// Lambda 함수 예제 (TypeScript)
import { APIGatewayProxyHandler } from "aws-lambda";
import { DynamoDB } from "aws-sdk";
import { z } from "zod";

const dynamodb = new DynamoDB.DocumentClient();

// 입력 검증 스키마
const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(2),
  role: z.enum(["user", "admin"]).default("user"),
});

export const createUser: APIGatewayProxyHandler = async (event) => {
  try {
    // 요청 파싱 및 검증
    const body = JSON.parse(event.body || "{}");
    const userData = CreateUserSchema.parse(body);

    // DynamoDB에 저장
    const user = {
      pk: `USER#${userData.email}`,
      sk: `PROFILE`,
      ...userData,
      createdAt: new Date().toISOString(),
      userId: crypto.randomUUID(),
    };

    await dynamodb
      .put({
        TableName: process.env.USERS_TABLE!,
        Item: user,
        ConditionExpression: "attribute_not_exists(pk)",
      })
      .promise();

    // EventBridge로 이벤트 발행
    await eventbridge
      .putEvents({
        Entries: [
          {
            Source: "user.service",
            DetailType: "UserCreated",
            Detail: JSON.stringify(user),
          },
        ],
      })
      .promise();

    return {
      statusCode: 201,
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ user }),
    };
  } catch (error) {
    return handleError(error);
  }
};

Infrastructure as Code (CDK)

// AWS CDK로 인프라 정의
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";

export class ServerlessStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // DynamoDB 테이블
    const usersTable = new dynamodb.Table(this, "UsersTable", {
      partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
      sortKey: { name: "sk", type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      pointInTimeRecovery: true,
      stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,
    });

    // Lambda 함수
    const createUserFn = new lambda.Function(this, "CreateUserFunction", {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: "createUser.handler",
      code: lambda.Code.fromAsset("dist"),
      environment: {
        USERS_TABLE: usersTable.tableName,
      },
      timeout: cdk.Duration.seconds(30),
      memorySize: 1024,
    });

    // API Gateway
    const api = new apigateway.RestApi(this, "ServerlessAPI", {
      restApiName: "Serverless Service",
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS,
      },
    });

    // Lambda 통합
    const usersResource = api.root.addResource("users");
    usersResource.addMethod(
      "POST",
      new apigateway.LambdaIntegration(createUserFn),
    );

    // 권한 부여
    usersTable.grantReadWriteData(createUserFn);
  }
}

프론트엔드 (S3 + CloudFront)

// React 앱 배포 설정
const websiteBucket = new s3.Bucket(this, "WebsiteBucket", {
  websiteIndexDocument: "index.html",
  websiteErrorDocument: "error.html",
  publicReadAccess: false,
  blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});

// CloudFront 배포
const distribution = new cloudfront.CloudFrontWebDistribution(
  this,
  "Distribution",
  {
    originConfigs: [
      {
        s3OriginSource: {
          s3BucketSource: websiteBucket,
          originAccessIdentity: oai,
        },
        behaviors: [
          {
            isDefaultBehavior: true,
            compress: true,
            allowedMethods:
              cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
            cachedMethods:
              cloudfront.CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS,
            viewerProtocolPolicy:
              cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
            minTtl: cdk.Duration.seconds(0),
            defaultTtl: cdk.Duration.days(1),
            maxTtl: cdk.Duration.days(365),
          },
        ],
      },
    ],
    priceClass: cloudfront.PriceClass.PRICE_CLASS_100,
  },
);

이벤트 기반 아키텍처

// EventBridge 규칙과 타겟
new events.Rule(this, "UserCreatedRule", {
  eventPattern: {
    source: ["user.service"],
    detailType: ["UserCreated"],
  },
  targets: [
    new targets.LambdaFunction(sendWelcomeEmailFn),
    new targets.SqsQueue(analyticsQueue),
    new targets.LambdaFunction(updateSearchIndexFn),
  ],
});

// Step Functions로 복잡한 워크플로우
const definition = new sfn.Task(this, "ProcessOrder")
  .next(
    new sfn.Parallel(this, "ParallelProcessing")
      .branch(checkInventory)
      .branch(validatePayment)
      .branch(calculateShipping),
  )
  .next(
    new sfn.Choice(this, "AllChecksPass?")
      .when(sfn.Condition.booleanEquals("$.allValid", true), fulfillOrder)
      .otherwise(cancelOrder),
  );

💰 비용 분석 (사용량 기반)

| 서비스 | 무료 티어 | 가격 | 월 10K 요청 | 월 1M 요청 | | --------------- | ------------------------ | -------------------------------------------- | ----------- | ----------- | | Lambda | 1M 요청/월400K GB-초 | $0.20/1M 요청$0.0166/GB-초 | $0 | ~$20 | | API Gateway | 1M 요청/월 (12개월) | $3.50/1M 요청 | $0 | ~$3.50 | | DynamoDB | 25GB 저장25 RCU/WCU | On-demand:$0.25/1M 읽기$1.25/1M 쓰기 | ~$1 | ~$50 | | S3 | 5GB (12개월) | $0.023/GB | ~$0.50 | ~$5 | | CloudFront | 1TB 전송/월 (12개월) | $0.085/GB | $0 | ~$10 | | Cognito | 50K MAU 무료 | $0.0055/MAU | $0 | ~$5 | | 총계 | - | - | ~$1.50 | ~$93.50 |

💡 비용 최적화 팁

// 1. Lambda 메모리 최적화
// AWS Lambda Power Tuning으로 최적 메모리 찾기
const optimalMemory = {
  simpleAPI: 512, // MB - 간단한 CRUD
  dataProcessing: 1024, // MB - 데이터 처리
  aiInference: 3008, // MB - ML 추론
};

// 2. DynamoDB 비용 절감
const costOptimizedTable = new dynamodb.Table(this, "OptimizedTable", {
  // On-Demand vs Provisioned 선택
  billingMode: dynamodb.BillingMode.PROVISIONED,
  readCapacity: 5,
  writeCapacity: 5,
  // Auto Scaling 설정
  autoScaleReadCapacity: {
    minCapacity: 1,
    maxCapacity: 100,
    targetUtilizationPercent: 70,
  },
});

// 3. S3 라이프사이클 정책
websiteBucket.addLifecycleRule({
  id: "MoveOldFilesToGlacier",
  transitions: [
    {
      storageClass: s3.StorageClass.GLACIER,
      transitionAfter: cdk.Duration.days(90),
    },
  ],
  expiration: cdk.Duration.days(365),
});

🚀 구현 로드맵

Week 1: 기초 설정

# AWS CDK 프로젝트 초기화
npx cdk init app --language typescript
npm install @aws-cdk/aws-lambda @aws-cdk/aws-apigateway

# 로컬 개발 환경
npm install -D @serverless/offline
npm install -D aws-sam-cli

# 프로젝트 구조
serverless-app/
├── infra/           # CDK 인프라 코드
├── backend/         # Lambda 함수들
├── frontend/        # React 앱
└── shared/          # 공통 타입, 유틸

Week 2: 백엔드 구현

// Lambda 레이어로 공통 의존성 관리
const commonLayer = new lambda.LayerVersion(this, "CommonLayer", {
  code: lambda.Code.fromAsset("layers/common"),
  compatibleRuntimes: [lambda.Runtime.NODEJS_18_X],
  description: "Common dependencies",
});

// 함수별 최적화 설정
const functions = {
  // 빠른 응답이 필요한 API
  getUser: {
    memorySize: 512,
    timeout: cdk.Duration.seconds(10),
    reservedConcurrentExecutions: 100,
  },
  // 무거운 처리 작업
  processData: {
    memorySize: 3008,
    timeout: cdk.Duration.minutes(15),
    reservedConcurrentExecutions: 10,
  },
};

Week 3: 프론트엔드 및 인증

// Cognito 사용자 풀
const userPool = new cognito.UserPool(this, "UserPool", {
  selfSignUpEnabled: true,
  signInAliases: { email: true },
  autoVerify: { email: true },
  standardAttributes: {
    email: { required: true, mutable: false },
  },
  passwordPolicy: {
    minLength: 8,
    requireLowercase: true,
    requireUppercase: true,
    requireDigits: true,
    requireSymbols: true,
  },
});

// React 앱에서 Amplify 사용
import { Amplify, Auth } from "aws-amplify";

Amplify.configure({
  Auth: {
    region: "us-east-1",
    userPoolId: "us-east-1_xxxxx",
    userPoolWebClientId: "xxxxx",
  },
});

Week 4: 모니터링 및 최적화

// X-Ray 추적
import * as AWSXRay from "aws-xray-sdk-core";
const AWS = AWSXRay.captureAWS(require("aws-sdk"));

// CloudWatch 대시보드
new cloudwatch.Dashboard(this, "ServerlessDashboard", {
  widgets: [
    [
      new cloudwatch.GraphWidget({
        title: "Lambda Invocations",
        left: [lambdaInvocations],
        right: [lambdaErrors],
      }),
      new cloudwatch.GraphWidget({
        title: "API Gateway Latency",
        left: [apiLatency],
      }),
    ],
    [
      new cloudwatch.GraphWidget({
        title: "DynamoDB Consumed Capacity",
        left: [dynamoReadCapacity],
        right: [dynamoWriteCapacity],
      }),
    ],
  ],
});

🏗️ 아키텍처 패턴

이벤트 기반 패턴

// Event-Driven Architecture
interface UserEvent {
  eventType: "USER_CREATED" | "USER_UPDATED" | "USER_DELETED";
  userId: string;
  timestamp: string;
  data: any;
}

// 이벤트 발행
const publishEvent = async (event: UserEvent) => {
  await eventbridge
    .putEvents({
      Entries: [
        {
          Source: "user.service",
          DetailType: event.eventType,
          Detail: JSON.stringify(event),
          Time: new Date(),
        },
      ],
    })
    .promise();
};

// 이벤트 처리
export const handleUserEvent: EventBridgeHandler<
  string,
  UserEvent,
  void
> = async (event) => {
  const { eventType, userId, data } = event.detail;

  switch (eventType) {
    case "USER_CREATED":
      await Promise.all([
        sendWelcomeEmail(userId),
        createAnalyticsProfile(userId),
        indexUserForSearch(data),
      ]);
      break;
    // ... 다른 이벤트 처리
  }
};

서버리스 마이크로서비스

services/
├── user-service/
│   ├── functions/
│   │   ├── createUser/
│   │   ├── getUser/
│   │   └── updateUser/
│   └── infrastructure/
├── order-service/
│   ├── functions/
│   └── infrastructure/
└── shared/
    ├── types/
    ├── utils/
    └── layers/

API 응답 캐싱

// API Gateway 캐싱 설정
const getUserMethod = userResource.addMethod(
  "GET",
  new apigateway.LambdaIntegration(getUserFn),
  {
    requestParameters: {
      "method.request.path.userId": true,
    },
    methodResponses: [
      {
        statusCode: "200",
        responseParameters: {
          "method.response.header.Cache-Control": true,
        },
      },
    ],
  },
);

// Lambda에서 캐시 헤더 설정
return {
  statusCode: 200,
  headers: {
    "Cache-Control": "max-age=300", // 5분 캐싱
    "Content-Type": "application/json",
  },
  body: JSON.stringify(userData),
};

🔄 대안 및 변형

멀티 클라우드 옵션

| AWS | Azure | GCP | Cloudflare | | ----------- | -------------- | ----------------- | ---------- | | Lambda | Functions | Cloud Functions | Workers | | DynamoDB | Cosmos DB | Firestore | Workers KV | | API Gateway | API Management | API Gateway | - | | S3 | Blob Storage | Cloud Storage | R2 | | CloudFront | CDN | Cloud CDN | CDN | | Cognito | AD B2C | Identity Platform | Access |

엣지 우선 변형

// Cloudflare Workers 예제
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    // KV 스토어에서 데이터 조회
    const cached = await env.KV.get(url.pathname);
    if (cached) {
      return new Response(cached, {
        headers: { "Content-Type": "application/json" },
      });
    }

    // 오리진 요청
    const response = await fetch(request);
    const data = await response.json();

    // 엣지에 캐싱
    ctx.waitUntil(
      env.KV.put(url.pathname, JSON.stringify(data), {
        expirationTtl: 300,
      }),
    );

    return new Response(JSON.stringify(data));
  },
};

🏆 성공 사례

Coca-Cola (Vending Pass)

  • 규모: 100개국, 수백만 사용자
  • 비용: 기존 대비 65% 절감
  • 특징: 완전 서버리스, 자동 확장

iRobot

  • 이전: EC2 기반 모놀리스
  • 현재: 100% 서버리스
  • 성과: 운영 비용 80% 절감

FINRA

  • 규모: 일일 370억 이벤트 처리
  • 아키텍처: Lambda + Kinesis + S3
  • 비용: 기존 온프레미스 대비 50% 절감

📚 필수 리소스

학습 자료

도구 및 프레임워크

커뮤니티


💬 실무자 조언

"서버리스는 코드만 작성하면 된다고 생각하기 쉽지만, 분산 시스템의 모든 복잡성은 그대로입니다. 특히 디버깅이 어려워요." - @serverless_veteran

"콜드 스타트 때문에 고민 많았는데, Provisioned Concurrency로 해결했습니다. 비용은 좀 들지만 일관된 성능이 중요하면 필수예요." - @performance_focused

"DynamoDB 모델링이 핵심이에요. Single Table Design 제대로 못하면 나중에 마이그레이션 지옥입니다." - @nosql_expert

"비용 모니터링 꼭 하세요. 무한 루프 한 번이면 월 청구서가 폭탄됩니다. Budget Alert는 필수!" - @cost_conscious_dev


⚡ 성능 최적화

Lambda 콜드 스타트 최소화

// 1. 번들 크기 최적화
// webpack.config.js
module.exports = {
  externals: ["aws-sdk"], // SDK는 Lambda 런타임에 포함
  optimization: {
    minimize: true,
    usedExports: true,
  },
};

// 2. 초기화 코드 최적화
let dynamoClient: DynamoDB.DocumentClient;

const getClient = () => {
  if (!dynamoClient) {
    dynamoClient = new DynamoDB.DocumentClient({
      httpOptions: {
        connectTimeout: 5000,
        timeout: 5000,
      },
    });
  }
  return dynamoClient;
};

// 3. Provisioned Concurrency
const criticalFunction = new lambda.Function(this, "CriticalFunction", {
  // ... 다른 설정
  provisionedConcurrentExecutions: 5,
});

DynamoDB 최적화

// 배치 작업으로 비용 절감
const batchWrite = async (items: any[]) => {
  const chunks = chunk(items, 25); // DynamoDB 배치 제한

  for (const chunk of chunks) {
    await dynamodb
      .batchWrite({
        RequestItems: {
          [tableName]: chunk.map((item) => ({
            PutRequest: { Item: item },
          })),
        },
      })
      .promise();
  }
};

🚨 주의사항

제한사항

  • Lambda 실행 시간: 최대 15분
  • API Gateway 타임아웃: 29초
  • Lambda 페이로드: 6MB (동기), 256KB (비동기)
  • DynamoDB 아이템 크기: 400KB

모범 사례

  1. 멱등성: 모든 함수는 멱등적으로 설계
  2. 에러 처리: DLQ (Dead Letter Queue) 필수
  3. 보안: 최소 권한 원칙 적용
  4. 모니터링: CloudWatch Logs Insights 활용

마지막 업데이트: 2025-01-28
기여하기: GitHub에서 편집

Found this helpful? Share it with others!
Tweet

🔗 Related Content

You might also be interested in these articles

🏢 company

🏢 Canva 기술 스택 분석

캔바가 1억 명 이상의 사용자에게 쉽고 강력한 디자인 도구를 제공하는 기술 스택 심층 분석 - Java, TypeScript, AWS로 구축한 대규모 디자인 플랫폼

32 min read
canva, java +10
Read more
🏗️ stack

🚀 Modern SaaS MVP Stack

4주 안에 SaaS를 출시하기 위한 검증된 기술 스택 - Next.js, Supabase, Stripe로 빠르게 시작하기

8 min read
nextjs, supabase +13
Read more
🏗️ stack

🔄 Realtime Collaboration Stack

Figma, Notion 같은 실시간 협업 앱 구축을 위한 기술 스택 - WebSocket, CRDT, WebRTC로 만드는 동시 편집 시스템

21 min read
nodejs, socketio +13
Read more
🏢 company

🏢 Figma 기술 스택 분석

피그마가 브라우저에서 실시간 협업 디자인 툴을 구현한 기술 스택 심층 분석 - C++, WebAssembly, WebGL로 만든 차세대 디자인 플랫폼

22 min read
figma, c++ +11
Read more
🏢 company

🏢 Notion 기술 스택 분석

노션이 수천만 사용자에게 All-in-One 워크스페이스를 제공하는 기술 스택 심층 분석 - 블록 기반 에디터, 실시간 협업, 그리고 확장 가능한 데이터베이스

24 min read
notion, typescript +11
Read more

Found this helpful?

Help us improve this content by contributing on GitHub or sharing your feedback with the community.