🔗 나만의 단축 URL 서비스

60

왜 URL 단축 서비스를 만들까?

여러분이 마케팅 팀에서 일하고 있다고 상상해 보세요. 새 캠페인을 시작하면서 SNS에 링크를 올려야 합니다. 그런데 링크가 이렇게 생겼습니다:

URL 단축 서비스 아키텍처 — 생성과 리다이렉트 두 가지 흐름
코드
https://www.example.com/products/2024-spring-sale/category?utm_source=instagram&utm_medium=story&utm_campaign=spring_launch_2024&ref=homepage_banner_v3

이 링크를 인스타그램 바이오에 넣으려니 너무 길고, SMS로 보내면 두 줄을 차지하고, QR 코드로 만들면 점이 빽빽해서 인식이 안 됩니다.

그래서 등장한 것이 URL 단축 서비스입니다. bit.ly, tinyurl 같은 서비스가 대표적이죠. https://short.link/abc123 처럼 짧은 주소로 변환하고, 누군가 그 주소를 클릭하면 원본 페이지로 자동 이동시킵니다.

이 서비스가 비즈니스에서 중요한 이유는 단순한 "URL 줄이기" 이상입니다:

  • 클릭 추적: 언제, 어디서, 몇 명이 클릭했는지 데이터 수집
  • A/B 테스트: 같은 페이지에 다른 단축 URL을 붙여 어떤 채널이 효과적인지 비교
  • 브랜딩: 자체 도메인으로 단축 URL을 만들면 신뢰도 향상
  • 만료 관리: 이벤트가 끝나면 자동으로 URL을 비활성화

오늘 이 실습에서는 bit.ly의 핵심 기능을 API Gateway + Lambda + DynamoDB로 직접 구현합니다. 서버 한 대 없이, 완전히 서버리스로 만들어 보겠습니다. 트래픽이 0이면 비용도 0이고, 갑자기 트래픽이 100배 늘어도 자동으로 처리됩니다.

실습을 시작하기 전에 AWS 콘솔에 로그인되어 있는지 확인하세요. 리전은 ap-northeast-2 (서울) 을 사용합니다.

아키텍처 개요

URL 단축 서비스 구축 5단계 실습 가이드

서비스 흐름

다이어그램 로딩 중...
URL 단축 서비스 요청 흐름

비용 예측

비용 계산기

1시간
0h24h
API Gateway

1,000 요청당 $3.50

$0.0040
Lambda

100만 요청당 $0.20 + 실행시간

$0.0020
DynamoDB

쓰기 $1.25/백만, 읽기 $0.25/백만

$0.0030
예상 총 비용$0.0090

* 실제 비용은 AWS 요금 정책에 따라 달라질 수 있습니다.

Step 1: DynamoDB 테이블 생성

DynamoDB는 URL 매핑 정보를 저장하는 데이터베이스 역할을 합니다. 각 단축 URL에 대해 원본 URL, 생성 시간, 클릭 수를 저장합니다.

  1. AWS 콘솔 상단 검색창에 DynamoDB를 입력하고 서비스를 클릭합니다
  2. 왼쪽 메뉴에서 테이블 클릭 후 오른쪽 상단의 테이블 생성 버튼을 클릭합니다
  3. 테이블 이름: lab-urls 입력
  4. 파티션 키: shortId 입력 후 타입은 문자열(S) 을 선택합니다
  5. 테이블 설정에서 설정 사용자 지정 을 선택하고, 읽기/쓰기 용량 모드를 온디맨드 로 변경합니다
  6. 나머지는 기본값 그대로 두고 테이블 생성 클릭
  7. 상태가 활성 으로 바뀔 때까지 약 10-20초 대기합니다
코드
aws dynamodb create-table \
  --table-name lab-urls \
  --attribute-definitions AttributeName=shortId,AttributeType=S \
  --key-schema AttributeName=shortId,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

