Guider/Database/DatabaseDevGuide0005
Database#05

DatabaseDevGuide0005

NoSQL ์™„์ „ ์ •๋ณต

DATABASE DEV GUIDE

๐Ÿƒ DatabaseDevGuide0005

NoSQL ์™„์ „ ์ •๋ณต A-Z

MongoDB · Redis · ์–ธ์ œ SQL? ์–ธ์ œ NoSQL?

ํ˜„์—…์—์„œ ๋ฐ”๋กœ ์“ฐ๋Š” NoSQL์˜ ๋ชจ๋“  ๊ฒƒ์„ A-Z๋กœ ๋ฐฐ์›Œ๋ด…์‹œ๋‹ค

๐Ÿƒ MongoDB โšก Redis ๐Ÿ”ต Elasticsearch ๐Ÿ”ถ Cassandra โš–๏ธ SQL vs NoSQL

๐Ÿ“‹ ํ•™์Šต ๋ชฉ์ฐจ

์ฑ•ํ„ฐ ์ฃผ์ œ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ
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 ํ•œ๋ˆˆ์— ๋น„๊ต

๋น„๊ต ํ•ญ๋ชฉ ๐Ÿ”ต SQL (๊ด€๊ณ„ํ˜• DB) ๐Ÿƒ NoSQL
๋ฐ์ดํ„ฐ ํ˜•์‹ ํ‘œ(ํ…Œ์ด๋ธ”) ํ˜•์‹ ๋ฌธ์„œ, ํ‚ค-๊ฐ’, ๊ทธ๋ž˜ํ”„ ๋“ฑ
์Šคํ‚ค๋งˆ ๊ณ ์ • ์Šคํ‚ค๋งˆ (๋ฏธ๋ฆฌ ์ •์˜) ์œ ์—ฐํ•œ ์Šคํ‚ค๋งˆ (๋™์ )
ํ™•์žฅ ๋ฐฉ์‹ ์ˆ˜์ง ํ™•์žฅ (์„œ๋ฒ„ ์—…๊ทธ๋ ˆ์ด๋“œ) ์ˆ˜ํ‰ ํ™•์žฅ (์„œ๋ฒ„ ์ถ”๊ฐ€)
JOIN โœ… ๊ฐ•๋ ฅํ•œ JOIN ์ง€์› โŒ ๋ณดํ†ต JOIN ์—†์Œ
ACID โœ… ์™„์ „ํ•œ ACID ๋ณด์žฅ โš ๏ธ ๋ณดํ†ต ์ตœ์ข… ์ผ๊ด€์„ฑ
๋Œ€ํ‘œ DB MySQL, PostgreSQL, Oracle MongoDB, Redis, Cassandra
์ ํ•ฉํ•œ ์ƒํ™ฉ ๊ธˆ์œต, ๊ฒฐ์ œ, ์ •ํ˜• ๋ฐ์ดํ„ฐ SNS, ๋กœ๊ทธ, ์‹ค์‹œ๊ฐ„, ๋Œ€์šฉ๋Ÿ‰

๐Ÿ—‚๏ธ 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๊ฐ€์ง€๋ฅผ ๋™์‹œ์— ๋ชจ๋‘ ๋งŒ์กฑํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ด๋ก 

ํ•ญ๋ชฉ ์„ค๋ช… ์˜ˆ์‹œ
C (Consistency) ๋ชจ๋“  ๋…ธ๋“œ๊ฐ€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์Œ ์€ํ–‰ ์ž”์•ก: ์–ด๋””์„œ ์กฐํšŒํ•ด๋„ ๊ฐ™์•„์•ผ ํ•จ
A (Availability) ๋ชจ๋“  ์š”์ฒญ์— ์‘๋‹ต ๊ฐ€๋Šฅ (๋‹ค์šด ์—†์Œ) 24/7 ์„œ๋น„์Šค ์œ ์ง€
P (Partition tolerance) ๋„คํŠธ์›Œํฌ ๋‹จ์ ˆ๋ผ๋„ ๋™์ž‘ ์„œ๋ฒ„ ๊ฐ„ ์—ฐ๊ฒฐ ๋Š๊ฒจ๋„ ์ž‘๋™

