infra
Platform

모듈 맵

[Database] 우리 서비스에 맞는 최적의 RDBMS vs NoSQL 고르기

0 / 37 완료

펼치기
0 / 37 완료0%

데이터베이스 설계 & 튜닝 · 30 / 37

[Database] 우리 서비스에 맞는 최적의 RDBMS vs NoSQL 고르기

PostgreSQL, MySQL, MongoDB, Redis를 언제 선택하는지 — 도메인, 쿼리 패턴, 운영 환경에 따른 실무 기준을 정리합니다

🚨INCIDENT ALERT
HIGH

새 프로젝트를 시작할 때 어떤 DB를 고를지 빨리 결정해야 하는 순간이 있습니다. 단순히 유명한 제품을 고르면 트랜잭션, 검색, 캐시, 분석 요구가 뒤늦게 충돌합니다. 선택 기준을 정리해두면 팀이 같은 근거로 의사결정할 수 있습니다.

이번 챕터에서 배울 것

DB 선택은 기술 성능 비교가 아닙니다. 도메인 특성, 쿼리 패턴, 팀의 운영 능력, 일관성 요구사항을 함께 고려해야 합니다. 이 모듈에서는 각 DB의 강점과 약점을 정리하고, 실무에서 반복되는 선택 패턴을 익힙니다.

  • 1쿼리 패턴, 정합성 요구, 운영 비용을 기준으로 DB를 선택할 수 있다
  • 2관계형, 문서형, 키-값 DB를 도메인 특성에 맞게 선택할 수 있다
  • 3폴리글랏 퍼시스턴스로 여러 DB를 조합해 사용하는 설계를 할 수 있다
  • 4CQRS로 쓰기와 읽기 저장소를 분리하는 전략을 적용할 수 있다
  • 5실무 시나리오에 맞춰 DB 선택을 판단하고 근거를 제시할 수 있다

데이터베이스 선택 기준 — 프로젝트에 맞는 DB 고르기

스타트업 초기에 "빠르게 만들어야 한다"는 이유로 모든 걸 MySQL에 때려박았다. 6개월 뒤, 실시간 피드 조회가 느려지고, 검색 쿼리가 인덱스를 못 타고, 세션 테이블이 DB 부하의 30%를 잡아먹고 있었다. 결국 당시에 팀 전체가 2주를 들여서 Redis와 Elasticsearch로 마이그레이션했다 — 처음부터 올바른 도구를 선택했다면 없었을 작업이었다. DB 선택은 기술 취향의 문제가 아니라 쿼리 패턴과 데이터 특성에 따른 공학적 판단이다. JOIN이 많으면 PostgreSQL, 유연한 문서 구조면 MongoDB, 마이크로초 응답이 필요하면 Redis — 이 기준을 알면 처음부터 맞는 도구를 고를 수 있고, 나중에 갈아엎는 고통을 피할 수 있다.


실습 환경 준비

💡개념

DB 선택의 핵심 질문 3가지

아키텍처 회의에서 DB를 뭘 쓸지 논쟁이 붙었습니다. "PostgreSQL이 더 빠르다", "MongoDB가 유연하다", "Redis 쓰면 된다"는 말들이 오갑니다. 벤치마크 스펙만 보고 선택하면 나중에 데이터 접근 패턴이 맞지 않아 마이그레이션을 하게 됩니다. DB 선택은 스펙 비교가 아니라 올바른 질문에서 시작됩니다. 어떤 질문을 던지느냐가 선택의 방향을 결정합니다.

DB 선택의 핵심 질문 3가지 — "어떤 DB가 좋아요?"가 아니라 ①데이터 구조(정형·중첩·K-V·시계열·그래프)②쿼리 패턴(JOIN·키 조회·전문검색·집계)③운영 환경(SQL 숙련도·관리형·글로벌·비용)을 물어야 함. 각 축의 답이 PostgreSQL·MongoDB·Redis·Elasticsearch 등 적합한 DB를 가리킴확대

스펙이 아닌 질문으로 선택한다

"어떤 DB가 더 빠른가?"는 잘못된 질문입니다. 올바른 질문은 다음 3가지입니다.

① 데이터 관계가 얼마나 복잡한가?

테이블 간 JOIN이 필수인 도메인과 단일 문서로 표현 가능한 도메인은 최적의 DB가 다릅니다.

