⚡ Serverless Full Stack
운영 부담 없이 무한 확장 가능한 서버리스 아키텍처 - AWS Lambda, DynamoDB, API Gateway로 구축하는 현대적 풀스택
⚡ Serverless Full Stack
서버 관리 없이 무한 확장 가능한 현대적 풀스택 아키텍처
"Zero ops, infinite scale, pay only for what you use" - 진정한 클라우드 네이티브
🎯 이 스택이 적합한 경우
✅ 추천하는 경우
- 불규칙한 트래픽 패턴 (이벤트성, 계절성)
- 빠른 프로토타이핑과 실험
- 운영 인력이 없는 소규모 팀
- 글로벌 배포가 필요한 서비스
- 비용 최적화가 중요한 프로젝트
❌ 다른 스택을 고려해야 할 경우
- 지속적인 고부하 워크로드 → Container 기반 스택
- 15분 이상 실행 작업 → EC2/ECS 기반 스택
- WebSocket 집중 사용 → Realtime Stack
🧩 핵심 구성요소
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
모범 사례
- 멱등성: 모든 함수는 멱등적으로 설계
- 에러 처리: DLQ (Dead Letter Queue) 필수
- 보안: 최소 권한 원칙 적용
- 모니터링: CloudWatch Logs Insights 활용
마지막 업데이트: 2025-01-28
기여하기: GitHub에서 편집