ํ˜„์‹ค์—์„œ 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 ํ•ต์‹ฌ ์šฉ์–ด ๋น„๊ต

MySQL (SQL) MongoDB (NoSQL) ์„ค๋ช…
Database Database ๋™์ผ: ๋ฐ์ดํ„ฐ์˜ ์ง‘ํ•ฉ
Table Collection ๋ฐ์ดํ„ฐ๋ฅผ ๋ฌถ๋Š” ๊ทธ๋ฃน
Row (ํ–‰) Document ํ•˜๋‚˜์˜ ๋ฐ์ดํ„ฐ ๋‹จ์œ„ (JSON ํ˜•ํƒœ)
Column (์—ด) Field ๋ฌธ์„œ ์•ˆ์˜ ์†์„ฑ
Primary Key _id ์ž๋™ ์ƒ์„ฑ ObjectId (12๋ฐ”์ดํŠธ)
JOIN $lookup (Aggregation) ์ปฌ๋ ‰์…˜ ๊ฐ„ ๊ฒฐํ•ฉ (๊ถŒ์žฅํ•˜์ง€ ์•Š์Œ)
WHERE ์ ˆ Query Filter ์กฐ๊ฑด ํ•„ํ„ฐ๋ง

๐Ÿ“„ 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 ์ฃผ์š” ์‚ฌ์šฉ ์‚ฌ๋ก€ ์ •๋ฆฌ

์‚ฌ์šฉ ์‚ฌ๋ก€ ์ž๋ฃŒ๊ตฌ์กฐ ์„ค๋ช…
API ์‘๋‹ต ์บ์‹ฑ String DB ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ JSON์œผ๋กœ ์บ์‹ฑ, TTL ์„ค์ •
๋กœ๊ทธ์ธ ์„ธ์…˜ Hash ์„ธ์…˜ID๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ
์‹ค์‹œ๊ฐ„ ๋žญํ‚น Sorted Set ๊ฒŒ์ž„ ์ ์ˆ˜, ์ธ๊ธฐ ์ƒํ’ˆ, ์ข‹์•„์š” ์ˆœ์œ„
์ตœ๊ทผ ๋ณธ ์ƒํ’ˆ List LPUSH + LTRIM์œผ๋กœ ์ตœ๊ทผ N๊ฐœ ์œ ์ง€
์ค‘๋ณต ๋ฐฉ์ง€ Set ์ข‹์•„์š” ์ค‘๋ณต ํด๋ฆญ ๋ฐฉ์ง€, ์œ ๋‹ˆํฌ ๋ฐฉ๋ฌธ์ž
์ฑ„ํŒ…/์•Œ๋ฆผ Pub/Sub ์‹ค์‹œ๊ฐ„ ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ
๋ถ„์‚ฐ ๋ฝ String + NX ๋™์‹œ์„ฑ ์ œ์–ด (์žฌ๊ณ  ์ฐจ๊ฐ ๋“ฑ)

๐Ÿ“Œ 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 ๋น„๊ต

ํ•ญ๋ชฉ MySQL LIKE Elasticsearch
๊ฒ€์ƒ‰ ๋ฐฉ์‹ %๊ฒ€์ƒ‰์–ด% ํŒจํ„ด ๋งค์นญ ์—ญ์ƒ‰์ธ ๊ธฐ๋ฐ˜ ์ „๋ฌธ ๊ฒ€์ƒ‰
์†๋„ (1์–ต ๊ฑด) ์ˆ˜์ดˆ~์ˆ˜๋ถ„ ๋ฐ€๋ฆฌ์ดˆ(ms)
ํ˜•ํƒœ์†Œ ๋ถ„์„ โŒ โœ… (ํ•œ๊ตญ์–ด nori ํ”Œ๋Ÿฌ๊ทธ์ธ)
์ ํ•ฉํ•œ ์šฉ๋„ ์ •ํ˜• ๋ฐ์ดํ„ฐ ์กฐํšŒ ์ „๋ฌธ ๊ฒ€์ƒ‰, ๋กœ๊ทธ ๋ถ„์„