테이블 항목에는 shortId, originalUrl, createdAt, clickCount 필드를 저장합니다. DynamoDB는 스키마리스이므로 테이블 생성 시에는 키만 정의하면 됩니다. 나머지 필드는 데이터를 넣을 때 자유롭게 추가할 수 있습니다.

Step 2: URL 생성 Lambda 함수

이 Lambda 함수는 원본 URL을 받아서 8자리 단축 코드를 생성하고 DynamoDB에 저장합니다.

  1. AWS 콘솔 상단 검색창에 Lambda를 입력하고 서비스 클릭
  2. 함수 생성 버튼 클릭 → 새로 작성 선택
  3. 함수 이름: lab-url-create 입력
  4. 런타임: Node.js 20.x 선택 (Python 3.12도 가능하지만 이 실습에서는 Node.js 사용)
  5. 기본 실행 역할 변경 펼치기 → 기본 Lambda 권한을 가진 새 역할 생성 선택
  6. 함수 생성 클릭
  7. 함수가 생성되면 구성 탭 → 권한 → 역할 이름 클릭 → IAM 콘솔로 이동
  8. 권한 추가정책 연결AmazonDynamoDBFullAccess 검색 후 연결
  9. Lambda 콘솔로 돌아와 구성 탭 → 환경 변수TABLE_NAME = lab-urls 추가
  10. 코드 탭에서 아래 코드를 붙여넣고 Deploy 클릭
코드
aws lambda create-function \
  --function-name lab-url-create \
  --runtime nodejs20.x \
  --role arn:aws:iam::ACCOUNT_ID:role/lambda-execution-role \
  --handler index.handler \
  --zip-file fileb://url-create.zip

아래는 URL 생성 Lambda의 전체 코드입니다. Lambda 콘솔의 코드 편집기에 붙여넣으세요.

코드
// createUrl Lambda — URL 생성 함수 (Node.js 20.x 런타임)
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { PutCommand, DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import crypto from 'crypto';
 
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const TABLE = process.env.TABLE_NAME || 'lab-urls';
 
export const handler = async (event) => {
  const { url } = JSON.parse(event.body);
  const shortCode = crypto.createHash('sha256')
    .update(url + Date.now()).digest('hex').slice(0, 8);
 
  await client.send(new PutCommand({
    TableName: TABLE,
    Item: {
      shortId: shortCode,
      originalUrl: url,
      clickCount: 0,
      createdAt: new Date().toISOString(),
      ttl: Math.floor(Date.now() / 1000) + 86400 * 30, // 30일 후 자동 삭제
    },
  }));
 
  return {
    statusCode: 201,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      shortId: shortCode,
      shortUrl: `https://${event.headers.Host}/${shortCode}`,
    }),
  };
};

TABLE_NAME 환경 변수를 Lambda 설정에서 lab-urls로 지정하세요. SHA-256 해시의 앞 8자리를 단축 코드로 사용하며, TTL을 설정하여 30일 후 DynamoDB에서 자동 삭제됩니다.

자주 하는 실수: Lambda 함수를 만든 후 코드를 수정했는데 Deploy 버튼을 누르지 않는 경우가 많습니다. 코드 편집기 상단에 "배포되지 않은 변경 사항이 있습니다"라는 주황색 배너가 보이면 반드시 Deploy를 클릭하세요.

Step 3: 리다이렉트 Lambda 함수

이 함수는 단축 URL로 접속한 사용자를 원본 URL로 리다이렉트합니다. 동시에 클릭 수도 증가시킵니다.

진행률 0/8
  1. 1Lambda 콘솔 → 함수 생성 → 이름: lab-url-redirect, 런타임: Node.js 20.x
  2. 2Step 2와 같은 방식으로 IAM 역할에 AmazonDynamoDBFullAccess 정책을 연결합니다
  3. 3환경 변수에 TABLE_NAME = lab-urls 추가
  4. 4코드 작성: pathParameters에서 shortId 추출
  5. 5DynamoDB에서 shortId로 원본 URL 조회 (GetCommand 사용)
  6. 6clickCount를 1 증가 (UpdateCommand의 ADD 연산 사용 — 원자적 증가)
  7. 7HTTP 301 응답 헤더에 Location: 원본URL 설정하여 브라우저가 자동 리다이렉트하도록 합니다
  8. 8URL을 찾지 못하면 404 응답 반환

