서버를 관리하지 않고도 서비스를 운영할 수 있다면?
"서버리스"라는 이름이 다소 오해를 불러일으킬 수 있지만, 서버가 없는 것이 아니라 서버 관리를 클라우드 제공자에게 위임하는 것입니다. 인프라 프로비저닝, OS 패치, 용량 계획, 스케일링 — 이 모든 운영 부담에서 벗어나 비즈니스 로직에만 집중할 수 있다는 것이 서버리스의 핵심 가치입니다.
Datadog의 2024 State of Serverless 보고서에 따르면, AWS Lambda 함수의 월간 호출 횟수가 전년 대비 33% 증가했으며, 서버리스를 "주요 컴퓨팅 방식"으로 채택한 기업은 47%에 달합니다. 스타트업부터 대기업까지, 서버리스는 이미 주류 아키텍처가 되었습니다.
---
AWS Lambda — 서버리스의 핵심
Lambda는 이벤트에 반응하여 코드를 실행하는 컴퓨팅 서비스입니다. 요청이 없으면 비용이 0이고, 트래픽이 급증하면 자동으로 수천 개의 인스턴스로 확장됩니다.
Lambda 함수 작성
```typescript // handler.ts import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
export const getUser = async ( event: APIGatewayProxyEvent ): Promise<APIGatewayProxyResult> => { try { const userId = event.pathParameters?.id; if (!userId) { return { statusCode: 400, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: '사용자 ID가 필요합니다.' }), }; }
const user = await dynamodb.get({ TableName: process.env.USERS_TABLE!, Key: { id: userId }, }).promise();
if (!user.Item) { return { statusCode: 404, body: JSON.stringify({ error: '사용자를 찾을 수 없습니다.' }), }; }
return { statusCode: 200, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=60', }, body: JSON.stringify(user.Item), }; } catch (error) { console.error('Error:', error); return { statusCode: 500, body: JSON.stringify({ error: '서버 오류가 발생했습니다.' }), }; } }; ```
Cold Start 최적화
Lambda의 가장 큰 단점은 Cold Start입니다. 일정 시간 호출이 없으면 실행 환경이 제거되고, 다음 호출 시 새로 초기화해야 합니다. Node.js 기준 Cold Start는 보통 100~500ms 정도이지만, VPC 연결이나 무거운 SDK 초기화가 있으면 수 초까지 늘어날 수 있습니다.
```typescript // ✅ 모듈 레벨에서 초기화 — Cold Start 시에만 실행 import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({}); const dynamodb = DynamoDBDocumentClient.from(client);
// 핸들러 함수 — 매 호출마다 실행 export const handler = async (event: any) => { // dynamodb 클라이언트는 재사용됨 }; ```
Provisioned Concurrency를 설정하면 사전에 "따뜻한" 인스턴스를 유지하여 Cold Start를 완전히 제거할 수 있습니다. 다만 비용이 발생하므로 지연 시간에 민감한 API에만 선택적으로 적용하세요.
---
API Gateway — REST/HTTP API 구축
API Gateway는 Lambda 함수에 HTTP 엔드포인트를 연결합니다.
```yaml # serverless.yml (Serverless Framework) service: user-api
provider: name: aws runtime: nodejs20.x region: ap-northeast-2 environment: USERS_TABLE: ${self:service}-users-${sls:stage}
functions: getUser: handler: handler.getUser events:
- httpApi:
path: /users/{id} method: get
createUser: handler: handler.createUser events:
- httpApi:
path: /users method: post
listUsers: handler: handler.listUsers events:
- httpApi:
path: /users method: get
resources: Resources: UsersTable: Type: AWS::DynamoDB::Table Properties: TableName: ${self:provider.environment.USERS_TABLE} BillingMode: PAY_PER_REQUEST AttributeDefinitions:
- AttributeName: id
AttributeType: S KeySchema:
- AttributeName: id
KeyType: HASH ```
HTTP API(v2)는 REST API(v1)보다 약 70% 저렴하고 지연 시간도 더 짧습니다. JWT 인증이나 CORS 같은 기본 기능만 필요하다면 HTTP API를 선택하는 것이 경제적입니다.
---
DynamoDB — 서버리스 네이티브 데이터베이스
DynamoDB는 완전 관리형 NoSQL 데이터베이스로, Lambda와 궁합이 가장 좋습니다. 커넥션 풀이 필요 없고, 자동 확장이 가능하며, 한 자릿수 밀리초의 응답 시간을 제공합니다.
```typescript // 단일 테이블 설계 (Single Table Design) // PK와 SK를 활용하여 여러 엔티티를 하나의 테이블에 저장
const params = { TableName: 'MainTable', Item: { PK: `USER#${userId}`, // Partition Key SK: `PROFILE`, // Sort Key name: '김개발', email: '[email protected]', createdAt: new Date().toISOString(), GSI1PK: `EMAIL#[email protected]`, // Global Secondary Index }, };
// 같은 테이블에 주문 데이터도 저장 const orderParams = { TableName: 'MainTable', Item: { PK: `USER#${userId}`, SK: `ORDER#${orderId}`, totalAmount: 50000, status: 'COMPLETED', }, };
// 특정 사용자의 모든 주문 조회 const orders = await dynamodb.query({ TableName: 'MainTable', KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)', ExpressionAttributeValues: { ':pk': `USER#${userId}`, ':sk': 'ORDER#', }, }).promise(); ```
---
비동기 처리 — SQS, SNS, EventBridge
서버리스 아키텍처에서 비동기 처리는 핵심 패턴입니다.
SQS (Simple Queue Service): 메시지 큐. 작업을 대기열에 넣고 Lambda가 순서대로 처리합니다. 이미지 리사이즈, 이메일 발송 같은 작업에 적합하죠.
SNS (Simple Notification Service): Pub/Sub 메시지 전달. 하나의 이벤트를 여러 구독자에게 동시에 전달합니다. 주문 완료 시 이메일, SMS, 재고 업데이트를 동시에 트리거하는 팬아웃(fan-out) 패턴에 사용됩니다.
EventBridge: 이벤트 라우팅 서비스. 규칙 기반으로 이벤트를 다양한 대상(Lambda, SQS, Step Functions)으로 라우팅합니다.
```yaml # SQS 트리거 Lambda functions: processImage: handler: handler.processImage events:
- sqs:
arn: !GetAtt ImageQueue.Arn batchSize: 10 # 한 번에 10개 메시지 처리 maximumRetryAttempts: 3 # 최대 3회 재시도 ```
---
비용 최적화와 한계
서버리스의 과금 모델은 사용한 만큼만 지불하는 방식입니다. Lambda는 요청 수(100만 건당 $0.20)와 실행 시간(GB-초당 $0.0000166667)으로 과금됩니다.
트래픽이 적거나 변동이 큰 워크로드에서는 서버리스가 경제적이지만, 트래픽이 꾸준히 높으면 EC2나 컨테이너가 더 저렴할 수 있습니다. 대략적으로 월 100만 요청 이하에서는 서버리스가, 그 이상에서는 비용 비교가 필요합니다.
서버리스의 주요 제약사항도 알아두어야 합니다. Lambda의 최대 실행 시간은 15분, 메모리는 10GB, 동시 실행 수는 리전당 기본 1,000개입니다. 장시간 실행되는 작업이나 WebSocket 연결 유지 같은 요구사항에는 적합하지 않을 수 있습니다.
---
IaC — 코드로 인프라 관리하기
서버리스 인프라는 AWS 콘솔에서 클릭으로 만들 수 있지만, 프로덕션에서는 Infrastructure as Code(IaC)가 필수입니다. Serverless Framework, AWS SAM, AWS CDK, Terraform 중 하나를 선택하세요.
```typescript // AWS CDK (TypeScript) 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';
const fn = new lambda.Function(this, 'GetUserFn', { runtime: lambda.Runtime.NODEJS_20_X, handler: 'handler.getUser', code: lambda.Code.fromAsset('dist'), environment: { USERS_TABLE: table.tableName }, memorySize: 256, timeout: cdk.Duration.seconds(10), });
const api = new apigateway.RestApi(this, 'UserApi'); api.root.addResource('users').addResource('{id}').addMethod('GET', new apigateway.LambdaIntegration(fn) ); ```
서버리스는 "서버가 없는 것"이 아니라 "서버에 대해 생각하지 않아도 되는 것"입니다. 모든 아키텍처가 그렇듯 만능이 아니며, 비용, 성능, 제약사항을 이해한 위에서 선택해야 합니다. API 백엔드, 이벤트 처리, 데이터 파이프라인 같은 워크로드에서 서버리스의 생산성 이점은 압도적이며, 특히 소규모 팀이나 스타트업에서 인프라 운영 부담 없이 서비스를 런칭할 수 있다는 것은 큰 경쟁력입니다.