๐Ÿ“Œ 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

์›์น™ ์„ค๋ช…
์ฟผ๋ฆฌ ํŒจํ„ด ์šฐ์„  ๊ฐ€์žฅ ์ž์ฃผ ์“ฐ๋Š” ์ฟผ๋ฆฌ์— ๋งž๊ฒŒ ์Šคํ‚ค๋งˆ ์„ค๊ณ„ (SQL๊ณผ ๋ฐ˜๋Œ€)
ํ•จ๊ป˜ ์ฝ์œผ๋ฉด ํ•จ๊ป˜ ์ €์žฅ ํ•ญ์ƒ ๊ฐ™์ด ์กฐํšŒ๋˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ์ž„๋ฒ ๋”ฉ
๋…๋ฆฝ ์—…๋ฐ์ดํŠธ๋ฉด ์ฐธ์กฐ ์ž์ฃผ ๋…๋ฆฝ์ ์œผ๋กœ ์ˆ˜์ •๋˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ์ฐธ์กฐ
๋ฐฐ์—ด ํฌ๊ธฐ ์ œํ•œ ์ž„๋ฒ ๋”ฉ ๋ฐฐ์—ด์ด ๋ฌดํ•œ์ • ์ปค์ง€๋ฉด ๋ฌธ์„œ ํฌ๊ธฐ ์ดˆ๊ณผ (16MB)
Bucket ํŒจํ„ด ์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ๋ฅผ ์‹œ๊ฐ„๋Œ€๋ณ„๋กœ ๋ฌถ์–ด์„œ ์ €์žฅ (IoT, ๋กœ๊ทธ)

๐Ÿ“Œ 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 ์‹œ์ž‘ํ•˜๊ธฐ

1

ํšŒ์›๊ฐ€์ž…: cloud.mongodb.com ์ ‘์† → Google ๊ณ„์ •์œผ๋กœ ๊ฐ„ํŽธ ๊ฐ€์ž…

2

ํด๋Ÿฌ์Šคํ„ฐ ์ƒ์„ฑ: "Free" ์„ ํƒ → AWS/Google Cloud ์„ ํƒ → ์„œ์šธ(ap-northeast-2) ๋ฆฌ์ „ ์„ ํƒ

3

๋ณด์•ˆ ์„ค์ •: DB ์‚ฌ์šฉ์ž ์ƒ์„ฑ (์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ) → IP ํ—ˆ์šฉ (0.0.0.0/0 = ๋ชจ๋“  IP ํ—ˆ์šฉ)

4

์—ฐ๊ฒฐ: "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 ์ŠคํŽ™

ํ•ญ๋ชฉ Free Tier (M0)
์ €์žฅ ๊ณต๊ฐ„ 512MB (๋ฌด๋ฃŒ)
์—ฐ๊ฒฐ ์ˆ˜ ์ตœ๋Œ€ 500๊ฐœ ์—ฐ๊ฒฐ
๊ฐ€์šฉ์„ฑ 3๊ฐœ ๋…ธ๋“œ Replica Set
๋ฐฑ์—… ๋ฏธํฌํ•จ (์œ ๋ฃŒ ํ”Œ๋žœ๋ถ€ํ„ฐ)
์ ํ•ฉํ•œ ์šฉ๋„ ํ•™์Šต, ํฌํŠธํด๋ฆฌ์˜ค, ์†Œ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ

๐Ÿ“Œ 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 ๊ด€๋ จ ์‹ค์ œ ๋ฉด์ ‘์—์„œ ์ž์ฃผ ๋‚˜์˜ค๋Š” ์งˆ๋ฌธ๋“ค!

Q1. NoSQL๊ณผ SQL์˜ ์ฐจ์ด๋ฅผ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.

