๐ 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 - ์ค์ ํ์ฉ
(๋ฐฑ์๋ ์ฐ๊ฒฐ, ํธ๋์ญ์
, ๋ฐฑ์
& ๋ณด์)