관계 복잡도적합한 DB이유
테이블 간 JOIN이 필수PostgreSQL, MySQL외래키, 트랜잭션, 인덱스 결합
단일 문서로 표현 가능MongoDB임베딩으로 JOIN 제거
키로만 조회Redis해시맵처럼 단순 K-V

② 얼마나 강한 일관성이 필요한가?

비즈니스 도메인마다 허용되는 일관성 수준이 다릅니다. 계좌이체는 강한 일관성이 필수지만 좋아요 수는 잠깐 틀려도 됩니다.

강한 일관성 필요           →  PostgreSQL (ACID 완전 지원)
  예: 계좌이체, 재고 차감

최종 일관성으로 충분         →  MongoDB, Cassandra
  예: 좋아요 수, 조회수

일관성보다 속도가 중요       →  Redis
  예: 피드 목록, 세션, 랭킹

③ 쿼리 패턴이 고정인가, 유동적인가?

  • 고정 패턴 (주문 조회, 사용자 조회): RDBMS의 정형화된 스키마가 유리
  • 유동 패턴 (상품 속성, A/B 테스트 데이터): MongoDB의 유연한 스키마가 유리
  • 집계/분석 쿼리: ClickHouse, BigQuery 같은 OLAP DB 고려

💡개념

도메인별 DB 선택 패턴

이커머스, 소셜, IoT — 도메인마다 데이터 특성과 접근 패턴이 다릅니다. 그런데 "우리도 카카오처럼 하면 된다"는 식으로 남의 아키텍처를 그대로 복사하면 규모와 맥락이 달라 맞지 않습니다. 도메인별로 어떤 DB 조합이 반복적으로 나타나는지 알면, 새 서비스 설계 때 출발점이 생깁니다. 패턴을 아는 것과 모르는 것의 차이는 첫 설계의 품질에서 드러납니다.

폴리글랏 퍼시스턴스 — 도메인별 DB 선택 — 한 서비스도 도메인마다 다른 DB를 씀: 주문·결제는 트랜잭션 보장 RDBMS, 세션·캐시는 Redis, 상품 카탈로그는 MongoDB, 검색은 Elasticsearch, 로그·메트릭은 시계열 DB. "하나의 DB로 전부"보다 각 도메인 특성에 맞는 DB를 조합하는 것이 폴리글랏 퍼시스턴스확대

실무에서 반복되는 선택 패턴

도메인마다 데이터 접근 패턴이 다르기 때문에 최적의 DB 조합도 달라집니다. 아래 패턴들은 실무에서 가장 자주 등장하는 폴리글랏 구성입니다.

이커머스

주문·결제·재고는 ACID가 필수이므로 PostgreSQL이 핵심이고, 빠른 응답이 필요한 캐시와 검색은 각각 Redis와 Elasticsearch가 담당합니다.

PostgreSQL   ← 주문, 결제, 재고 (ACID 필수)
Redis        ← 장바구니, 세션, 상품 조회 캐시
Elasticsearch ← 상품 검색, 자동완성
MongoDB      ← 상품 상세 (속성이 카테고리마다 다름)

SaaS B2B

조직·사용자·권한처럼 복잡한 관계형 데이터는 PostgreSQL이 담당하고, Rate Limit처럼 TTL이 필요한 임시 데이터는 Redis가 맡습니다.

PostgreSQL   ← 조직, 사용자, 권한, 청구 (관계형 + 멀티테넌트)
Redis        ← API Rate Limit, 실시간 알림 큐
S3 + PostgreSQL ← 첨부파일 메타데이터

소셜 피드

피드 타임라인은 최신 N개를 O(log N)으로 조회해야 하므로 Redis Sorted Set이 최적이고, 포스트 내용은 미디어·태그 등 구조가 다양해 MongoDB가 유연합니다.

PostgreSQL   ← 사용자 계정, 팔로우 관계
Redis Sorted Set ← 타임라인 피드 (ZREVRANGE로 O(log N))
MongoDB      ← 포스트 내용 (자유 형식, 미디어 임베딩)

실시간 분석 대시보드

트랜잭션 원본은 PostgreSQL에 두고, 수억 건 집계 쿼리는 OLAP에 특화된 ClickHouse가 초 단위로 처리합니다.