SQL์€ ํ…Œ์ด๋ธ” ํ˜•ํƒœ์˜ ์ •ํ˜• ๋ฐ์ดํ„ฐ๋ฅผ ACID ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ณต์žกํ•œ JOIN๊ณผ ๊ฐ•ํ•œ ์ผ๊ด€์„ฑ์ด ์žฅ์ ์ด๋‚˜ ์ˆ˜ํ‰ ํ™•์žฅ์ด ์–ด๋ ต์Šต๋‹ˆ๋‹ค. NoSQL์€ ๋ฌธ์„œ/ํ‚ค-๊ฐ’/๊ทธ๋ž˜ํ”„ ๋“ฑ ๋‹ค์–‘ํ•œ ํ˜•ํƒœ๋กœ ์ €์žฅํ•˜๋ฉฐ, ์Šคํ‚ค๋งˆ๊ฐ€ ์œ ์—ฐํ•˜๊ณ  ์ˆ˜ํ‰ ํ™•์žฅ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋Š” ACID ๋ณด์žฅ์ด ์•ฝํ•˜๊ณ  JOIN์ด ์ œํ•œ์ ์ด๋ผ๋Š” ์ ์ž…๋‹ˆ๋‹ค.

Q2. MongoDB์—์„œ _id๋Š” ๋ฌด์—‡์ด๊ณ , ์™œ ObjectId๋ฅผ ์‚ฌ์šฉํ•˜๋‚˜์š”?

_id๋Š” MongoDB์˜ ๊ธฐ๋ณธํ‚ค๋กœ ๋ชจ๋“  ๋ฌธ์„œ์— ์ž๋™ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ObjectId๋Š” 12๋ฐ”์ดํŠธ ๊ตฌ์กฐ๋กœ ์•ž 4๋ฐ”์ดํŠธ๋Š” ํƒ€์ž„์Šคํƒฌํ”„, 5๋ฐ”์ดํŠธ๋Š” ๋žœ๋ค๊ฐ’, 3๋ฐ”์ดํŠธ๋Š” ์นด์šดํ„ฐ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ ์„œ๋ฒ„ ๊ฐ„ ์ถฉ๋Œ ์—†์ด ์ „์—ญ ์œ ์ผํ•œ ID๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด AUTO_INCREMENT ๋ฐฉ์‹๋ณด๋‹ค ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

Q3. Redis๋ฅผ ์บ์‹œ๋กœ ์‚ฌ์šฉํ•  ๋•Œ Cache Invalidation ์ „๋žต์€?

โ‘  TTL ๋งŒ๋ฃŒ: ์ผ์ • ์‹œ๊ฐ„ ํ›„ ์ž๋™ ์‚ญ์ œ (๊ฐ„๋‹จํ•˜์ง€๋งŒ ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜ ๊ฐ€๋Šฅ). โ‘ก Write-Through: DB ์“ธ ๋•Œ ์บ์‹œ๋„ ๋™์‹œ์— ์—…๋ฐ์ดํŠธ. โ‘ข Write-Behind: ์บ์‹œ์— ๋จผ์ € ์“ฐ๊ณ  ๋‚˜์ค‘์— DB์— ๋ฐ˜์˜. โ‘ฃ ์บ์‹œ ์ง์ ‘ ์‚ญ์ œ: ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ๊ด€๋ จ ์บ์‹œ ํ‚ค๋ฅผ ์ฆ‰์‹œ DEL. ์‹ค๋ฌด์—์„œ๋Š” ์ฃผ๋กœ TTL + ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ์บ์‹œ ์ง์ ‘ ์‚ญ์ œ๋ฅผ ํ˜ผ์šฉํ•ฉ๋‹ˆ๋‹ค.

Q4. MongoDB์—์„œ ์ž„๋ฒ ๋”ฉ๊ณผ ์ฐธ์กฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์„ ํƒํ•˜๋‚˜์š”?

