⚡ 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에서 편집