PostgreSQL   ← 원본 트랜잭션 데이터
ClickHouse   ← OLAP 집계 쿼리 (수억 건도 초 단위)
Redis        ← 최근 1시간 지표 캐시
DB 선택 기준 — 도메인 및 쿼리 패턴별
복잡한 JOIN, 강한 ACID 트랜잭션이 필요한 관계형 데이터 (주문, 결제, 재고)PostgreSQL 또는 MySQL외래키 무결성, 복잡한 JOIN 최적화, 트랜잭션 격리 수준 지원
카테고리마다 속성이 다른 상품 정보, 자주 바뀌는 유연한 문서 구조MongoDB스키마 없이 JSON 형태로 자유롭게 저장, 임베딩으로 JOIN 제거 가능
피드 타임라인, 랭킹, 세션, 캐시처럼 마이크로초 응답이 필요한 경우Redis메모리 기반, Sorted Set으로 정렬 조회, TTL로 자동 만료 관리
상품 검색, 자동완성처럼 풀텍스트 검색이 핵심인 기능Elasticsearch 또는 OpenSearch역인덱스 기반 풀텍스트 검색, 형태소 분석, 집계 쿼리 최적화
수억 건 데이터를 집계하는 분석 쿼리나 BI 대시보드ClickHouse 또는 BigQueryOLAP 특화, 컬럼 지향 저장으로 집계 속도 압도적, 트랜잭션 미지원

💡개념

폴리글랏 퍼시스턴스와 CQRS

쓰기는 PostgreSQL에서 하고 읽기는 Redis 캐시에서 합니다. 그런데 캐시 갱신이 늦어 읽기 요청에서 오래된 데이터가 노출됩니다. 검색은 Elasticsearch로 따로 인덱싱하는데 DB와 ES 사이 데이터가 맞지 않을 때가 있습니다. 여러 DB를 함께 쓰면 일관성 유지와 동기화가 새로운 과제가 됩니다. 폴리글랏 퍼시스턴스와 CQRS는 이런 복잡도를 관리하는 설계 패턴입니다.

여러 DB를 함께 쓰는 설계 패턴

폴리글랏 퍼시스턴스 (Polyglot Persistence)

한 서비스 안에서 목적에 맞는 DB를 혼용하는 전략

하나의 Application이 목적별로 여러 DB를 함께 씁니다.

DB용도
PostgreSQL주문/결제 (ACID)
Redis캐시/세션
MongoDB상품 상세/로그

장점: 각 도메인에 최적화된 DB 사용 단점: 운영 복잡성 증가 (장애 포인트 증가, 팀 숙련도 분산)

결정 기준: 단일 DB로 해결할 수 없는 명확한 이유가 있을 때만 추가

CQRS (Command Query Responsibility Segregation)

쓰기(Command)와 읽기(Query) 저장소를 분리합니다.

사용자 요청을 쓰기/읽기로 분리합니다.

  • 쓰기 (POST /order) → PostgreSQL (ACID, 정규화) → 이벤트 발행 → Redis/Elasticsearch (읽기 뷰 갱신)
  • 읽기 (GET /orders) → Redis (캐시) 또는 Elasticsearch (검색 최적화 뷰)

읽기 트래픽이 쓰기보다 10배 이상 많은 서비스에서 효과적입니다.


실습: 시나리오별 DB 선택 판단

다음 시나리오에서 어떤 DB를 고를지 직접 판단해보세요.

시나리오 1: 의료 기록 시스템 — 환자별 진료 기록을 저장하고, 의사가 과거 기록을 시간순으로 조회합니다. 진료 기록 삭제는 절대 불가입니다.

판단 포인트:
- 시간순 조회 → created_at 인덱스
- 삭제 불가 → soft delete + audit log
- 규정 준수 → ACID 필수

→ PostgreSQL (+ 감사 로그용 append-only 테이블)

시나리오 2: 라이브 스트리밍 플랫폼 — 시청자 수를 1초마다 업데이트하고 스트리머 대시보드에 표시합니다.

판단 포인트:
- 초당 업데이트 → 높은 쓰기 빈도
- 정확한 수치보다 빠른 응답이 중요
- 데이터 소실 허용 (±0.1% 오차 무방)

→ Redis INCR (RDBMS에 매초 UPDATE하면 커넥션 병목)