โ‘  ๋ฐ์ดํ„ฐ ํฌ๊ธฐ: ๋ฌดํ•œ์ • ์ปค์งˆ ์ˆ˜ ์žˆ์œผ๋ฉด ์ฐธ์กฐ (16MB ๋ฌธ์„œ ํ•œ๋„). โ‘ก ์ ‘๊ทผ ํŒจํ„ด: ํ•ญ์ƒ ํ•จ๊ป˜ ์กฐํšŒ๋˜๋ฉด ์ž„๋ฒ ๋”ฉ, ๋…๋ฆฝ์ ์œผ๋กœ ์กฐํšŒ๋˜๋ฉด ์ฐธ์กฐ. โ‘ข ์—…๋ฐ์ดํŠธ ๋นˆ๋„: ์ž„๋ฒ ๋”ฉ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋ฉด ์—ฌ๋Ÿฌ ๊ณณ์„ ์ˆ˜์ •ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ฐธ์กฐ๊ฐ€ ์œ ๋ฆฌ. โ‘ฃ 1:N ๋น„์œจ: N์ด ๋งค์šฐ ํฌ๋ฉด ์ฐธ์กฐ๊ฐ€ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

Q5. Redis์˜ ๋ฐ์ดํ„ฐ ์˜์†์„ฑ(Persistence)์€ ์–ด๋–ป๊ฒŒ ๋ณด์žฅํ•˜๋‚˜์š”?

Redis๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์˜์†์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. โ‘  RDB(์Šค๋ƒ…์ƒท): ์ผ์ • ์‹œ๊ฐ„๋งˆ๋‹ค ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋””์Šคํฌ์— ๋คํ”„. ๋น ๋ฅด์ง€๋งŒ ๋งˆ์ง€๋ง‰ ์Šค๋ƒ…์ƒท ์ดํ›„ ๋ฐ์ดํ„ฐ ์œ ์‹ค ๊ฐ€๋Šฅ. โ‘ก AOF(Append Only File): ๋ชจ๋“  ์“ฐ๊ธฐ ๋ช…๋ น์„ ๋กœ๊ทธ๋กœ ์ €์žฅ. ๋ฐ์ดํ„ฐ ์œ ์‹ค ์ตœ์†Œํ™”์ง€๋งŒ ํŒŒ์ผ ํฌ๊ธฐ ์ฆ๊ฐ€. ํ˜„์—…์—์„œ๋Š” ๋‘˜์„ ํ˜ผ์šฉ(RDB + AOF)ํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Q6. CAP ์ด๋ก ์ด ๋ฌด์—‡์ธ์ง€ MongoDB๋ฅผ ์˜ˆ์‹œ๋กœ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.

CAP = Consistency(์ผ๊ด€์„ฑ), Availability(๊ฐ€์šฉ์„ฑ), Partition tolerance(๋ถ„์‚ฐ ๋‚ด์„ฑ). ์„ธ ๊ฐ€์ง€๋ฅผ ๋™์‹œ์— ๋งŒ์กฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. MongoDB๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ CP(์ผ๊ด€์„ฑ + ๋ถ„์‚ฐ ๋‚ด์„ฑ) ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. Replica Set์—์„œ Primary ์žฅ์•  ์‹œ Secondary๊ฐ€ ์„ ์ถœ๋˜๋Š” ๋™์•ˆ ์ž ์‹œ ์“ฐ๊ธฐ ๋ถˆ๊ฐ€(๊ฐ€์šฉ์„ฑ ํฌ์ƒ), ํ•˜์ง€๋งŒ ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

Q7. MongoDB Aggregation Pipeline์˜ $match๋Š” ์™œ ์•ž์— ๋‘๋Š” ๊ฒŒ ์ข‹๋‚˜์š”?

$match๋ฅผ ํŒŒ์ดํ”„๋ผ์ธ ์•ž์— ๋‘๋ฉด ํ•„ํ„ฐ๋ง ํ›„์— ๋‹ค์Œ ์Šคํ…Œ์ด์ง€๊ฐ€ ์‹คํ–‰๋˜์–ด ์ฒ˜๋ฆฌํ•  ๋ฌธ์„œ ์ˆ˜๊ฐ€ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค. ๋’ค์— ๋‘๋ฉด ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ $group ๋“ฑ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ ํ›„ ํ•„ํ„ฐ๋งํ•˜๋ฏ€๋กœ ๋งค์šฐ ๋น„ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ $match๊ฐ€ ์ธ๋ฑ์Šค๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ธ๋ฑ์Šค๋œ ํ•„๋“œ๋กœ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