아래는 리다이렉트 Lambda의 전체 코드입니다.

코드
// redirectUrl Lambda — 리다이렉트 함수 (Node.js 20.x 런타임)
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { GetCommand, UpdateCommand, DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
 
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const TABLE = process.env.TABLE_NAME || 'lab-urls';
 
export const handler = async (event) => {
  const shortCode = event.pathParameters.shortId;
 
  const { Item } = await client.send(new GetCommand({
    TableName: TABLE,
    Key: { shortId: shortCode },
  }));
 
  if (!Item) {
    return { statusCode: 404, body: JSON.stringify({ error: 'URL을 찾을 수 없습니다' }) };
  }
 
  // Atomic counter — 클릭 수 원자적 증가
  await client.send(new UpdateCommand({
    TableName: TABLE,
    Key: { shortId: shortCode },
    UpdateExpression: 'ADD clickCount :inc',
    ExpressionAttributeValues: { ':inc': 1 },
  }));
 
  return {
    statusCode: 301,
    headers: { Location: Item.originalUrl },
  };
};

UpdateExpressionADD 연산은 동시 요청에서도 안전하게 카운터를 증가시킵니다. GetItemPutItem으로 덮어쓰는 방식은 동시성 문제가 발생할 수 있으므로 반드시 UpdateCommand를 사용하세요.

Step 4: API Gateway 엔드포인트 구성

API Gateway는 외부 클라이언트(브라우저, curl 등)와 Lambda 함수 사이의 "현관문" 역할을 합니다.

  1. AWS 콘솔 상단에서 API Gateway 검색 후 클릭
  2. REST API 카드에서 빌드 클릭 (HTTP API가 아닌 REST API를 선택하세요)
  3. 새 API 선택, API 이름: lab-url-shortener, 엔드포인트 유형: 리전 선택 후 API 생성 클릭
  4. 리소스 생성: 리소스 이름 shorten, 리소스 경로 /shorten리소스 생성 클릭
  5. /shorten 선택 → 메서드 생성 → POST → 통합 유형: Lambda 함수 → lab-url-create 선택 → 메서드 생성 클릭
  6. 루트(/)로 돌아가서 리소스 생성 → 리소스 이름: shortId, 리소스 경로에 {shortId} 입력 (중괄호 포함) → 리소스 생성 클릭
  7. /{shortId} 선택 → 메서드 생성 → GET → 통합 유형: Lambda 함수 → lab-url-redirect 선택 → 메서드 생성 클릭
  8. GET 메서드의 메서드 응답에서 응답 추가 → HTTP 상태: 301Location 헤더 추가
  9. API 배포 클릭 → 새 스테이지 → 스테이지 이름: prod배포
  10. 화면 상단에 표시되는 호출 URL을 복사하세요 (예: https://abc123.execute-api.ap-northeast-2.amazonaws.com/prod)
코드
# REST API 생성
aws apigateway create-rest-api \
  --name lab-url-shortener \
  --endpoint-configuration types=REGIONAL
 
# 리소스 및 메서드는 콘솔에서 구성을 권장합니다

Step 5: 리다이렉트 및 클릭 카운트 테스트

이제 완성된 서비스를 테스트해 봅시다. 터미널(명령 프롬프트)을 열어 아래 명령을 실행합니다.

진행률 0/6
  1. 1URL 단축 테스트 — 아래 curl 명령으로 URL을 단축합니다 (API_URL을 복사한 호출 URL로 교체): curl -X POST https://YOUR_API_URL/prod/shorten \ -H "Content-Type: application/json" \ -d '{"url": "https://aws.amazon.com/ko/lambda/"}'
  2. 2반환된 JSON에서 shortUrl 또는 shortId 값을 확인합니다
  3. 3리다이렉트 테스트 — 브라우저 주소창에 https://YOUR_API_URL/prod/{반환된shortId}를 입력하면 원본 페이지로 이동합니다
  4. 4클릭 카운트 확인 — DynamoDB 콘솔 → lab-urls 테이블 → 항목 탐색에서 해당 항목의 clickCount 값을 확인합니다
  5. 5여러 번 접속 후 clickCount가 정확히 증가하는지 확인합니다
  6. 6404 테스트 — 존재하지 않는 shortId로 접속하여 {"error": "URL을 찾을 수 없습니다"} 응답을 확인합니다

curl -v 옵션을 사용하면 301 응답 헤더의 Location 값을 확인할 수 있습니다. 브라우저는 자동으로 리다이렉트를 따라갑니다. curl -L을 사용하면 리다이렉트를 자동으로 따라가서 최종 페이지 HTML을 받습니다.

트러블슈팅

"Internal Server Error" (500 에러)가 나오면: Lambda 함수의 CloudWatch Logs를 확인하세요. Lambda 콘솔 → 함수 선택 → 모니터링 탭 → CloudWatch Logs 보기 클릭. 가장 흔한 원인은 DynamoDB 테이블 이름 불일치 또는 IAM 권한 부족입니다.

"Missing Authentication Token" 에러가 나오면: API Gateway에서 리소스 경로나 메서드를 잘못 설정한 경우입니다. /shorten에 POST 메서드가, /{shortId}에 GET 메서드가 올바르게 연결되어 있는지 확인하세요. 또한 API를 배포했는지 확인하세요 — 변경 후 재배포하지 않으면 이전 버전이 동작합니다.

리다이렉트가 안 되고 JSON 응답만 나오면: API Gateway 메서드 응답에 301 상태 코드와 Location 헤더가 추가되어 있는지 확인하세요. Lambda 프록시 통합을 사용하는 경우 Lambda에서 직접 statusCode: 301headers: { Location: url }을 반환해야 합니다.

"AccessDeniedException" 에러가 나오면: Lambda 실행 역할에 AmazonDynamoDBFullAccess 정책이 연결되어 있는지 확인하세요. IAM 콘솔 → 역할 → Lambda 실행 역할 선택 → 권한 탭에서 정책 목록을 확인합니다.

핵심 개념 확인

확장 아이디어

기본 서비스가 완성되었으니, 더 발전시켜 보세요:

  1. 커스텀 단축 코드: 사용자가 원하는 문자열(예: my-link)을 직접 지정할 수 있도록 POST 요청에 customCode 필드 추가
  2. 만료 시간 설정: DynamoDB TTL 기능을 활용하여 1시간, 1일, 7일 등 원하는 기간 후 자동 삭제
  3. 클릭 통계 API: GET /stats/{shortId} 엔드포인트를 추가하여 클릭 수, 생성일, 마지막 접속일 등 반환
  4. 중복 URL 방지: 같은 원본 URL이 이미 등록되어 있으면 기존 단축 코드를 반환하도록 GSI(Global Secondary Index) 활용
  5. QR 코드 생성: 단축 URL 생성 시 QR 코드 이미지도 함께 생성하여 S3에 저장

학습 정리

핵심 치트시트

리소스 정리

실습 완료 후 반드시 아래 순서대로 리소스를 정리하여 불필요한 과금을 방지하세요.

  1. API Gateway → API 삭제 (lab-url-shortener)
  2. Lambda 함수 삭제 (lab-url-create, lab-url-redirect)
  3. DynamoDB 테이블 삭제 (lab-urls)
  4. IAM 역할 정리 (Lambda 실행 역할)
  5. CloudWatch 로그 그룹 삭제