시나리오 3: B2B SaaS 리포트 — 월별 매출, 고객사별 사용량을 집계해서 PDF로 내보냅니다.

판단 포인트:
- 집계 쿼리 (GROUP BY, SUM, 날짜 범위)
- 실시간보다 배치 처리
- 데이터 크기: 수천만 건

→ PostgreSQL (규모 작으면 충분) or ClickHouse (억 단위 이상)

스키마가 자주 바뀐다는 이유로 MongoDB 선택 → 관계형 쿼리 필요해짐

// 처음에는 단순해 보였던 구조
{ user_id: 1, orders: [...] }   // 사용자 안에 주문 임베딩

// 6개월 후: "이번 달 특정 상품을 구매한 모든 사용자 목록 뽑아줘"
// → 전체 컬렉션 스캔, 집계 파이프라인으로 복잡한 $unwind + $match 필요

왜 이렇게 됐는가: 초기 요구사항만 보고 DB를 선택했습니다. 비즈니스가 성장하면 분석 쿼리가 반드시 생깁니다.

교훈:

  1. "스키마가 유연해서 MongoDB" → 잘못된 이유. 스키마 변경은 RDBMS에서 마이그레이션으로 관리 가능
  2. 예상 쿼리 패턴을 먼저 작성해보고 DB를 선택
  3. 도메인의 핵심 데이터는 관계형이 거의 항상 유리

이미 MongoDB에 있다면: $lookup(JOIN에 해당)으로 임시 대응하고, 장기적으로 핵심 데이터는 PostgreSQL로 마이그레이션을 계획합니다.

DB 클라이언트
# Redis 재시작 후
redis-cli keys "*"
# (empty array)
# 모든 캐시, 세션, 그리고 잘못 저장한 주문 데이터까지 소멸
OUTPUT
실행 완료 또는 조회 결과가 표시됩니다.
🔍실행 후 확인할 것
  • CAP 판단 먼저: 결제·재고처럼 강한 일관성(C)이 필요하면 PostgreSQL — 좋아요 수·조회수처럼 최종 일관성(A)으로 충분하면 MongoDB/Redis 선택
  • TPS 기준 판단: 관계형(PostgreSQL/MySQL)은 초당 수천 건 트랜잭션에 적합, Redis·Cassandra는 초당 수만 건 이상 키-값 조회에 적합
  • JOIN 빈도와 DB 조합 해석: JOIN이 3개 테이블 이상 필요하고 빈번하면 → PostgreSQL. 단일 문서 조회이고 속성 구조가 다양하면 → MongoDB. 두 패턴이 동시에 필요하면 → 폴리글랏 퍼시스턴스 적용 시점

원인: Redis의 기본 설정은 인메모리만 사용합니다. RDB 스냅샷이나 AOF(Append Only File)를 설정하지 않으면 재시작 시 데이터가 사라집니다.

해결: Redis는 캐시·세션·임시 데이터에만 사용하고, 영구 보존이 필요한 데이터는 반드시 RDBMS나 MongoDB에 저장합니다.

로컬 터미널
# redis.conf에서 영속성 활성화 (캐시 용도가 아닐 때만)
appendonly yes
appendfsync everysec

심화 — 폴리글랏의 청구서는 '정합성'으로 날아온다

💡개념

심화: 두 저장소에 걸친 쓰기는 원자적이지 않다 — dual write와 outbox