Q8. Redis Sorted Set์œผ๋กœ ์‹ค์‹œ๊ฐ„ ๋žญํ‚น์„ ๊ตฌํ˜„ํ•  ๋•Œ ๊ณ ๋ ค์‚ฌํ•ญ์€?

โ‘  ๊ธฐ๊ฐ„๋ณ„ ๋žญํ‚น: ์ผ๋ณ„/์ฃผ๋ณ„ ํ‚ค๋ฅผ ๋”ฐ๋กœ ๊ด€๋ฆฌ (์˜ˆ: ranking:2026-03-27). โ‘ก TTL ์„ค์ •: ์˜ค๋ž˜๋œ ๋žญํ‚น ํ‚ค๋Š” EXPIRE๋กœ ์ž๋™ ์‚ญ์ œ. โ‘ข ๋™์  ์ฒ˜๋ฆฌ: Sorted Set์€ ์ ์ˆ˜๊ฐ€ ๊ฐ™์œผ๋ฉด ์‚ฌ์ „์ˆœ์œผ๋กœ ์ •๋ ฌ๋˜๋ฏ€๋กœ ๋ณ„๋„ ์ฒ˜๋ฆฌ ํ•„์š”. โ‘ฃ ์ •ํ•ฉ์„ฑ: Redis๊ฐ€ ์žฌ์‹œ์ž‘๋˜๋ฉด ๋ฐ์ดํ„ฐ ์œ ์‹ค ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ์ฃผ๊ธฐ์ ์œผ๋กœ DB์— ๋™๊ธฐํ™”.

Q9. Elasticsearch๋ฅผ MySQL๊ณผ ํ•จ๊ป˜ ์“ธ ๋•Œ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”๋ฅผ ์–ด๋–ป๊ฒŒ ํ•˜๋‚˜์š”?

โ‘  ์ด์ค‘ ์“ฐ๊ธฐ(Dual Write): ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ MySQL ์ €์žฅ ํ›„ Elasticsearch์—๋„ ์ €์žฅ. ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์‹คํŒจ ์ฒ˜๋ฆฌ ํ•„์š”. โ‘ก CDC(Change Data Capture): MySQL ๋ฐ”์ด๋„ˆ๋ฆฌ ๋กœ๊ทธ(binlog)๋ฅผ Debezium ๋“ฑ์œผ๋กœ ์ฝ์–ด ES์— ์ž๋™ ๋ฐ˜์˜. โ‘ข ๋ฐฐ์น˜ ๋™๊ธฐํ™”: ์ฃผ๊ธฐ์ ์œผ๋กœ MySQL์—์„œ ๋ณ€๊ฒฝ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด ES๋ฅผ ๊ฐฑ์‹ . ํ˜„์—…์—์„œ๋Š” CDC ๋ฐฉ์‹์ด ๊ฐ€์žฅ ์•ˆ์ •์ ์ž…๋‹ˆ๋‹ค.

Q10. NoSQL์„ ์„ ํƒํ•  ๋•Œ ๊ฐ€์žฅ ์ค‘์š”ํ•˜๊ฒŒ ๊ณ ๋ คํ•˜๋Š” ๊ฒƒ์€?

โ‘  ์ฟผ๋ฆฌ ํŒจํ„ด: ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ž์ฃผ ์ฝ๊ณ  ์“ฐ๋Š”๊ฐ€. โ‘ก ์ผ๊ด€์„ฑ ์š”๊ตฌ์‚ฌํ•ญ: ๊ธˆ์œต์ฒ˜๋Ÿผ ๊ฐ•ํ•œ ์ผ๊ด€์„ฑ์ด ํ•„์š”ํ•œ๊ฐ€. โ‘ข ํ™•์žฅ์„ฑ: ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ๋ฐ์ดํ„ฐ์™€ ํŠธ๋ž˜ํ”ฝ์ด ์˜ˆ์ƒ๋˜๋Š”๊ฐ€. โ‘ฃ ํŒ€ ์—ญ๋Ÿ‰: ํŒ€์›๋“ค์˜ ๊ธฐ์ˆ  ์Šคํƒ๊ณผ ํ•™์Šต ๋น„์šฉ. โ‘ค ์šด์˜ ๋น„์šฉ: ํด๋ผ์šฐ๋“œ ๋งค๋‹ˆ์ง€๋“œ ์„œ๋น„์Šค ๋น„์šฉ. ๊ธฐ์ˆ ์  ์šฐ์—ด๋ณด๋‹ค ์„œ๋น„์Šค ํŠน์„ฑ์— ๋งž๋Š” ์„ ํƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ 11. ํ•™์Šต ๋กœ๋“œ๋งต & ์ฒดํฌ๋ฆฌ์ŠคํŠธ

