🍃 DatabaseDevGuide0005
NoSQL 완전 정복 A-Z
MongoDB · Redis · 언제 SQL? 언제 NoSQL?
현업에서 바로 쓰는 NoSQL의 모든 것을 A-Z로 배워봅시다
📋 학습 목차
| 챕터 | 주제 | 핵심 키워드 |
|---|---|---|
| 01 | NoSQL이란? | SQL vs NoSQL, 4가지 NoSQL 종류, CAP 이론 |
| 02 | MongoDB 기초 | Document, Collection, CRUD, mongosh |
| 03 | MongoDB 심화 | Aggregation, 인덱스, 스키마 설계, Mongoose |
| 04 | Redis 완전 정복 | 자료구조, 캐싱, 세션, Pub/Sub, TTL |
| 05 | Elasticsearch 입문 | 전문 검색, 역색인, 한국어 분석기 |
| 06 | NoSQL 데이터 모델링 | 임베딩 vs 참조, 패턴, 스키마 전략 |
| 07 | 언제 SQL? 언제 NoSQL? | 선택 기준, 혼합 사용 전략, 실전 아키텍처 |
| 08 | MongoDB Atlas (클라우드) | 클라우드 DB, 무료 Tier, 연결 설정 |
| 09 | 실전 프로젝트 | 블로그 서비스 MongoDB + Redis 구현 |
| 10 | 현업 면접 Q&A TOP 10 | NoSQL 관련 면접 질문 완전 정리 |
| 11 | 학습 로드맵 & 체크리스트 | 단계별 학습 경로, 추천 도구 |
📌 01. NoSQL이란 무엇인가?
Not Only SQL — 표(테이블) 형식이 아닌 방법으로 데이터를 저장하는 DB!
🏗️ SQL vs NoSQL 한눈에 비교
🗂️ NoSQL의 4가지 종류
문서형 (Document)
JSON/BSON 형태의 문서로 저장.
각 문서마다 다른 구조 가능
대표: MongoDB, CouchDB
SNS 프로필, 상품 카탈로그
키-값형 (Key-Value)
키(key)로 값(value)을 저장.
극도로 빠른 읽기/쓰기
대표: Redis, DynamoDB
캐싱, 세션, 장바구니
컬럼형 (Column-Family)
컬럼 단위로 데이터 저장.
대용량 쓰기에 최적화
대표: Cassandra, HBase
IoT 센서, 로그, 시계열 데이터
그래프형 (Graph)
노드와 엣지(관계)로 저장.
복잡한 관계 탐색에 최적화
대표: Neo4j, Amazon Neptune
SNS 친구관계, 추천 엔진
⚖️ CAP 이론 (분산 시스템의 핵심)
CAP 이론: 분산 시스템에서 아래 3가지를 동시에 모두 만족할 수 없다는 이론
현실에서 P는 포기 불가 → CP(일관성+분산) 또는 AP(가용성+분산) 중 선택!
MongoDB = CP, Cassandra = AP, Redis = CP (Sentinel/Cluster 구성 시)
💡 비유로 이해하는 NoSQL
SQL = 정돈된 엑셀 표. 모든 행이 같은 컬럼 구조. 찾기 쉽고 관계 표현 탁월.
MongoDB = 메모장 파일 더미. 각 파일(문서)이 다른 내용을 가질 수 있음. 유연하고 빠름.
Redis = 초고속 사물함. 열쇠(키)로 물건(값)을 꺼내는 방식. 속도가 생명!
Cassandra = 거대한 스프레드시트 클러스터. 수십억 행도 거뜬히 처리.
📌 02. MongoDB 기초 — 문서형 NoSQL의 대표주자
JSON과 비슷한 BSON 형식으로 데이터를 저장하는 세계 1위 NoSQL DB!
🔑 MongoDB 핵심 용어 비교
📄 Document란?
// MongoDB Document 예시 (BSON = Binary JSON)
{
"_id": ObjectId("64f2a1b3c4d5e6f789012345"), // 자동 생성 고유 ID
"name": "김철수",
"email": "chulsoo@example.com",
"age": 28,
"address": { // 중첩 문서 (Embedded Document)
"city": "서울",
"district": "강남구"
},
"hobbies": ["독서", "코딩", "여행"], // 배열 가능!
"orders": [ // 배열 안에 문서도 가능!
{ "product": "노트북", "price": 1200000 },
{ "product": "마우스", "price": 30000 }
],
"createdAt": ISODate("2026-03-27T09:00:00Z"),
"isActive": true
}
// ✅ SQL과 달리 각 문서가 다른 필드를 가져도 됩니다!
{
"_id": ObjectId("..."),
"name": "이영희",
"phone": "010-1234-5678" // address 없어도 OK!
// hobbies 없어도 OK!
}
⚡ MongoDB CRUD 완전 정복
📥 Create (데이터 삽입)
// 단일 문서 삽입
db.users.insertOne({
name: "김철수",
email: "chulsoo@example.com",
age: 28,
role: "user"
});
// 여러 문서 한꺼번에 삽입
db.users.insertMany([
{ name: "이영희", email: "younghee@example.com", age: 25 },
{ name: "박민준", email: "minjun@example.com", age: 32 },
{ name: "최수진", email: "soojin@example.com", age: 29 }
]);
// 결과:
// { acknowledged: true, insertedId: ObjectId("...") }
// { acknowledged: true, insertedIds: { '0': ..., '1': ..., '2': ... } }
🔍 Read (데이터 조회)
// 전체 조회
db.users.find();
// 조건 조회 (SQL의 WHERE)
db.users.find({ age: 28 }); // age가 28인 문서
db.users.find({ role: "user", age: { $gte: 25 } }); // role=user AND age>=25
// 단일 문서 조회
db.users.findOne({ email: "chulsoo@example.com" });
// 특정 필드만 조회 (SQL의 SELECT)
db.users.find(
{ role: "user" }, // 조건
{ name: 1, email: 1, _id: 0 } // 프로젝션: name, email만 가져오기 (_id 제외)
);
// 정렬, 개수 제한
db.users.find().sort({ age: 1 }); // age 오름차순 (1=오름, -1=내림)
db.users.find().sort({ age: -1 }).limit(10).skip(20); // 페이징!
// 개수 세기
db.users.countDocuments({ role: "user" });
// 비교 연산자
// $eq: 같음, $ne: 다름, $gt: 초과, $gte: 이상, $lt: 미만, $lte: 이하
db.users.find({ age: { $gte: 25, $lte: 30 } }); // 25~30세
// 논리 연산자
db.users.find({ $or: [{ age: 25 }, { age: 30 }] }); // 25세 OR 30세
db.users.find({ $and: [{ age: { $gte: 20 } }, { role: "admin" }] });
// 배열 조회
db.users.find({ hobbies: "코딩" }); // hobbies 배열에 "코딩" 포함
db.users.find({ hobbies: { $in: ["코딩", "독서"] } }); // "코딩" 또는 "독서" 포함
// 중첩 문서 조회 (dot notation)
db.users.find({ "address.city": "서울" }); // 중첩 필드 접근
// 정규식 (LIKE와 유사)
db.users.find({ name: { $regex: "김", $options: "i" } }); // 이름에 "김" 포함 (대소문자 무시)
✏️ Update (데이터 수정)
// 단일 문서 수정
db.users.updateOne(
{ email: "chulsoo@example.com" }, // 조건
{ $set: { age: 29, city: "부산" } } // 수정 내용
);
// 여러 문서 수정
db.users.updateMany(
{ role: "user" },
{ $set: { isActive: true } }
);
// ⚠️ $set 없이 쓰면 문서 전체가 교체됩니다!
db.users.updateOne({ email: "..." }, { age: 30 }); // ❌ 다른 필드 모두 사라짐!
db.users.updateOne({ email: "..." }, { $set: { age: 30 } }); // ✅ age만 변경
// 자주 쓰는 Update 연산자
// $set: 필드 값 설정
// $unset: 필드 삭제
// $inc: 숫자 증가/감소
// $push: 배열에 값 추가
// $pull: 배열에서 값 제거
// $addToSet: 배열에 중복 없이 추가
db.users.updateOne(
{ email: "chulsoo@example.com" },
{
$inc: { loginCount: 1 }, // loginCount +1
$push: { hobbies: "게임" }, // hobbies 배열에 "게임" 추가
$set: { lastLogin: new Date() } // lastLogin 업데이트
}
);
// upsert: 없으면 삽입, 있으면 수정
db.users.updateOne(
{ email: "new@example.com" },
{ $set: { name: "신규유저", role: "user" } },
{ upsert: true } // 없으면 새로 만들어!
);
🗑️ Delete (데이터 삭제)
// 단일 문서 삭제
db.users.deleteOne({ email: "chulsoo@example.com" });
// 여러 문서 삭제
db.users.deleteMany({ isActive: false });
// 컬렉션의 모든 문서 삭제 (컬렉션 자체는 유지)
db.users.deleteMany({});
// ⚠️ 실수 방지: 삭제 전에 find()로 먼저 확인!
db.users.find({ isActive: false }); // 먼저 확인
db.users.deleteMany({ isActive: false }); // 그 다음 삭제
📌 03. MongoDB 심화 — Aggregation, 인덱스, Mongoose
현업에서 꼭 필요한 MongoDB 중급 기술들!
🔧 Aggregation Pipeline (집계 파이프라인)
Aggregation Pipeline은 SQL의 GROUP BY + JOIN + HAVING을 한꺼번에 할 수 있는 강력한 기능입니다.
데이터가 여러 단계(Stage)를 파이프라인처럼 통과하면서 변환됩니다.
// 주요 Aggregation 스테이지
// $match: 조건 필터 (SQL의 WHERE)
// $group: 그룹화 (SQL의 GROUP BY)
// $sort: 정렬
// $limit: 개수 제한
// $skip: 건너뛰기
// $project: 필드 선택/변환 (SQL의 SELECT)
// $lookup: 다른 컬렉션과 조인
// $unwind: 배열을 풀어서 개별 문서로 변환
// ✅ 예시: 역할별 사용자 수와 평균 나이
db.users.aggregate([
{ $match: { isActive: true } }, // 활성 사용자만
{ $group: {
_id: "$role", // role별 그룹화
count: { $sum: 1 }, // 각 그룹 수
avgAge: { $avg: "$age" }, // 평균 나이
names: { $push: "$name" } // 이름 목록 배열로
}},
{ $sort: { count: -1 } }, // 많은 순으로 정렬
{ $project: {
_id: 0,
role: "$_id",
count: 1,
avgAge: { $round: ["$avgAge", 1] } // 소수점 1자리로 반올림
}}
]);
// ✅ 예시: 주문 컬렉션에서 월별 매출 집계
db.orders.aggregate([
{ $match: { status: "완료" } },
{ $group: {
_id: {
year: { $year: "$createdAt" },
month: { $month: "$createdAt" }
},
totalRevenue: { $sum: "$finalPrice" },
orderCount: { $sum: 1 }
}},
{ $sort: { "_id.year": -1, "_id.month": -1 } },
{ $limit: 12 } // 최근 12개월
]);
// ✅ $lookup: 컬렉션 JOIN (users + orders)
db.orders.aggregate([
{ $lookup: {
from: "users", // 조인할 컬렉션
localField: "userId", // orders의 필드
foreignField: "_id", // users의 필드
as: "userInfo" // 결과 배열 이름
}},
{ $unwind: "$userInfo" }, // 배열을 단일 문서로
{ $project: {
orderId: "$_id",
userName: "$userInfo.name",
totalPrice: 1,
status: 1
}}
]);
📑 MongoDB 인덱스
// 인덱스 생성
db.users.createIndex({ email: 1 }); // 오름차순 단일 인덱스
db.users.createIndex({ email: 1 }, { unique: true }); // 유니크 인덱스
db.users.createIndex({ age: 1, role: 1 }); // 복합 인덱스
db.users.createIndex({ name: "text" }); // 텍스트 검색 인덱스
db.users.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 }); // TTL 인덱스
// 인덱스 목록 확인
db.users.getIndexes();
// 인덱스 삭제
db.users.dropIndex("email_1");
// 쿼리 성능 분석 (SQL의 EXPLAIN)
db.users.find({ email: "test@example.com" }).explain("executionStats");
// "IXSCAN" = 인덱스 사용 ✅ "COLLSCAN" = 풀스캔 ❌
🟢 Mongoose — Node.js에서 MongoDB 사용하기
Mongoose는 Node.js에서 MongoDB를 쉽게 사용하기 위한 ODM(Object Document Mapper) 라이브러리입니다.
스키마를 정의하고, 유효성 검사, 미들웨어 기능을 제공합니다.
// npm install mongoose
const mongoose = require('mongoose');
// 1. DB 연결
mongoose.connect('mongodb://localhost:27017/shopdb')
.then(() => console.log('MongoDB 연결 성공!'))
.catch(err => console.error('연결 실패:', err));
// 2. 스키마 정의
const userSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
age: { type: Number, min: 0, max: 150 },
role: { type: String, enum: ['user', 'admin'], default: 'user' },
isActive: { type: Boolean, default: true },
createdAt: { type: Date, default: Date.now }
});
// 3. 모델 생성
const User = mongoose.model('User', userSchema);
// 4. CRUD 사용 (async/await)
// Create
const newUser = await User.create({ name: '김철수', email: 'cs@example.com', age: 28 });
// Read
const users = await User.find({ role: 'user' }).select('name email').limit(10);
const user = await User.findById('64f2a1b3c4d5e6f789012345');
const one = await User.findOne({ email: 'cs@example.com' });
// Update
await User.findByIdAndUpdate(id, { $set: { age: 29 } }, { new: true });
// Delete
await User.findByIdAndDelete(id);
// 5. Mongoose 가상 필드 (Virtual)
userSchema.virtual('fullInfo').get(function() {
return `${this.name} (${this.email})`;
});
// 6. pre/post 미들웨어 (Hooks)
userSchema.pre('save', async function(next) {
if (this.isModified('password')) {
this.password = await bcrypt.hash(this.password, 10); // 저장 전 암호화
}
next();
});
📌 04. Redis 완전 정복 ⚡
인메모리(RAM) 기반의 초고속 키-값 저장소. 캐싱, 세션, 실시간 처리의 핵심!
⚡ Redis가 빠른 이유: 데이터를 디스크가 아닌 메모리(RAM)에 저장합니다.
메모리 접근 속도 = 약 100ns (나노초) vs 디스크 = 약 10ms (밀리초) → 10만 배 차이!
초당 100만 건 이상의 읽기/쓰기가 가능합니다.
🗂️ Redis 5가지 핵심 자료구조
① String — 가장 기본적인 자료형
# SET, GET
SET username "김철수"
GET username # "김철수"
# TTL (Time To Live) 설정 - 자동 만료!
SET session:user123 "data..." EX 3600 # 1시간 후 자동 삭제
TTL session:user123 # 남은 초 확인
PERSIST session:user123 # TTL 제거 (영구 저장)
# 숫자 증가/감소 (카운터)
SET pageView 0
INCR pageView # 1
INCR pageView # 2
INCRBY pageView 10 # 12
DECR pageView # 11
② Hash — 객체 저장 (사용자 정보 등)
# 사용자 정보를 Hash로 저장
HSET user:1001 name "김철수" email "cs@example.com" age 28
HGET user:1001 name # "김철수"
HGETALL user:1001 # 모든 필드와 값
# 특정 필드 수정
HSET user:1001 age 29
# 필드 삭제
HDEL user:1001 age
# 존재 여부 확인
HEXISTS user:1001 email # 1 (존재) or 0 (없음)
# 모든 키 / 값 목록
HKEYS user:1001 # ["name", "email", "age"]
HVALS user:1001 # ["김철수", "cs@example.com", "28"]
③ List — 순서 있는 목록 (큐, 스택)
# 왼쪽/오른쪽에 추가
LPUSH recentView:user1 "상품A"
LPUSH recentView:user1 "상품B"
RPUSH recentView:user1 "상품C"
# 조회 (0부터 시작, -1은 끝까지)
LRANGE recentView:user1 0 -1 # ["상품B", "상품A", "상품C"]
LRANGE recentView:user1 0 4 # 최근 5개
# 꺼내기 (큐: LPUSH + RPOP, 스택: LPUSH + LPOP)
RPOP recentView:user1 # "상품C" (오른쪽에서 꺼냄)
LPOP recentView:user1 # "상품B" (왼쪽에서 꺼냄)
# 크기
LLEN recentView:user1 # 리스트 길이
④ Set — 중복 없는 집합 (좋아요, 팔로워)
# 팔로워 관리 예시
SADD followers:user1 user2 user3 user4
SADD followers:user1 user2 # 중복! 무시됨
SMEMBERS followers:user1 # { user2, user3, user4 }
SCARD followers:user1 # 3 (개수)
SISMEMBER followers:user1 user2 # 1 (있음)
# 집합 연산 (SNS 공통 친구 찾기!)
SINTERSTORE commonFriends followers:user1 followers:user2 # 교집합
SUNIONSTORE allFriends followers:user1 followers:user2 # 합집합
SDIFFSTORE onlyUser1 followers:user1 followers:user2 # 차집합
⑤ Sorted Set (ZSet) — 점수 기반 정렬 집합 (랭킹!)
# 실시간 랭킹 구현!
ZADD gameRanking 98500 "김철수"
ZADD gameRanking 87200 "이영희"
ZADD gameRanking 99100 "박민준"
# 높은 점수 순으로 상위 10명 조회 (점수도 같이)
ZREVRANGE gameRanking 0 9 WITHSCORES
# 1위: 박민준 99100, 2위: 김철수 98500, ...
# 내 순위 조회 (0부터 시작)
ZREVRANK gameRanking "김철수" # 1 (2위)
# 점수 갱신
ZINCRBY gameRanking 1500 "이영희" # 이영희 점수 +1500
🚀 Redis 실전 활용 패턴
① 캐싱 (Caching) — DB 부하 줄이기
// Node.js + Redis 캐싱 패턴 (Cache-Aside Pattern)
const redis = require('redis');
const client = redis.createClient({ url: 'redis://localhost:6379' });
async function getUserById(userId) {
const cacheKey = `user:${userId}`;
// 1. Redis에서 먼저 확인
const cached = await client.get(cacheKey);
if (cached) {
console.log('캐시 히트! Redis에서 반환');
return JSON.parse(cached);
}
// 2. 캐시 없으면 DB에서 조회
const user = await User.findById(userId);
// 3. Redis에 저장 (1시간 TTL)
await client.setEx(cacheKey, 3600, JSON.stringify(user));
console.log('캐시 미스. DB에서 조회 후 캐싱');
return user;
}
② 세션 관리 (Session)
// 로그인 세션 저장
async function saveSession(sessionId, userData) {
const key = `session:${sessionId}`;
await client.hSet(key, {
userId: userData.id,
email: userData.email,
role: userData.role
});
await client.expire(key, 86400); // 24시간 후 자동 만료
}
// 세션 확인
async function getSession(sessionId) {
return await client.hGetAll(`session:${sessionId}`);
}
// 로그아웃 (세션 삭제)
async function destroySession(sessionId) {
await client.del(`session:${sessionId}`);
}
③ Pub/Sub — 실시간 메시지 전달
// Publisher (메시지 발행)
const publisher = redis.createClient();
await publisher.publish('notifications', JSON.stringify({
type: 'NEW_ORDER',
orderId: 12345,
userId: 678
}));
// Subscriber (메시지 구독)
const subscriber = redis.createClient();
await subscriber.subscribe('notifications', (message) => {
const data = JSON.parse(message);
console.log('새 알림:', data);
// 알림 처리 로직...
});
📊 Redis 주요 사용 사례 정리
📌 05. Elasticsearch 입문 🔵
전문 검색(Full-Text Search)의 최강자! 로그 분석, 검색 서비스에 필수
Elasticsearch는 Apache Lucene 기반의 분산 검색 엔진입니다.
"쿠팡에서 '노트북' 검색 시 관련 상품이 즉시 나오는 것", "유튜브 자막 검색", "로그 모니터링"에 사용됩니다.
역색인(Inverted Index) 구조 덕분에 수억 건의 텍스트도 밀리초 단위로 검색합니다!
🔍 역색인(Inverted Index)이란?
📚 일반 DB 저장 방식
| ID | 내용 |
|---|---|
| 1 | 노트북 삼성 갤럭시 |
| 2 | 노트북 LG 그램 |
| 3 | 삼성 갤럭시 폰 |
❌ "삼성" 검색 시 모든 행을 스캔!
⚡ 역색인 방식
| 단어 | 문서 ID |
|---|---|
| 노트북 | [1, 2] |
| 삼성 | [1, 3] |
| 갤럭시 | [1, 3] |
| LG | [2] |
✅ "삼성" → 바로 [1,3] 조회! 초고속
💻 Elasticsearch 기본 사용법 (REST API)
# 인덱스 생성 (= 테이블 생성)
PUT /products
{
"mappings": {
"properties": {
"name": { "type": "text", "analyzer": "korean" },
"price": { "type": "integer" },
"category": { "type": "keyword" },
"createdAt": { "type": "date" }
}
}
}
# 문서 삽입
POST /products/_doc
{
"name": "삼성 갤럭시 노트북 15인치",
"price": 1200000,
"category": "전자제품"
}
# 전문 검색
GET /products/_search
{
"query": {
"match": { "name": "갤럭시 노트북" }
}
}
# 복합 검색 (AND/OR/필터)
GET /products/_search
{
"query": {
"bool": {
"must": [{ "match": { "name": "노트북" } }],
"filter": [{ "range": { "price": { "gte": 500000, "lte": 2000000 } } }]
}
},
"sort": [{ "price": "asc" }],
"from": 0, "size": 10
}
📊 Elasticsearch vs MySQL 비교
📌 06. NoSQL 데이터 모델링
NoSQL에서는 SQL과 다른 방식으로 데이터를 설계해야 합니다!
🔗 임베딩 vs 참조 — 가장 중요한 설계 결정
📦 임베딩 (Embedding)
{
"_id": 1,
"name": "김철수",
"address": { // 임베딩!
"city": "서울",
"zip": "12345"
},
"orders": [...] // 임베딩!
}
- ✅ 한 번 읽기로 모든 데이터 조회
- ✅ 조인 불필요, 빠른 읽기
- ❌ 데이터 중복 발생
- ❌ 문서 크기 제한 (16MB)
적합: 1:1, 소규모 1:N, 자주 함께 조회
🔗 참조 (Reference)
{
"_id": 1,
"name": "김철수",
"addressId": ObjectId("..."), // 참조!
"orderIds": [ // 참조!
ObjectId("..."),
ObjectId("...")
]
}
- ✅ 데이터 중복 없음
- ✅ 독립적 업데이트 가능
- ❌ 추가 쿼리($lookup) 필요
- ❌ 성능 저하 가능
적합: 대규모 1:N, M:N, 독립 관리 필요
🎯 MongoDB 스키마 설계 Best Practices
📌 07. 언제 SQL? 언제 NoSQL? — 현업 선택 가이드
둘 중 하나가 더 낫다는 건 없습니다. 상황에 맞는 선택이 중요!
🔵 SQL이 더 적합한 경우
- 금융/결제 시스템: 트랜잭션 무결성(ACID)이 절대적으로 필요한 경우
- 복잡한 JOIN이 필요: 여러 테이블의 관계가 복잡하고 JOIN 쿼리가 핵심인 경우
- 정형화된 구조: 데이터 구조가 고정되어 있고 변경이 거의 없는 경우
- 데이터 일관성 중요: 어느 서버에서 읽어도 같은 데이터를 보장해야 하는 경우
- 팀 역량: 팀원들이 SQL에 익숙하고 학습 비용을 줄여야 하는 경우
대표 사례: 은행 계좌 시스템, 쇼핑몰 결제, ERP, 인사/급여 시스템
🍃 NoSQL이 더 적합한 경우
- 빠른 확장이 필요: 트래픽이 급증할 때 서버를 수평 확장해야 하는 경우
- 다양한 데이터 구조: 상품마다 속성이 달라서 고정 스키마가 불편한 경우
- 대용량 쓰기: 초당 수십만 건의 로그, IoT 센서 데이터 저장
- 실시간 처리: 캐싱, 실시간 랭킹, 채팅 등 속도가 생명인 경우
- 빠른 프로토타이핑: 스키마를 자주 변경해야 하는 초기 개발 단계
대표 사례: SNS 피드(MongoDB), 캐시(Redis), 검색(Elasticsearch), IoT(Cassandra)
🏗️ 현업 실전 아키텍처 — SQL + NoSQL 함께 쓰기
┌─────────────────────────────────────────────────────────────┐
│ 쇼핑몰 서비스 아키텍처 │
├────────────────┬────────────────┬────────────────────────────┤
│ MySQL │ MongoDB │ Redis │
│ (핵심 데이터) │ (유연한 데이터) │ (캐시/세션) │
├────────────────┼────────────────┼────────────────────────────┤
│ 회원 정보 │ 상품 상세 정보 │ 로그인 세션 │
│ 주문/결제 │ 리뷰/평점 │ 상품 목록 캐시 │
│ 재고 관리 │ 검색 로그 │ 장바구니 임시 저장 │
│ 정산 데이터 │ 사용자 활동 로그 │ 실시간 인기 상품 랭킹 │
└────────────────┴────────────────┴────────────────────────────┘
+
┌─────────────────────────────────────────────────────────────┐
│ Elasticsearch │
│ 상품 검색 (한국어 형태소 분석, 유사 검색, 필터 검색) │
└─────────────────────────────────────────────────────────────┘
- 카카오/라인: MySQL(메인) + Redis(캐시, 메시지큐) + HBase(로그)
- Netflix: Cassandra(사용자 시청 기록) + MySQL(결제) + Redis(캐시)
- Airbnb: MySQL + Elasticsearch(숙소 검색) + Redis(캐시)
- Uber: MySQL + Cassandra(위치 데이터) + Redis(실시간 드라이버 위치)
📌 08. MongoDB Atlas — 클라우드 MongoDB 완벽 가이드
설치 없이 바로 쓸 수 있는 MongoDB 클라우드 서비스! 무료 Tier 제공
🚀 MongoDB Atlas 시작하기
회원가입: cloud.mongodb.com 접속 → Google 계정으로 간편 가입
클러스터 생성: "Free" 선택 → AWS/Google Cloud 선택 → 서울(ap-northeast-2) 리전 선택
보안 설정: DB 사용자 생성 (아이디/비밀번호) → IP 허용 (0.0.0.0/0 = 모든 IP 허용)
연결: "Connect" 클릭 → "Connect using MongoDB Driver" → Connection String 복사
// MongoDB Atlas 연결 (Node.js)
const mongoose = require('mongoose');
const MONGODB_URI = 'mongodb+srv://username:password@cluster0.xxxxx.mongodb.net/shopdb?retryWrites=true&w=majority';
mongoose.connect(MONGODB_URI)
.then(() => console.log('✅ MongoDB Atlas 연결 성공!'))
.catch(err => console.error('❌ 연결 실패:', err));
// .env 파일에 저장하여 보안 관리
// MONGODB_URI=mongodb+srv://...
// require('dotenv').config();
// mongoose.connect(process.env.MONGODB_URI)
📊 MongoDB Atlas Free Tier 스펙
📌 09. 실전 프로젝트 — 블로그 서비스 MongoDB + Redis 구현
실제 블로그 서비스의 NoSQL 구조를 A-Z로 설계하고 구현합니다!
📋 요구사항
- 사용자가 블로그 포스트를 작성/수정/삭제할 수 있다 → MongoDB posts 컬렉션
- 포스트에 댓글을 달 수 있다 → MongoDB 임베딩 or 별도 컬렉션
- 포스트에 태그를 달 수 있다 → MongoDB Array 필드
- 로그인 세션 관리 → Redis Hash + TTL
- 포스트 목록은 캐싱 → Redis String + TTL
- 인기 포스트 랭킹 → Redis Sorted Set
🏗️ MongoDB 컬렉션 설계
// ✅ users 컬렉션
{
"_id": ObjectId("..."),
"username": "김철수",
"email": "cs@example.com",
"passwordHash": "...",
"bio": "안녕하세요! 개발자입니다.",
"avatar": "https://...",
"followersCount": 142,
"followingCount": 87,
"createdAt": ISODate("2026-01-01")
}
// ✅ posts 컬렉션 (댓글은 별도 컬렉션으로 참조)
{
"_id": ObjectId("..."),
"title": "MongoDB 완전 정복 가이드",
"slug": "mongodb-complete-guide", // URL 친화적 ID
"content": "# MongoDB란?...", // Markdown 형식
"excerpt": "MongoDB 입문부터 심화까지...", // 미리보기 텍스트
"authorId": ObjectId("..."), // users 참조
"tags": ["MongoDB", "NoSQL", "Database"], // 배열로 태그
"thumbnail": "https://...",
"status": "published", // draft, published, archived
"viewCount": 1523,
"likeCount": 89,
"commentCount": 23, // 반정규화: 매번 COUNT() 안 해도 됨
"publishedAt": ISODate("2026-03-27"),
"createdAt": ISODate("2026-03-27"),
"updatedAt": ISODate("2026-03-27")
}
// ✅ comments 컬렉션 (많은 경우 별도 컬렉션 권장)
{
"_id": ObjectId("..."),
"postId": ObjectId("..."), // posts 참조
"authorId": ObjectId("..."), // users 참조
"parentId": null, // 대댓글: 부모 댓글 ID (자기 참조)
"content": "좋은 글이네요!",
"likeCount": 5,
"createdAt": ISODate("...")
}
⚡ Redis 활용 코드 (Node.js)
const redis = require('redis');
const client = redis.createClient();
// ① 포스트 목록 캐싱
async function getPostList(page = 1) {
const cacheKey = `posts:list:page:${page}`;
const cached = await client.get(cacheKey);
if (cached) return JSON.parse(cached);
const posts = await Post.find({ status: 'published' })
.sort({ publishedAt: -1 })
.skip((page - 1) * 10)
.limit(10)
.select('title slug excerpt tags viewCount likeCount publishedAt');
// 5분 캐싱
await client.setEx(cacheKey, 300, JSON.stringify(posts));
return posts;
}
// ② 조회수 증가 + 인기 포스트 랭킹 업데이트
async function incrementViewCount(postId) {
// MongoDB 조회수 업데이트
await Post.findByIdAndUpdate(postId, { $inc: { viewCount: 1 } });
// Redis Sorted Set으로 실시간 인기 랭킹
await client.zIncrBy('posts:popular:today', 1, postId.toString());
}
// ③ 인기 포스트 TOP 10
async function getPopularPosts() {
const postIds = await client.zRevRange('posts:popular:today', 0, 9, { withScores: true });
// ID 목록으로 MongoDB 조회
const ids = postIds.filter((_, i) => i % 2 === 0); // 짝수 인덱스가 ID
return await Post.find({ _id: { $in: ids } }).select('title slug');
}
// ④ 글 발행 시 캐시 무효화
async function publishPost(postData) {
const post = await Post.create(postData);
// 캐시 무효화 (모든 페이지 캐시 삭제)
const keys = await client.keys('posts:list:*');
if (keys.length > 0) await client.del(keys);
return post;
}
📊 인덱스 설정
// posts 컬렉션 인덱스
db.posts.createIndex({ slug: 1 }, { unique: true }); // URL 중복 방지
db.posts.createIndex({ authorId: 1, status: 1 }); // 특정 작가 글 조회
db.posts.createIndex({ tags: 1 }); // 태그별 검색
db.posts.createIndex({ publishedAt: -1 }); // 최신순 정렬
db.posts.createIndex({ title: "text", content: "text" }); // 텍스트 검색
📌 10. 현업 면접 Q&A TOP 10
NoSQL 관련 실제 면접에서 자주 나오는 질문들!
📌 11. 학습 로드맵 & 체크리스트
1주차
NoSQL 개념 이해
SQL vs NoSQL 비교
CAP 이론
2주차
MongoDB CRUD
Atlas 계정 생성
Mongoose 연결
3주차
Redis 자료구조
캐싱 패턴
세션 관리
4주차
MongoDB Aggregation
인덱스 최적화
데이터 모델링
5주차
실전 프로젝트
블로그/쇼핑몰 구현
면접 준비
🛠️ 추천 학습 도구 & 환경
🎉 DatabaseDevGuide0005 완료!
NoSQL의 세계를 완전 정복했습니다!
MongoDB, Redis, Elasticsearch — 이제 현업에서 상황에 맞게 선택하고 활용하세요!
📌 다음 단계: DatabaseDevGuide0006 - 실전 활용
(백엔드 연결, 트랜잭션, 백업 & 보안)