폴리글랏·CQRS는 읽기와 쓰기를 각자 잘하는 DB에 맡긴다는 매력적인 그림입니다. 그런데 한 번의 사용자 요청이 PostgreSQL에 쓰고 Elasticsearch에도 색인해야 한다면, 그 순간 분산 시스템의 가장 어려운 문제가 조용히 들어옵니다.

  • 트랜잭션은 한 DB 안에서만 원자적입니다: PostgreSQL의 COMMIT은 PostgreSQL만 보장합니다. 이어지는 ES 색인은 별개의 네트워크 호출이라 둘을 하나의 트랜잭션으로 묶을 수 없습니다. 앱이 PG 커밋과 ES 색인 사이에서 죽으면 주문은 DB엔 있는데 검색엔 없는 영구 불일치가 남습니다(dual write 문제).
  • 순서를 바꿔도 해결되지 않습니다: ES 먼저 쓰고 PG를 나중에 써도, PG가 실패하면 이번엔 검색엔 있는데 DB엔 없는 유령 데이터가 됩니다. 두 저장소를 앱에서 순차 호출하는 한 중간 실패의 창은 사라지지 않습니다.
  • 2단계 커밋(2PC)은 대개 답이 아닙니다: 이론상 분산 트랜잭션으로 묶을 수 있지만, ES·Redis 같은 저장소는 완전한 2PC를 지원하지 않거나 운영·성능 비용이 큽니다. 실무는 강한 원자성 대신 결국 맞춰지는(eventually consistent) 설계를 택합니다.
  • 정석은 transactional outbox입니다: 주문을 저장하는 바로 그 PG 트랜잭션 안에서 outbox 테이블에 이 주문을 색인하라는 이벤트도 함께 커밋합니다. 그러면 주문과 이벤트는 원자적으로 같이 저장됩니다. 별도 프로세스나 CDC가 outbox를 읽어 ES에 반영하고, 실패하면 재시도합니다. 반영은 at-least-once라 같은 이벤트가 두 번 올 수 있으므로 색인은 멱등(같은 id면 덮어쓰기)해야 합니다.

핵심은, 폴리글랏의 진짜 비용은 DB를 하나 더 배우는 게 아니라 두 저장소를 어떻게 일관되게 유지할 것인가라는 점입니다. DB를 나눈다는 결정에는 항상 어떻게 동기화할 것인가라는 설계가 딸려 옵니다(Master-Slave 복제(Replication) 구축과 DB 고가용성(HA) 아키텍처).

상황: 주문은 PostgreSQL에, 검색은 Elasticsearch에 두는 CQRS 구조입니다. 대부분 잘 되는데 특정 주문들이 상세 페이지에는 있지만 검색·목록에는 없습니다. 밤마다 전체 재색인 배치를 돌리면 사라졌던 주문이 채워집니다.

원인: 앱이 PG에 저장 후 커밋하고 그다음 ES에 색인하는 순서로 두 저장소에 따로 씁니다. 배포·타임아웃·인스턴스 종료 등으로 커밋 직후 ES 색인 전에 실패하면, 그 주문은 DB에만 남고 검색엔 영영 반영되지 않습니다. 평소엔 드물지만 트래픽·배포가 몰릴 때 집중적으로 발생합니다.

진단: 특정 시간대(배포·스케일인 시점)에 불일치가 몰리는지 봅니다. PG의 주문 집합과 ES의 문서 집합을 대사해 DB엔 있고 ES엔 없는 id를 뽑으면 dual write 실패가 확정됩니다. 애플리케이션 로그에서 PG 커밋 성공 후 ES 호출 예외가 조용히 삼켜졌는지도 확인합니다.

해결: 앱에서의 순차 이중 쓰기를 걷어내고 transactional outbox로 바꿉니다. 주문과 함께 outbox 이벤트를 같은 PG 트랜잭션에서 커밋하고, 별도 워커나 CDC가 outbox를 ES로 흘려보내며 실패 시 재시도합니다. 색인은 주문 id 기준 멱등 upsert로 만들어 중복 이벤트에도 안전하게 합니다. 야간 전체 재색인은 최후의 보정일 뿐 상시 정합성의 근거가 되어서는 안 됩니다.


💼
실무 맥락
현업 패턴

실무에서 DB 선택 결정이 일어나는 순간

시나리오: 신규 기능 개발 전 아키텍처 리뷰

팀장: "이번에 상품 리뷰 기능 추가하는데, 어디에 저장할까요?"

주니어: "MongoDB요. 리뷰마다 구조가 다를 수 있으니까요."

시니어: "잠깐, 리뷰 조회 패턴이 어떻게 되나요?
  - 상품별 리뷰 목록 (product_id로 조회)
  - 사용자가 쓴 리뷰 목록 (user_id로 조회)
  - 리뷰에 대댓글 기능 예정?
  - 구조가 '다를 수 있다'는 게 구체적으로 어떤 부분인가요?"

주니어: "rating이 별점인 상품도 있고 점수인 것도 있어서요..."

시니어: "그건 컬럼 두 개면 해결됩니다. PostgreSQL로 가는 게 낫겠어요.
  나중에 '이번 달 평점 4점 이상 리뷰 남긴 사용자에게 쿠폰 발송' 같은
  마케팅 쿼리가 반드시 생기거든요. MongoDB에서 그런 집계는 고통스럽습니다."