๐Ÿ“–

1์ฃผ์ฐจ

NoSQL ๊ฐœ๋… ์ดํ•ด
SQL vs NoSQL ๋น„๊ต
CAP ์ด๋ก 

๐Ÿƒ

2์ฃผ์ฐจ

MongoDB CRUD
Atlas ๊ณ„์ • ์ƒ์„ฑ
Mongoose ์—ฐ๊ฒฐ

โšก

3์ฃผ์ฐจ

Redis ์ž๋ฃŒ๊ตฌ์กฐ
์บ์‹ฑ ํŒจํ„ด
์„ธ์…˜ ๊ด€๋ฆฌ

๐Ÿ—๏ธ

4์ฃผ์ฐจ

MongoDB Aggregation
์ธ๋ฑ์Šค ์ตœ์ ํ™”
๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง

๐Ÿ›๏ธ

5์ฃผ์ฐจ

์‹ค์ „ ํ”„๋กœ์ ํŠธ
๋ธ”๋กœ๊ทธ/์‡ผํ•‘๋ชฐ ๊ตฌํ˜„
๋ฉด์ ‘ ์ค€๋น„

๐Ÿ› ๏ธ ์ถ”์ฒœ ํ•™์Šต ๋„๊ตฌ & ํ™˜๊ฒฝ

๋„๊ตฌ ์šฉ๋„ ๋น„์šฉ
MongoDB Atlas ํด๋ผ์šฐ๋“œ MongoDB (์„ค์น˜ ๋ถˆํ•„์š”) ๋ฌด๋ฃŒ 512MB
MongoDB Compass GUI ๊ด€๋ฆฌ ๋„๊ตฌ (์ฟผ๋ฆฌ ํ…Œ์ŠคํŠธ) ๋ฌด๋ฃŒ
Redis Stack (Docker) ๋กœ์ปฌ Redis ์„ค์น˜ (RedisInsight ํฌํ•จ) ๋ฌด๋ฃŒ
Upstash Redis ํด๋ผ์šฐ๋“œ Redis (Vercel ์—ฐ๋™ ์ตœ์ ) ๋ฌด๋ฃŒ Tier
mongosh MongoDB ๊ณต์‹ CLI ์…ธ ๋ฌด๋ฃŒ

๐ŸŽ‰ DatabaseDevGuide0005 ์™„๋ฃŒ!

NoSQL์˜ ์„ธ๊ณ„๋ฅผ ์™„์ „ ์ •๋ณตํ–ˆ์Šต๋‹ˆ๋‹ค!
MongoDB, Redis, Elasticsearch — ์ด์ œ ํ˜„์—…์—์„œ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•˜๊ณ  ํ™œ์šฉํ•˜์„ธ์š”!

๐Ÿ“Œ ๋‹ค์Œ ๋‹จ๊ณ„: DatabaseDevGuide0006 - ์‹ค์ „ ํ™œ์šฉ
(๋ฐฑ์—”๋“œ ์—ฐ๊ฒฐ, ํŠธ๋žœ์žญ์…˜, ๋ฐฑ์—… & ๋ณด์•ˆ)

โœ… MongoDB CRUD โœ… Aggregation โœ… Redis ์ž๋ฃŒ๊ตฌ์กฐ โœ… ์บ์‹ฑ ํŒจํ„ด โœ… SQL vs NoSQL
๋ฐ˜์‘ํ˜•