핵심: DB 선택 전에 "6개월 후 어떤 쿼리가 필요할까?"를 미리 물어보는 것이 중요합니다. 현재 요구사항만으로 결정하면 결국 마이그레이션 비용이 생깁니다.


정리: DB 선택 체크리스트

✅ 데이터 간 관계가 복잡하다 (JOIN 필요) → PostgreSQL/MySQL
✅ ACID 트랜잭션이 필수다 (결제, 재고) → PostgreSQL
✅ 초당 수만 번 읽기, 정확성보다 속도 → Redis
✅ 문서 구조가 진짜로 다양하다 (e-commerce 상품 속성) → MongoDB
✅ 전문 검색, 자동완성이 필요하다 → Elasticsearch
✅ 집계 분석, 수억 건 이상 → ClickHouse, BigQuery

⚠️ "스키마가 유연해서" MongoDB → 잘못된 이유
⚠️ "빠를 것 같아서" Redis를 메인 DB로 → 잘못된 용도
⚠️ 팀이 한 번도 안 써본 DB → 운영 비용 과소평가

명령어·구문 빠른 참조

SQL 문법 학습 모듈이 아니라 DB 선택 판단 모듈이므로, 각 저장소의 적합성을 가르는 데 등장한 대표 명령·구문만 모았습니다.

구문/명령용도
psql / mongo / redis-cli워크로드별로 다른 DB 접속 클라이언트관계형은 psql, 문서는 mongo, 캐시는 redis-cli
Redis ZREVRANGE ... WITHSCORESSorted Set으로 최신 피드 O(log N) 조회ZREVRANGE user:42:feed 0 99 WITHSCORES
Redis INCR조회수·시청자 수 같은 고빈도 카운터INCR live:1234:viewers (매초 RDBMS UPDATE 대신)
Redis keys "*"저장된 키 확인(운영 남용 주의)redis-cli keys "*" — 재시작 후 비면 영속성 미설정
redis.conf appendonly / appendfsyncRedis AOF 영속성 활성화appendonly yes / appendfsync everysec
MongoDB $lookup문서 DB에서 JOIN 대응(임시 대응책)임베딩 후 관계형 쿼리가 필요해질 때
MongoDB $unwind + $match임베딩 배열을 펼쳐 집계·조건 조회컬렉션 전체 스캔 비용 주의
GROUP BY / SUM() 집계분석 쿼리 규모로 DB 선택 판단억 단위 집계면 ClickHouse·BigQuery 고려

관련 모듈로 더 깊이:

다음 모듈에서는 SQL Injection 예방, 최소 권한 접근 제어, 데이터 암호화 등 DB 보안 기초를 다룹니다.

지식 확인

퀴즈 — 8문제

Q1

주문 내역 조회와 사용자 조회를 자주 INNER JOIN해야 하는 서비스에서 가장 적합한 DB는?

Q2

소셜 미디어 앱에서 사용자별 피드 목록(최신 100개)을 매우 빠르게 읽어야 할 때 가장 적합한 저장소는?

Q3

단일 DB로 해결하기 어렵다고 판단하는 기준으로 가장 적절한 것은?

Q4

스타트업 MVP에서 DB를 선택할 때 가장 중요한 기준은?

Q5

CQRS 패턴에서 Command DB와 Query DB를 분리하는 이유는?

Q6

새 기능의 DB를 고를 때 '요즘 인기 있는 것'이 아니라 먼저 던져야 할 핵심 질문에 가까운 것은?

Q7

[심화] 한 요청에서 PostgreSQL에 저장하고 이어서 Elasticsearch에 색인하는 폴리글랏 구조가 근본적으로 데이터 불일치에 노출되는 이유는?

Q8

[심화] PostgreSQL 쓰기와 Elasticsearch 색인의 dual write 불일치를 구조적으로 막는 표준 패턴은?

0 / 8 답변

🧪 실습으로 확인하기

PostgreSQL 설치 및 기본 설정

초급

Linux 서버에 PostgreSQL을 설치하고, 데이터베이스와 사용자를 생성한 뒤 외부 접속이 가능하도록 설정한다.

50📋 5단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요