Ver3.0 ๐ NoSQL์ ์๋ ์ฐ๋งฅ! MongoDB์ Cassandra๋ก ๋๊ท๋ชจ ๋ฐ์ดํฐ ์ ๋ณตํ๊ธฐ

๐ NoSQL์ ์๋ ์ฐ๋งฅ! MongoDB์ Cassandra๋ก ๋๊ท๋ชจ ๋ฐ์ดํฐ ์ ๋ณตํ๊ธฐ
์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ๋ฐฐ์ฐ๋ NoSQL ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ์ ๋ณต ๊ฐ์ด๋
๋จผ์ ์ด๋ฐ ์ํฉ์ ์์ํด๋ด. ๋๊ฐ ๋ง๋ ์ฑ์ด ๊ฐ์๊ธฐ ๋๋ฐ์ด ๋ฌ์ด! ์ฌ์ฉ์๊ฐ ํ๋ฃจ์ 10๋ช ์์ 100๋ง ๋ช ์ผ๋ก ํญ๋ฐ์ ์ผ๋ก ๋์ด๋ฌ๋ค๊ณ ์๊ฐํด๋ด. ๊ธฐ์กด์ ์ฌ์ฉํ๋ MySQL์ด๋ PostgreSQL ๊ฐ์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค(RDBMS)๋ ์จ์ด ํฑํฑ ๋งํ๊ธฐ ์์ํ ๊ฑฐ์ผ. ์ ๊ทธ๋ด๊น?
์ ํต์ ์ธ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ACID ์์ฑ(Atomicity, Consistency, Isolation, Durability)์ ์๊ฒฉํ๊ฒ ์งํค๋ฉด์ ๋ฐ์ดํฐ์ ์ ํฉ์ฑ์ ๋ณด์ฅํด. ์ด๊ฑด ์ ๋ง ํ๋ฅญํ ํน์ฑ์ด์ง๋ง, ๋๊ท๋ชจ ํธ๋ํฝ๊ณผ ๋น ๋ฐ์ดํฐ ํ๊ฒฝ์์๋ ๋ณ๋ชฉํ์์ด ๋ฐ์ํ ์ ์์ด. ํนํ ์ํ์ ํ์ฅ(Scale-out)์ด ์ด๋ ต๋ค๋ ๊ฒ ํฐ ๋ฌธ์ ์ผ.
โข ์คํค๋ง ๋ณ๊ฒฝ์ ์ด๋ ค์: ํ ์ด๋ธ ๊ตฌ์กฐ๋ฅผ ๋ฐ๊พธ๋ ค๋ฉด ์ ์ฒด ์์คํ ์ ์ํฅ
โข ์กฐ์ธ ์ฐ์ฐ์ ๋ถ๋ด: ๋ณต์กํ ๊ด๊ณ๋ฅผ ๋ค๋ฃฐ ๋ ์ฑ๋ฅ ์ ํ
โข ๋ถ์ฐ ํ๊ฒฝ์์์ ๋ณต์ก์ฑ: ์ฌ๋ฌ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋๋๊ธฐ ์ด๋ ค์
JSON์ด๋ BSON ํํ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํด. ๋ง์น ์๋ ๋ฌธ์์ฒ๋ผ ํ๋์ ๋ฌธ์ ์์ ๊ด๋ จ๋ ๋ชจ๋ ์ ๋ณด๋ฅผ ๋ด์ ์ ์์ด์ ์ง๊ด์ ์ด์ผ. ์คํค๋ง๊ฐ ์ ์ฐํด์ ํ๋๋ฅผ ์์ ๋กญ๊ฒ ์ถ๊ฐํ๊ฑฐ๋ ๋ณ๊ฒฝํ ์ ์๋ค๋ ๊ฒ ์ต๊ณ ์ ์ฅ์ ์ด์ง! ๐
์ ํฉํ ์ฌ์ฉ ์ฌ๋ก: ์ฝํ ์ธ ๊ด๋ฆฌ ์์คํ , ์ ์์๊ฑฐ๋ ์นดํ๋ก๊ทธ, ์ฌ์ฉ์ ํ๋กํ ๊ด๋ฆฌ
๊ฐ์ฅ ๋จ์ํ ํํ์ผ. ํค(Key)์ ๊ฐ(Value)์ ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํด. ๋ง์น ์ฌ์ ์ฒ๋ผ ๋จ์ด(ํค)๋ฅผ ์ฐพ์ผ๋ฉด ๋ป(๊ฐ)์ด ๋์ค๋ ๊ตฌ์กฐ์ง. ์์ฒญ๋๊ฒ ๋น ๋ฅธ ์กฐํ ์๋๊ฐ ํน์ง์ด์ผ! โก
์ ํฉํ ์ฌ์ฉ ์ฌ๋ก: ์ธ์ ๊ด๋ฆฌ, ์บ์ฑ, ์ค์๊ฐ ์ถ์ฒ ์์คํ
๋ฐ์ดํฐ๋ฅผ ํ์ด ์๋ ์ด(์ปฌ๋ผ) ๋จ์๋ก ์ ์ฅํด. ๊ฐ์ ์ข ๋ฅ์ ๋ฐ์ดํฐ๋ฅผ ๋ชจ์์ ์ ์ฅํ๊ธฐ ๋๋ฌธ์ ์์ถ๋ฅ ์ด ๋๊ณ , ํน์ ์ปฌ๋ผ๋ง ๋น ๋ฅด๊ฒ ์ฝ์ ์ ์์ด. ๋์ฉ๋ ๋ฐ์ดํฐ ๋ถ์์ ์ต์ ํ๋์ด ์์ง! ๐
์ ํฉํ ์ฌ์ฉ ์ฌ๋ก: ์๊ณ์ด ๋ฐ์ดํฐ, ๋ก๊ทธ ๋ถ์, IoT ์ผ์ ๋ฐ์ดํฐ
๋ฐ์ดํฐ ๊ฐ์ ๊ด๊ณ๋ฅผ ๊ทธ๋ํ ๊ตฌ์กฐ๋ก ํํํด. ๋ ธ๋(Node)์ ์ฃ์ง(Edge)๋ก ์ฐ๊ฒฐ๋์ด ์์ด์ ๋ณต์กํ ๊ด๊ณ๋ฅผ ํ์ํ๋ ๋ฐ ํ์ํด. SNS์ ์น๊ตฌ ๊ด๊ณ๋ ์ถ์ฒ ์์คํ ์ ๋ฑ์ด์ผ! ๐ธ๏ธ
์ ํฉํ ์ฌ์ฉ ์ฌ๋ก: ์์ ๋คํธ์ํฌ, ์ถ์ฒ ์์ง, ์ฌ๊ธฐ ํ์ง ์์คํ
MongoDB์ ๊ฐ์ฅ ํฐ ๋งค๋ ฅ์ ๋ฐ๋ก ์คํค๋ง๋ฆฌ์ค(Schema-less) ๊ตฌ์กฐ์ผ. ์ ํํ๋ "๋์ ์คํค๋ง"๋ผ๊ณ ํํํ๋ ๊ฒ ๋ง์. ๊ฐ์ ์ปฌ๋ ์ ์์ ์๋ ๋ฌธ์๋ค์ด ์๋ก ๋ค๋ฅธ ํ๋๋ฅผ ๊ฐ์ง ์ ์๋ค๋ ๋ป์ด์ผ.
์๋ฅผ ๋ค์ด๋ณผ๊ฒ:
// ์ฌ์ฉ์ ๋ฌธ์ 1
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"name": "๊น์ฒ ์",
"email": "chulsoo@example.com",
"age": 28
}
// ์ฌ์ฉ์ ๋ฌธ์ 2 - ์ถ๊ฐ ํ๋๊ฐ ์์ด๋ OK!
{
"_id": ObjectId("507f1f77bcf86cd799439012"),
"name": "์ด์ํฌ",
"email": "younghee@example.com",
"age": 32,
"address": {
"city": "์์ธ",
"district": "๊ฐ๋จ๊ตฌ"
},
"hobbies": ["๋
์", "์ฌํ", "์๋ฆฌ"]
}
๋ณด๋ค์ํผ ๋ ๋ฒ์งธ ๋ฌธ์์๋ address์ hobbies ํ๋๊ฐ ์ถ๊ฐ๋์ด ์์ง๋ง ์ ํ ๋ฌธ์ ์์ด! ์ด๋ฐ ์ ์ฐ์ฑ ๋๋ถ์ ์ ์์ผ ๊ฐ๋ฐ ํ๊ฒฝ์์ ๋น ๋ฅด๊ฒ ํ๋กํ ํ์
์ ๋ง๋ค๊ณ ๋ฐ๋ณต ๊ฐ์ ํ๊ธฐ์ ์ ๋ง ์ข์. ๐2. ๊ฐ๋ ฅํ ์ฟผ๋ฆฌ ์ธ์ด
MongoDB๋ ํ๋ถํ ์ฟผ๋ฆฌ ๊ธฐ๋ฅ์ ์ ๊ณตํด. SQL๋งํผ์ด๋ ๋ค์ํ ์กฐ๊ฑด์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํ ์ ์์ด:
// ๋์ด๊ฐ 25์ธ ์ด์์ด๊ณ ์์ธ์ ์ฌ๋ ์ฌ์ฉ์ ์ฐพ๊ธฐ
db.users.find({
age: { $gte: 25 },
"address.city": "์์ธ"
})
// ์ง๊ณ ํ์ดํ๋ผ์ธ์ ์ฌ์ฉํ ๋ณต์กํ ๋ถ์
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: {
_id: "$customerId",
totalAmount: { $sum: "$amount" }
}},
{ $sort: { totalAmount: -1 } },
{ $limit: 10 }
])
MongoDB๋ ์ค๋ฉ(Sharding)์ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฌ ์๋ฒ์ ์๋์ผ๋ก ๋ถ์ฐ์์ผ. ๋ฐ์ดํฐ๊ฐ ๋์ด๋๋ฉด ์๋ฒ๋ฅผ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋๋๊น ํ์ฅ์ด ์ ๋ง ์ฌ์! ๐ช
์ค๋ฉ์ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ์ดํด๋ณด๋ฉด:
โข Config Server: ๋ฉํ๋ฐ์ดํฐ์ ํด๋ฌ์คํฐ ์ค์ ์ ๋ณด๋ฅผ ์ ์ฅ
โข Mongos: ์ฟผ๋ฆฌ ๋ผ์ฐํฐ ์ญํ , ํด๋ผ์ด์ธํธ ์์ฒญ์ ์ ์ ํ ์ค๋๋ก ์ ๋ฌ
โข Shard Key: ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ๋ถ์ฐ์ํฌ์ง ๊ฒฐ์ ํ๋ ๊ธฐ์ค
MongoDB๋ ๋ ํ๋ฆฌ์นด ์ (Replica Set)์ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฌ ์๋ฒ์ ๋ณต์ ํด. ์ฃผ ์๋ฒ(Primary)๊ฐ ๋ค์ด๋๋ฉด ์๋์ผ๋ก ๋ถ ์๋ฒ(Secondary) ์ค ํ๋๊ฐ ์ฃผ ์๋ฒ๋ก ์น๊ฒฉ๋์ด ์๋น์ค ์ค๋จ ์์ด ์ด์ํ ์ ์์ด! ๐ก๏ธ
์ฌ๋ก 1: ์ ์์๊ฑฐ๋ ์ํ ์นดํ๋ก๊ทธ
์ ์์๊ฑฐ๋ ์ฌ์ดํธ์์ ์ํ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ๋ค๊ณ ์๊ฐํด๋ด. ์ํ๋ง๋ค ์์ฑ์ด ์ฒ์ฐจ๋ง๋ณ์ด์ผ. ๋ ธํธ๋ถ์ CPU, RAM, ์ ์ฅ์ฉ๋ ๊ฐ์ ์คํ์ด ํ์ํ์ง๋ง, ์๋ฅ๋ ์ฌ์ด์ฆ, ์์, ์์ฌ ์ ๋ณด๊ฐ ํ์ํ์ง. MongoDB์ ์ ์ฐํ ์คํค๋ง๊ฐ ๋ฑ์ด์ผ!
// ๋
ธํธ๋ถ ์ํ
{
"productId": "LAPTOP001",
"name": "๊ณ ์ฑ๋ฅ ๊ฒ์ด๋ฐ ๋
ธํธ๋ถ",
"category": "์ ์์ ํ",
"price": 1500000,
"specs": {
"cpu": "Intel i7-12700H",
"ram": "16GB DDR5",
"storage": "512GB NVMe SSD",
"gpu": "RTX 3060"
},
"reviews": [
{
"userId": "user123",
"rating": 5,
"comment": "์ ๋ง ๋น ๋ฅด๊ณ ์ข์์!",
"date": ISODate("2024-01-15")
}
]
}
// ์๋ฅ ์ํ
{
"productId": "SHIRT001",
"name": "๋ฉด ๋ฐํ ํฐ์
์ธ ",
"category": "์๋ฅ",
"price": 29000,
"sizes": ["S", "M", "L", "XL"],
"colors": ["ํ์ดํธ", "๋ธ๋", "๋ค์ด๋น"],
"material": "๋ฉด 100%",
"reviews": []
}
์ฌ๋ก 2: ์ค์๊ฐ ๋ก๊ทธ ์์ง ๋ฐ ๋ถ์์ ํ๋ฆฌ์ผ์ด์ ๋ก๊ทธ๋ฅผ ์ค์๊ฐ์ผ๋ก ์์งํ๊ณ ๋ถ์ํ๋ ์์คํ ์๋ MongoDB๊ฐ ์์ฃผ ์ฌ์ฉ๋ผ. ๋ก๊ทธ ๋ฐ์ดํฐ๋ ๊ตฌ์กฐ๊ฐ ๋ค์ํ๊ณ ์์ด ๋ง๊ธฐ ๋๋ฌธ์ ์ ์ฐ์ฑ๊ณผ ํ์ฅ์ฑ์ด ์ค์ํ๊ฑฐ๋ .
// ์ ํ๋ฆฌ์ผ์ด์
๋ก๊ทธ ๋ฌธ์
{
"timestamp": ISODate("2024-01-20T10:30:45Z"),
"level": "ERROR",
"service": "payment-service",
"message": "๊ฒฐ์ ์ฒ๋ฆฌ ์คํจ",
"errorCode": "PAYMENT_GATEWAY_TIMEOUT",
"userId": "user456",
"metadata": {
"orderId": "ORD123456",
"amount": 50000,
"gateway": "PG์ฌA"
},
"stackTrace": "..."
}
1. ์ธ๋ฑ์ค ์ ๋ต
์ ์ ํ ์ธ๋ฑ์ค๋ ์ฟผ๋ฆฌ ์ฑ๋ฅ์ ์์ญ ๋ฐฐ์์ ์๋ฐฑ ๋ฐฐ๊น์ง ํฅ์์ํฌ ์ ์์ด!
// ๋จ์ผ ํ๋ ์ธ๋ฑ์ค
db.users.createIndex({ email: 1 })
// ๋ณตํฉ ์ธ๋ฑ์ค (์์ฃผ ํจ๊ป ์กฐํ๋๋ ํ๋๋ค)
db.orders.createIndex({ customerId: 1, orderDate: -1 })
// ํ
์คํธ ์ธ๋ฑ์ค (์ ๋ฌธ ๊ฒ์์ฉ)
db.articles.createIndex({ title: "text", content: "text" })
// ์ง๋ฆฌ๊ณต๊ฐ ์ธ๋ฑ์ค (์์น ๊ธฐ๋ฐ ๊ฒ์)
db.places.createIndex({ location: "2dsphere" })
2. ์ฟผ๋ฆฌ ์ต์ ํโข explain() ๋ฉ์๋๋ก ์ฟผ๋ฆฌ ์คํ ๊ณํ์ ๋ถ์ํด
โข ํ์ํ ํ๋๋ง ์ ํํด์ ๊ฐ์ ธ์ (Projection ์ฌ์ฉ)
โข ํ์ด์ง๋ค์ด์ ๊ตฌํ ์ skip()๋ณด๋ค๋ ๋ฒ์ ์ฟผ๋ฆฌ ์ฌ์ฉ
โข ๋๋ ์ ๋ฐ์ดํธ๋ bulkWrite() ํ์ฉ
3. ๋ฌธ์ ์ค๊ณ ์์น
โข ์์ฃผ ํจ๊ป ์กฐํ๋๋ ๋ฐ์ดํฐ๋ ์๋ฒ ๋ฉ(Embedding)์ผ๋ก ํ ๋ฌธ์์ ํฌํจ
โข ๋ ๋ฆฝ์ ์ผ๋ก ๊ด๋ฆฌ๋์ด์ผ ํ๋ ๋ฐ์ดํฐ๋ ์ฐธ์กฐ(Reference) ๋ฐฉ์ ์ฌ์ฉ
โข ๋ฌธ์ ํฌ๊ธฐ๋ 16MB ์ ํ์ ๊ณ ๋ ค (์ผ๋ฐ์ ์ผ๋ก ์ KB ์ด๋ด ๊ถ์ฅ)
โข ๋ฐฐ์ด ํ๋์ ํฌ๊ธฐ๊ฐ ๋ฌดํ์ ์ปค์ง์ง ์๋๋ก ์ฃผ์
Cassandra์ ๊ฐ์ฅ ํฐ ํน์ง์ ๋ชจ๋ ๋ ธ๋๊ฐ ๋๋ฑํ๋ค๋ ๊ฑฐ์ผ! MongoDB์ ๋ ํ๋ฆฌ์นด ์ ์ฒ๋ผ Primary-Secondary ๊ตฌ์กฐ๊ฐ ์๋๋ผ, ๋ชจ๋ ๋ ธ๋๊ฐ ์ฝ๊ธฐ์ ์ฐ๊ธฐ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ด. ์ด๋ฅผ ํผ์ด-ํฌ-ํผ์ด(Peer-to-Peer) ์ํคํ ์ฒ๋ผ๊ณ ํด. ๐
์ด๊ฒ ์ ์ค์ํ๋๊ณ ? ๋จ์ผ ์ฅ์ ์ (Single Point of Failure)์ด ์๋ค๋ ๋ป์ด์ผ! ์ด๋ค ๋ ธ๋๊ฐ ๋ค์ด๋์ด๋ ๋ค๋ฅธ ๋ ธ๋๋ค์ด ๊ณ์ ์๋น์ค๋ฅผ ์ ๊ณตํ ์ ์์ด. ์ง์ ํ ์๋ฏธ์ ๊ณ ๊ฐ์ฉ์ฑ์ด์ง!
2. ์ ํ์ ํ์ฅ์ฑ (Linear Scalability)
Cassandra๋ ๋ ธ๋๋ฅผ ์ถ๊ฐํ๋ฉด ์ฑ๋ฅ์ด ๊ฑฐ์ ์ ํ์ ์ผ๋ก ์ฆ๊ฐํด. ๋ ธ๋๊ฐ 2๋ฐฐ๊ฐ ๋๋ฉด ์ฒ๋ฆฌ๋๋ ๊ฑฐ์ 2๋ฐฐ๊ฐ ๋๋ ๊ฑฐ์ง. ์ด๊ฑด ์ ๋ง ๋๋ผ์ด ํน์ฑ์ด์ผ! ๐
โข 2๊ฐ ๋ ธ๋: ~20,000 writes/sec
โข 4๊ฐ ๋ ธ๋: ~40,000 writes/sec
โข 8๊ฐ ๋ ธ๋: ~80,000 writes/sec
์ค์ ๋ก๋ ๋คํธ์ํฌ ์ค๋ฒํค๋ ๋ฑ์ผ๋ก ์๋ฒฝํ ์ ํ์ ์๋์ง๋ง, ๋ค๋ฅธ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋นํด ์๋ฑํ ๋ฐ์ด๋ ํ์ฅ์ฑ์ ๋ณด์ฌ์ค!
Cassandra๋ ์ฐ๊ธฐ ์ต์ ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ผ. LSM Tree(Log-Structured Merge Tree) ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํด์ ์ฐ๊ธฐ ์์ ์ด ์์ฒญ๋๊ฒ ๋นจ๋ผ. ๋ฐ์ดํฐ๋ฅผ ๋จผ์ ๋ฉ๋ชจ๋ฆฌ์ Memtable์ ์ฐ๊ณ , ๋์ค์ ๋์คํฌ์ SSTable๋ก ํ๋ฌ์ํ๋ ๋ฐฉ์์ด์ผ. โก
์ฐ๊ธฐ ํ๋ก์ธ์ค:
1. CommitLog์ ๋จผ์ ๊ธฐ๋ก (๋ด๊ตฌ์ฑ ๋ณด์ฅ)
2. Memtable(๋ฉ๋ชจ๋ฆฌ)์ ๋ฐ์ดํฐ ์ ์ฅ
3. Memtable์ด ๊ฐ๋ ์ฐจ๋ฉด SSTable(๋์คํฌ)๋ก ํ๋ฌ์
4. ์ฃผ๊ธฐ์ ์ผ๋ก SSTable๋ค์ Compaction (๋ณํฉ ๋ฐ ์ต์ ํ)
4. ํ๋ ๊ฐ๋ฅํ ์ผ๊ด์ฑ (Tunable Consistency)Cassandra๋ CAP ์ ๋ฆฌ์์ AP(Availability + Partition Tolerance)๋ฅผ ์ ํํ ์์คํ ์ด์ผ. ํ์ง๋ง ์ผ๊ด์ฑ ๋ ๋ฒจ์ ์กฐ์ ํ ์ ์์ด์ ์ํฉ์ ๋ง๊ฒ ์ ํํ ์ ์์ด! ๐๏ธ
| ์ผ๊ด์ฑ ๋ ๋ฒจ | ์ค๋ช | ์ฌ์ฉ ์ฌ๋ก |
|---|---|---|
| ONE | 1๊ฐ ๋ ธ๋๋ง ์๋ตํ๋ฉด OK | ์ต๊ณ ์ฑ๋ฅ, ๋ฎ์ ์ผ๊ด์ฑ ์๊ตฌ |
| QUORUM | ๊ณผ๋ฐ์ ๋ ธ๋๊ฐ ์๋ต | ๊ท ํ์กํ ์ ํ (๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ) |
| ALL | ๋ชจ๋ ๋ณต์ ๋ณธ์ด ์๋ต | ์ต๊ณ ์ผ๊ด์ฑ, ๋ฎ์ ๊ฐ์ฉ์ฑ |
| LOCAL_QUORUM | ๊ฐ์ ๋ฐ์ดํฐ์ผํฐ ๋ด ๊ณผ๋ฐ์ | ๋ค์ค ๋ฐ์ดํฐ์ผํฐ ํ๊ฒฝ |
๊ธฐ๋ณธ ๊ฐ๋ :
โข Keyspace: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฌํ ์ต์์ ์ปจํ ์ด๋
โข Table: ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ๊ตฌ์กฐ
โข Partition Key: ๋ฐ์ดํฐ๋ฅผ ์ด๋ ๋ ธ๋์ ์ ์ฅํ ์ง ๊ฒฐ์
โข Clustering Key: ํํฐ์ ๋ด์์ ๋ฐ์ดํฐ ์ ๋ ฌ ๊ธฐ์ค
โข Primary Key: Partition Key + Clustering Key
์์ : ์๊ณ์ด ์ผ์ ๋ฐ์ดํฐ
CREATE KEYSPACE sensor_data WITH replication = {
'class': 'NetworkTopologyStrategy',
'datacenter1': 3
};
CREATE TABLE sensor_readings (
sensor_id text,
reading_date date,
reading_time timestamp,
temperature decimal,
humidity decimal,
pressure decimal,
PRIMARY KEY ((sensor_id, reading_date), reading_time)
) WITH CLUSTERING ORDER BY (reading_time DESC);
์ด ์ค๊ณ์์:โข sensor_id์ reading_date๊ฐ Partition Key (ํจ๊ป ํด์๋์ด ๋ ธ๋ ๊ฒฐ์ )
โข reading_time์ด Clustering Key (์๊ฐ ์ญ์์ผ๋ก ์ ๋ ฌ)
โข ํน์ ์ผ์์ ํน์ ๋ ์ง ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ์กฐํ ๊ฐ๋ฅ
๋ฐ์ดํฐ ์ฝ์ :
INSERT INTO sensor_readings (
sensor_id, reading_date, reading_time,
temperature, humidity, pressure
) VALUES (
'SENSOR_001', '2024-01-20', '2024-01-20 14:30:00',
23.5, 65.2, 1013.25
);
๋ฐ์ดํฐ ์กฐํ:
// ํน์ ์ผ์์ ์ค๋ ๋ฐ์ดํฐ ์กฐํ
SELECT * FROM sensor_readings
WHERE sensor_id = 'SENSOR_001'
AND reading_date = '2024-01-20'
ORDER BY reading_time DESC
LIMIT 100;
// ์๊ฐ ๋ฒ์ ์กฐํ
SELECT * FROM sensor_readings
WHERE sensor_id = 'SENSOR_001'
AND reading_date = '2024-01-20'
AND reading_time >= '2024-01-20 10:00:00'
AND reading_time < '2024-01-20 15:00:00';
โข Partition Key ์์ด๋ ์กฐํ ๋ถ๊ฐ (Full table scan ์ง์)
โข ํํฐ์ ์ด ๋๋ฌด ์ปค์ง์ง ์๋๋ก ์ฃผ์ (๊ถ์ฅ: 100MB ์ดํ)
โข ์ฟผ๋ฆฌ ํจํด์ ๋จผ์ ์ ์ํ๊ณ ํ ์ด๋ธ ์ค๊ณ
Netflix๋ ์์ต ๋ช ์ ์ฌ์ฉ์ ์์ฒญ ๊ธฐ๋ก์ Cassandra๋ก ๊ด๋ฆฌํด. ์ด๋น ์๋ฐฑ๋ง ๊ฑด์ ์ฐ๊ธฐ๊ฐ ๋ฐ์ํ๋๋ฐ, Cassandra์ ๋ฐ์ด๋ ์ฐ๊ธฐ ์ฑ๋ฅ๊ณผ ํ์ฅ์ฑ์ด ์๋ฒฝํ๊ฒ ๋ค์ด๋ง๋ ๊ฑฐ์ง!
์ฌ๋ก 2: IoT ์ผ์ ๋ฐ์ดํฐ ์์ง
์์ฒ, ์๋ง ๊ฐ์ IoT ์ผ์์์ ์ค์๊ฐ์ผ๋ก ๋ฐ์ดํฐ๊ฐ ์์์ ธ ๋ค์ด์. ์๊ณ์ด ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ์ ์ฅํ๊ณ ์กฐํํ๋ ๋ฐ Cassandra๊ฐ ์ต์ ์ด์ผ. ์๊ฐ ๊ธฐ๋ฐ ํํฐ์ ๋์ผ๋ก ์ค๋๋ ๋ฐ์ดํฐ๋ ์๋์ผ๋ก ์ญ์ (TTL)ํ ์๋ ์์ด!
์ฌ๋ก 3: ๋ฉ์์ง ์์คํ
๋๊ท๋ชจ ์ฑํ ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ ๋ฉ์์ง ์์คํ ์์๋ Cassandra๊ฐ ์์ฃผ ์ฌ์ฉ๋ผ. ์ฌ์ฉ์๋ณ๋ก ๋ฉ์์ง๋ฅผ ๋น ๋ฅด๊ฒ ์ ์ฅํ๊ณ ์กฐํํ ์ ์๊ฑฐ๋ .
CREATE TABLE messages (
user_id uuid,
conversation_id uuid,
message_time timestamp,
message_id uuid,
sender_id uuid,
content text,
PRIMARY KEY ((user_id, conversation_id), message_time, message_id)
) WITH CLUSTERING ORDER BY (message_time DESC, message_id DESC);
โข SimpleStrategy: ๋จ์ผ ๋ฐ์ดํฐ์ผํฐ ํ๊ฒฝ (๊ฐ๋ฐ/ํ ์คํธ์ฉ)
โข NetworkTopologyStrategy: ๋ค์ค ๋ฐ์ดํฐ์ผํฐ ํ๊ฒฝ (ํ๋ก๋์ ๊ถ์ฅ)
// ๋ค์ค ๋ฐ์ดํฐ์ผํฐ ๋ณต์ ์ค์
CREATE KEYSPACE my_app WITH replication = {
'class': 'NetworkTopologyStrategy',
'dc1': 3, // ๋ฐ์ดํฐ์ผํฐ1์ 3๊ฐ ๋ณต์ ๋ณธ
'dc2': 2 // ๋ฐ์ดํฐ์ผํฐ2์ 2๊ฐ ๋ณต์ ๋ณธ
};
2. Compaction ์ ๋ตCassandra๋ ์ฃผ๊ธฐ์ ์ผ๋ก SSTable์ ๋ณํฉํ๋ Compaction ์์ ์ ์ํํด. ์ ๋ต์ ๋ฐ๋ผ ์ฑ๋ฅ์ด ํฌ๊ฒ ๋ฌ๋ผ์ ธ!
โข SizeTieredCompactionStrategy (STCS): ๊ธฐ๋ณธ ์ ๋ต, ์ฐ๊ธฐ ์์ฃผ ์ํฌ๋ก๋
โข LeveledCompactionStrategy (LCS): ์ฝ๊ธฐ ์์ฃผ ์ํฌ๋ก๋, ๊ณต๊ฐ ํจ์จ์
โข TimeWindowCompactionStrategy (TWCS): ์๊ณ์ด ๋ฐ์ดํฐ, TTL ์ฌ์ฉ ์ ์ต์
3. ๋ชจ๋ํฐ๋ง ํฌ์ธํธ
โข Read/Write Latency: ์๋ต ์๊ฐ ์ถ์
โข Pending Tasks: ๋๊ธฐ ์ค์ธ ์์ ์
โข Compaction ์ํ: ๋์คํฌ I/O ์ํฅ
โข Heap Memory ์ฌ์ฉ๋ฅ : GC ์ฑ๋ฅ ์ํฅ
โข Disk Space: SSTable ํฌ๊ธฐ ๋ชจ๋ํฐ๋ง
โ ๋ณต์กํ ์ฟผ๋ฆฌ๊ฐ ํ์ํ ๋
โ ์คํค๋ง๊ฐ ์์ฃผ ๋ณ๊ฒฝ๋ ๋
โ ๋ฌธ์ ๋จ์ ํธ๋์ญ์ ์ด ํ์ํ ๋
โ ์ง๊ณ ํ์ดํ๋ผ์ธ์ผ๋ก ๋ฐ์ดํฐ ๋ถ์
โ ๊ฐ๋ฐ ์๋๊ฐ ์ค์ํ ๋
โ ์ค์๊ท๋ชจ ํ๋ก์ ํธ
โ ์ด๋๊ท๋ชจ ์ฐ๊ธฐ ์์ ์ด ๋ง์ ๋
โ ์๊ณ์ด ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฐ ๋
โ ๋ค์ดํ์์ด ์ ๋ ์ ๋ ๋
โ ์ง๋ฆฌ์ ์ผ๋ก ๋ถ์ฐ๋ ํ๊ฒฝ
โ ์ ํ์ ํ์ฅ์ด ํ์ํ ๋
โ ๋จ์ํ์ง๋ง ๋น ๋ฅธ ์กฐํ
| ๋น๊ต ํญ๋ชฉ | MongoDB | Cassandra |
|---|---|---|
| ๋ฐ์ดํฐ ๋ชจ๋ธ | ๋ฌธ์ ์งํฅ (JSON/BSON) | ์ปฌ๋ผ ํจ๋ฐ๋ฆฌ (Wide Column) |
| ์ฟผ๋ฆฌ ์ธ์ด | MongoDB Query Language (ํ๋ถ) | CQL (SQL-like, ์ ํ์ ) |
| ์คํค๋ง | ์ ์ฐํ ๋์ ์คํค๋ง | ์ ์๋ ์คํค๋ง (๋ณ๊ฒฝ ๊ฐ๋ฅ) |
| ์ฐ๊ธฐ ์ฑ๋ฅ | ๋น ๋ฆ | ๋งค์ฐ ๋น ๋ฆ (์ต์ ํ) |
| ์ฝ๊ธฐ ์ฑ๋ฅ | ๋น ๋ฆ (์ธ๋ฑ์ค ํ์ฉ) | ๋น ๋ฆ (ํํฐ์ ํค ์กฐํ) |
| ํ์ฅ์ฑ | ์ค๋ฉ (์ํ ํ์ฅ ๊ฐ๋ฅ) | ์ ํ์ ํ์ฅ (๋งค์ฐ ์ฐ์) |
| ์ผ๊ด์ฑ | ๊ฐํ ์ผ๊ด์ฑ (๊ธฐ๋ณธ) | ์ต์ข ์ผ๊ด์ฑ (ํ๋ ๊ฐ๋ฅ) |
| ํธ๋์ญ์ | ACID ํธ๋์ญ์ ์ง์ | ๊ฒฝ๋ ํธ๋์ญ์ (์ ํ์ ) |
| ๋ณต์ | ๋ ํ๋ฆฌ์นด ์ (Primary-Secondary) | ํผ์ด-ํฌ-ํผ์ด (๋ง์คํฐ๋ฆฌ์ค) |
| ์กฐ์ธ | $lookup์ผ๋ก ๊ฐ๋ฅ (์ ํ์ ) | ๋ถ๊ฐ๋ฅ (๋น์ ๊ทํ ํ์) |
| ์ง๊ณ | ๊ฐ๋ ฅํ ์ง๊ณ ํ์ดํ๋ผ์ธ | ์ ํ์ (์ ํ๋ฆฌ์ผ์ด์ ๋ ๋ฒจ) |
| ํ์ต ๊ณก์ | ๋น๊ต์ ์ฌ์ | ๊ฐํ๋ฆ (๋ฐ์ดํฐ ๋ชจ๋ธ๋ง ๋ณต์ก) |
| ์ด์ ๋ณต์ก๋ | ์ค๊ฐ | ๋์ |
| ์ปค๋ฎค๋ํฐ | ๋งค์ฐ ํ๋ฐ | ํ๋ฐ |
| ์ฌ์ฉ ๊ธฐ์ | Uber, eBay, Adobe | Netflix, Apple, Instagram |
์ถ์ฒ: MongoDB
์ด์ : ์ํ๋ง๋ค ๋ค๋ฅธ ์์ฑ, ๋ณต์กํ ์ฟผ๋ฆฌ ํ์, ํธ๋์ญ์ ์ค์, ์ง๊ณ ๋ถ์ ํ์
์ถ์ฒ: Cassandra
์ด์ : ๋๋ ์ฐ๊ธฐ ์ต์ ํ, ์๊ณ์ด ๋ฐ์ดํฐ ์ฒ๋ฆฌ, TTL ์๋ ์ญ์ , ์ ํ ํ์ฅ
์ถ์ฒ: MongoDB (๋๋ ํ์ด๋ธ๋ฆฌ๋)
์ด์ : ์ ์ฐํ ๋ฐ์ดํฐ ๊ตฌ์กฐ, ๋ณต์กํ ์ฟผ๋ฆฌ, ์ค์๊ฐ ํผ๋ ์์ฑ. ๋จ, ํ์๋ผ์ธ์ Cassandra ํ์ฉ ๊ฐ๋ฅ
์ถ์ฒ: Cassandra
์ด์ : ์ด๊ณ ์ ์ฐ๊ธฐ, ์๊ฐ ๊ธฐ๋ฐ ํํฐ์ ๋, ์์ถ ํจ์จ, ๋ค์ดํ์ ์๋ ์ด์
ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ต์ 3๊ฐ ๋ ธ๋๋ก ๋ ํ๋ฆฌ์นด ์ ์ ๊ตฌ์ฑํด์ผ ํด. ํ์ ๊ฐ์ ๋ ธ๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ ๊ฑฐ(Election) ๊ณผ์ ์์ ์ ๋ฆฌํด!
// mongod.conf ์ค์ (๊ฐ ๋
ธ๋)
replication:
replSetName: "myReplicaSet"
net:
bindIp: 0.0.0.0
port: 27017
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true
// ๋ ํ๋ฆฌ์นด ์
์ด๊ธฐํ (Primary ๋
ธ๋์์)
rs.initiate({
_id: "myReplicaSet",
members: [
{ _id: 0, host: "mongodb1.example.com:27017" },
{ _id: 1, host: "mongodb2.example.com:27017" },
{ _id: 2, host: "mongodb3.example.com:27017" }
]
})
// ์ํ ํ์ธ
rs.status()
2๋จ๊ณ: ์ค๋ฉ ํด๋ฌ์คํฐ ๊ตฌ์ฑ๋๊ท๋ชจ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฌ๋ค๋ฉด ์ค๋ฉ์ด ํ์์ผ!
// Config Server ์์ (3๊ฐ ๊ถ์ฅ)
mongod --configsvr --replSet configReplSet --port 27019
// Mongos (์ฟผ๋ฆฌ ๋ผ์ฐํฐ) ์์
mongos --configdb configReplSet/config1:27019,config2:27019,config3:27019
// Shard ์ถ๊ฐ
sh.addShard("shard1/mongodb4:27017,mongodb5:27017,mongodb6:27017")
sh.addShard("shard2/mongodb7:27017,mongodb8:27017,mongodb9:27017")
// ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๋ฉ ํ์ฑํ
sh.enableSharding("myDatabase")
// ์ปฌ๋ ์
์ค๋ฉ (ํด์ ๊ธฐ๋ฐ)
sh.shardCollection("myDatabase.users", { userId: "hashed" })
// ๋๋ ๋ฒ์ ๊ธฐ๋ฐ ์ค๋ฉ
sh.shardCollection("myDatabase.orders", { customerId: 1, orderDate: 1 })
3๋จ๊ณ: ๋ณด์ ์ค์
// ์ธ์ฆ ํ์ฑํ
security:
authorization: enabled
keyFile: /path/to/keyfile
// ๊ด๋ฆฌ์ ๊ณ์ ์์ฑ
use admin
db.createUser({
user: "admin",
pwd: "securePassword123!",
roles: [ { role: "root", db: "admin" } ]
})
// ์ ํ๋ฆฌ์ผ์ด์
์ฌ์ฉ์ ์์ฑ
use myDatabase
db.createUser({
user: "appUser",
pwd: "appPassword456!",
roles: [
{ role: "readWrite", db: "myDatabase" },
{ role: "read", db: "analytics" }
]
})
Cassandra๋ ๋ชจ๋ ๋ ธ๋๊ฐ ๋๋ฑํ๋ฏ๋ก ๊ฐ ๋ ธ๋์ ๋์ผํ ์ค์ ์ ์ ์ฉํด.
# cassandra.yaml ์ฃผ์ ์ค์
cluster_name: 'MyCluster'
# Seed ๋
ธ๋ ์ง์ (ํด๋ฌ์คํฐ ๋ฐ๊ฒฌ์ฉ)
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "192.168.1.10,192.168.1.11,192.168.1.12"
# ๋
ธ๋ ๊ฐ ํต์
listen_address: 192.168.1.10
rpc_address: 0.0.0.0
# ๋ฐ์ดํฐ ๋๋ ํ ๋ฆฌ
data_file_directories:
- /var/lib/cassandra/data
commitlog_directory: /var/lib/cassandra/commitlog
# ์ค๋์น ์ค์ (๋คํธ์ํฌ ํ ํด๋ก์ง)
endpoint_snitch: GossipingPropertyFileSnitch
2๋จ๊ณ: ํด๋ฌ์คํฐ ๊ตฌ์ฑ
# ์ฒซ ๋ฒ์งธ ๋
ธ๋ ์์
cassandra -R
# ๋ค๋ฅธ ๋
ธ๋๋ค ์์ฐจ์ ์ผ๋ก ์์
# ๊ฐ ๋
ธ๋๊ฐ ์๋์ผ๋ก ํด๋ฌ์คํฐ์ ์กฐ์ธ๋จ
# ํด๋ฌ์คํฐ ์ํ ํ์ธ
nodetool status
# ์ถ๋ ฅ ์์:
# Datacenter: dc1
# Status=Up/Down
# State=Normal/Leaving/Joining/Moving
# -- Address Load Tokens Owns Host ID
# UN 192.168.1.10 1.2 TB 256 33.3% abc123...
# UN 192.168.1.11 1.1 TB 256 33.4% def456...
# UN 192.168.1.12 1.3 TB 256 33.3% ghi789...
3๋จ๊ณ: Keyspace ๋ฐ ํ
์ด๋ธ ์์ฑ
// CQL Shell ์ ์
cqlsh
// Keyspace ์์ฑ
CREATE KEYSPACE my_app WITH replication = {
'class': 'NetworkTopologyStrategy',
'dc1': 3
} AND durable_writes = true;
// ์ฌ์ฉ์ ํ
์ด๋ธ
CREATE TABLE my_app.users (
user_id uuid PRIMARY KEY,
username text,
email text,
created_at timestamp,
last_login timestamp
);
// ์ฌ์ฉ์ ํ๋ ๋ก๊ทธ (์๊ณ์ด)
CREATE TABLE my_app.user_activity (
user_id uuid,
activity_date date,
activity_time timestamp,
activity_type text,
details map<text, text>,
PRIMARY KEY ((user_id, activity_date), activity_time)
) WITH CLUSTERING ORDER BY (activity_time DESC)
AND compaction = {
'class': 'TimeWindowCompactionStrategy',
'compaction_window_unit': 'DAYS',
'compaction_window_size': 1
}
AND default_time_to_live = 2592000; // 30์ผ ํ ์๋ ์ญ์
4๋จ๊ณ: ๋ณด์ ์ค์
# cassandra.yaml์ ์ธ์ฆ ํ์ฑํ
authenticator: PasswordAuthenticator
authorizer: CassandraAuthorizer
# ๊ธฐ๋ณธ cassandra ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ ํ ์ ๊ด๋ฆฌ์ ์์ฑ
CREATE ROLE admin WITH PASSWORD = 'securePassword123!'
AND SUPERUSER = true
AND LOGIN = true;
# ์ ํ๋ฆฌ์ผ์ด์
์ฌ์ฉ์ ์์ฑ
CREATE ROLE app_user WITH PASSWORD = 'appPassword456!'
AND LOGIN = true;
GRANT ALL PERMISSIONS ON KEYSPACE my_app TO app_user;
// npm install mongodb
const { MongoClient } = require('mongodb');
const uri = "mongodb://username:password@mongodb1:27017,mongodb2:27017,mongodb3:27017/myDatabase?replicaSet=myReplicaSet";
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
maxPoolSize: 50,
wtimeoutMS: 2500
});
async function connectDB() {
try {
await client.connect();
console.log("MongoDB ์ฐ๊ฒฐ ์ฑ๊ณต!");
return client.db("myDatabase");
} catch (error) {
console.error("์ฐ๊ฒฐ ์คํจ:", error);
throw error;
}
}
CRUD ์์
:
const db = await connectDB();
const usersCollection = db.collection('users');
// Create - ์ฌ์ฉ์ ์์ฑ
async function createUser(userData) {
const result = await usersCollection.insertOne({
username: userData.username,
email: userData.email,
profile: {
firstName: userData.firstName,
lastName: userData.lastName,
age: userData.age
},
createdAt: new Date(),
tags: userData.tags || []
});
return result.insertedId;
}
// Read - ์ฌ์ฉ์ ์กฐํ
async function findUserByEmail(email) {
return await usersCollection.findOne({ email: email });
}
// Read - ๋ณต์กํ ์ฟผ๋ฆฌ
async function findActiveUsers(minAge) {
return await usersCollection.find({
'profile.age': { $gte: minAge },
status: 'active',
lastLogin: { $gte: new Date(Date.now() - 30*24*60*60*1000) }
})
.sort({ lastLogin: -1 })
.limit(100)
.toArray();
}
// Update - ์ฌ์ฉ์ ์ ๋ณด ์์
async function updateUserProfile(userId, updates) {
return await usersCollection.updateOne(
{ _id: userId },
{
$set: {
'profile.firstName': updates.firstName,
'profile.lastName': updates.lastName,
updatedAt: new Date()
},
$push: { tags: updates.newTag }
}
);
}
// Delete - ์ฌ์ฉ์ ์ญ์
async function deleteUser(userId) {
return await usersCollection.deleteOne({ _id: userId });
}
// ์ง๊ณ ํ์ดํ๋ผ์ธ - ํต๊ณ ๋ถ์
async function getUserStatsByAge() {
return await usersCollection.aggregate([
{
$match: { status: 'active' }
},
{
$group: {
_id: {
$floor: { $divide: ['$profile.age', 10] }
},
count: { $sum: 1 },
avgAge: { $avg: '$profile.age' }
}
},
{
$project: {
ageGroup: { $multiply: ['$_id', 10] },
count: 1,
avgAge: { $round: ['$avgAge', 1] }
}
},
{
$sort: { ageGroup: 1 }
}
]).toArray();
}
ํธ๋์ญ์
์ฌ์ฉ:
// MongoDB 4.0+ ์์ ๋ค์ค ๋ฌธ์ ํธ๋์ญ์
์ง์
async function transferPoints(fromUserId, toUserId, points) {
const session = client.startSession();
try {
await session.withTransaction(async () => {
const usersCollection = db.collection('users');
// ํฌ์ธํธ ์ฐจ๊ฐ
await usersCollection.updateOne(
{ _id: fromUserId },
{ $inc: { points: -points } },
{ session }
);
// ํฌ์ธํธ ์ถ๊ฐ
await usersCollection.updateOne(
{ _id: toUserId },
{ $inc: { points: points } },
{ session }
);
// ๊ฑฐ๋ ๊ธฐ๋ก
const transactionsCollection = db.collection('transactions');
await transactionsCollection.insertOne({
fromUser: fromUserId,
toUser: toUserId,
amount: points,
timestamp: new Date()
}, { session });
});
console.log("ํธ๋์ญ์
์ฑ๊ณต!");
} catch (error) {
console.error("ํธ๋์ญ์
์คํจ:", error);
throw error;
} finally {
await session.endSession();
}
}
// npm install cassandra-driver
const cassandra = require('cassandra-driver');
const client = new cassandra.Client({
contactPoints: ['192.168.1.10', '192.168.1.11', '192.168.1.12'],
localDataCenter: 'dc1',
keyspace: 'my_app',
credentials: {
username: 'app_user',
password: 'appPassword456!'
},
pooling: {
coreConnectionsPerHost: {
[cassandra.types.distance.local]: 2,
[cassandra.types.distance.remote]: 1
}
}
});
async function connectCassandra() {
try {
await client.connect();
console.log("Cassandra ์ฐ๊ฒฐ ์ฑ๊ณต!");
} catch (error) {
console.error("์ฐ๊ฒฐ ์คํจ:", error);
throw error;
}
}
CRUD ์์
:
// Create - ์ฌ์ฉ์ ์์ฑ
async function createUser(userData) {
const query = `
INSERT INTO users (user_id, username, email, created_at)
VALUES (?, ?, ?, ?)
`;
const params = [
cassandra.types.Uuid.random(),
userData.username,
userData.email,
new Date()
];
await client.execute(query, params, { prepare: true });
}
// Read - ์ฌ์ฉ์ ์กฐํ
async function getUserById(userId) {
const query = 'SELECT * FROM users WHERE user_id = ?';
const result = await client.execute(query, [userId], { prepare: true });
return result.rows[0];
}
// ์๊ณ์ด ๋ฐ์ดํฐ ์ฝ์
async function logUserActivity(userId, activityType, details) {
const query = `
INSERT INTO user_activity
(user_id, activity_date, activity_time, activity_type, details)
VALUES (?, ?, ?, ?, ?)
`;
const now = new Date();
const params = [
userId,
cassandra.types.LocalDate.fromDate(now),
now,
activityType,
details
];
await client.execute(query, params, { prepare: true });
}
// ์๊ณ์ด ๋ฐ์ดํฐ ์กฐํ
async function getUserActivityToday(userId) {
const query = `
SELECT * FROM user_activity
WHERE user_id = ? AND activity_date = ?
ORDER BY activity_time DESC
LIMIT 100
`;
const today = cassandra.types.LocalDate.fromDate(new Date());
const result = await client.execute(
query,
[userId, today],
{ prepare: true }
);
return result.rows;
}
// ๋ฐฐ์น ์์
async function batchInsertActivities(activities) {
const queries = activities.map(activity => ({
query: `
INSERT INTO user_activity
(user_id, activity_date, activity_time, activity_type, details)
VALUES (?, ?, ?, ?, ?)
`,
params: [
activity.userId,
cassandra.types.LocalDate.fromDate(activity.timestamp),
activity.timestamp,
activity.type,
activity.details
]
}));
await client.batch(queries, { prepare: true });
}
// ์ผ๊ด์ฑ ๋ ๋ฒจ ์ง์
async function criticalRead(userId) {
const query = 'SELECT * FROM users WHERE user_id = ?';
const result = await client.execute(
query,
[userId],
{
prepare: true,
consistency: cassandra.types.consistencies.quorum
}
);
return result.rows[0];
}
โข Connection Pooling: ์ฐ๊ฒฐ ํ์ ์ ์ ํ ์ค์ ํด์ ์ฌ์ฌ์ฉํด์ผ ํด.
โข ๋ฐฐ์น ์์ : ์ฌ๋ฌ ๊ฐ์ ์ฝ์ /์์ ์์ ์ ๋ฐฐ์น๋ก ๋ฌถ์ด์ ์ฒ๋ฆฌํ๋ฉด ํจ์จ์ ์ด์ผ.
โข ๋น๋๊ธฐ ์ฒ๋ฆฌ: async/await๋ฅผ ํ์ฉํด์ ๋ ผ๋ธ๋กํน ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํด์ผ ํด.
โข Operations per Second: ์ด๋น ์์ ์ (์ฝ๊ธฐ/์ฐ๊ธฐ)
โข Queue Length: ๋๊ธฐ ์ค์ธ ์์ ์
2. ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋
โข Resident Memory: ์ค์ ์ฌ์ฉ ์ค์ธ ๋ฉ๋ชจ๋ฆฌ
โข Virtual Memory: ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋
โข Page Faults: ํ์ด์ง ํดํธ ๋ฐ์ ํ์
3. ๋ณต์ ์ง์ฐ
โข Replication Lag: Primary์ Secondary ๊ฐ ์ง์ฐ ์๊ฐ
โข Oplog Window: Oplog๊ฐ ์ปค๋ฒํ๋ ์๊ฐ ๋ฒ์
// ์๋ฒ ์ํ ํ์ธ
db.serverStatus()
// ํ์ฌ ์์
ํ์ธ
db.currentOp()
// ๋๋ฆฐ ์ฟผ๋ฆฌ ํ๋กํ์ผ๋ง
db.setProfilingLevel(1, { slowms: 100 }) // 100ms ์ด์ ์ฟผ๋ฆฌ ๊ธฐ๋ก
db.system.profile.find().sort({ ts: -1 }).limit(10)
// ์ธ๋ฑ์ค ์ฌ์ฉ ํต๊ณ
db.collection.aggregate([{ $indexStats: {} }])
// ๋ฐ์ดํฐ๋ฒ ์ด์ค ํต๊ณ
db.stats()
// ์ปฌ๋ ์
ํต๊ณ
db.collection.stats()
์ฑ๋ฅ ํ๋ ์ฒดํฌ๋ฆฌ์คํธ:โ ์ธ๋ฑ์ค ์ต์ ํ
โข ์์ฃผ ์กฐํ๋๋ ํ๋์ ์ธ๋ฑ์ค ์์ฑ
โข ๋ณตํฉ ์ธ๋ฑ์ค ์์ ์ต์ ํ (์ ํ๋๊ฐ ๋์ ํ๋๋ฅผ ์์)
โข ์ฌ์ฉํ์ง ์๋ ์ธ๋ฑ์ค ์ ๊ฑฐ
โข ์ปค๋ฒ๋ง ์ธ๋ฑ์ค ํ์ฉ (์ฟผ๋ฆฌ๊ฐ ์ธ๋ฑ์ค๋ง์ผ๋ก ์ฒ๋ฆฌ)
โ ์ฟผ๋ฆฌ ์ต์ ํ
โข explain()๋ก ์คํ ๊ณํ ๋ถ์
โข ํ์ํ ํ๋๋ง ํ๋ก์ ์
โข ์ ๊ทํํ์ ์ฌ์ฉ ์ต์ํ
โข $or ๋์ $in ์ฌ์ฉ ๊ณ ๋ ค
โ ํ๋์จ์ด ๋ฐ ์ค์
โข ์ถฉ๋ถํ RAM ํ๋ณด (Working Set์ด ๋ฉ๋ชจ๋ฆฌ์ ๋ค์ด๊ฐ๋๋ก)
โข SSD ์ฌ์ฉ ๊ถ์ฅ
โข WiredTiger ์บ์ ํฌ๊ธฐ ์กฐ์
โข ์ ์ ํ Read/Write Concern ์ค์
โข Load: ๊ฐ ๋ ธ๋์ ๋ฐ์ดํฐ ํฌ๊ธฐ
โข Ownership: ํ ํฐ ๋ฒ์ ์์ ๋น์จ
2. ์ฝ๊ธฐ/์ฐ๊ธฐ ์ฑ๋ฅ
โข Read Latency: ์ฝ๊ธฐ ์ง์ฐ ์๊ฐ
โข Write Latency: ์ฐ๊ธฐ ์ง์ฐ ์๊ฐ
โข Throughput: ์ด๋น ์ฒ๋ฆฌ๋
3. Compaction ๋ฐ GC
โข Pending Compactions: ๋๊ธฐ ์ค์ธ Compaction
โข GC Pause Time: ๊ฐ๋น์ง ์ปฌ๋ ์ ์ผ์์ ์ง ์๊ฐ
โข Heap Memory Usage: ํ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ
# ํด๋ฌ์คํฐ ์ํ
nodetool status
# ๋
ธ๋ ์ ๋ณด
nodetool info
# ์ฑ๋ฅ ํต๊ณ
nodetool tablestats my_app.users
# Compaction ์ํ
nodetool compactionstats
# ํ๋ก์ ํ์คํ ๊ทธ๋จ (์ง์ฐ ์๊ฐ ๋ถํฌ)
nodetool proxyhistograms
# ์ค๋ ๋ํ ์ํ
nodetool tpstats
# GC ํต๊ณ
nodetool gcstats
# ํน์ ํ
์ด๋ธ ํ๋ฌ์
nodetool flush my_app users
์ฑ๋ฅ ํ๋ ์ฒดํฌ๋ฆฌ์คํธ:โ ๋ฐ์ดํฐ ๋ชจ๋ธ ์ต์ ํ
โข ์ฟผ๋ฆฌ ํจํด์ ๋ง๋ ํํฐ์ ํค ์ค๊ณ
โข ํํฐ์ ํฌ๊ธฐ 100MB ์ดํ ์ ์ง
โข ์ ์ ํ Clustering Key๋ก ์ ๋ ฌ ์ต์ ํ
โข ํ์์ ๋น์ ๊ทํ๋ก ์กฐ์ธ ํํผ
โ Compaction ์ ๋ต
โข ์ํฌ๋ก๋์ ๋ง๋ Compaction ์ ๋ต ์ ํ
โข ์๊ณ์ด ๋ฐ์ดํฐ๋ TWCS ์ฌ์ฉ
โข Compaction ์ค์ผ์ค ์กฐ์
โ ํ๋์จ์ด ๋ฐ JVM ์ค์
โข ์ถฉ๋ถํ RAM (์ต์ 8GB, ๊ถ์ฅ 32GB+)
โข SSD ์ฌ์ฉ ํ์
โข JVM ํ ํฌ๊ธฐ ์กฐ์ (๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ์ 1/4 ~ 1/2)
โข G1GC ์ฌ์ฉ ๊ถ์ฅ
โ ์ผ๊ด์ฑ ๋ฐ ๋ณต์
โข ์ ์ ํ ๋ณต์ ๊ณ์ ์ค์ (์ผ๋ฐ์ ์ผ๋ก 3)
โข ์ผ๊ด์ฑ ๋ ๋ฒจ ์ต์ ํ
โข Hinted Handoff ํ์ฉ
ํด๊ฒฐ: ๋ฐ์ดํฐ ์ ๊ทผ ํจํด์ ๋ถ์ํด์ ์ ์ ํ ํผํฉ ์ฌ์ฉ. 1:N ๊ด๊ณ์์ N์ด ์์ผ๋ฉด ์๋ฒ ๋ฉ, ํฌ๋ฉด ์ฐธ์กฐ!
Cassandra: RDBMS ์ฌ๊ณ ๋ฐฉ์์ผ๋ก ์ ๊ทํ๋ ํ ์ด๋ธ ์ค๊ณ
ํด๊ฒฐ: ์ฟผ๋ฆฌ ํจํด์ ๋จผ์ ์ ์ํ๊ณ , ๊ฐ ์ฟผ๋ฆฌ๋ง๋ค ์ต์ ํ๋ ํ ์ด๋ธ ์์ฑ. ์ค๋ณต ์ ์ฅ์ ๋๋ ค์ํ์ง ๋ง ๊ฒ!
๋ฌธ์ : ๋ชจ๋ ํ๋์ ์ธ๋ฑ์ค๋ฅผ ๋ง๋ค๊ฑฐ๋, ๋ฐ๋๋ก ์ธ๋ฑ์ค๋ฅผ ์ ํ ๋ง๋ค์ง ์์
ํด๊ฒฐ: ์ค์ ์ฟผ๋ฆฌ ํจํด์ ๋ถ์ํด์ ํ์ํ ์ธ๋ฑ์ค๋ง ์์ฑ. ์ฐ๊ธฐ ์ฑ๋ฅ๊ณผ ์ฝ๊ธฐ ์ฑ๋ฅ์ ํธ๋ ์ด๋์คํ๋ฅผ ๊ณ ๋ ค!
// ์ข์ ์: ์์ฃผ ์กฐํ๋๋ ํ๋ ์กฐํฉ
db.orders.createIndex({ customerId: 1, orderDate: -1 })
// ๋์ ์: ๊ฑฐ์ ์ฌ์ฉํ์ง ์๋ ํ๋
db.orders.createIndex({ internalNotes: 1 }) // ๋ถํ์!
๋ฌธ์ : ํซ์คํ ๋ฐ์! ๋ชจ๋ ์ฐ๊ธฐ๊ฐ ํ๋์ ์ค๋๋ก ์ง์ค
ํด๊ฒฐ: ํด์ ์ค๋ฉ ์ฌ์ฉํ๊ฑฐ๋, ๋ณตํฉ ์ค๋ ํค๋ก ๋ถ์ฐ
Cassandra: ์นด๋๋๋ฆฌํฐ๊ฐ ๋ฎ์ ํ๋๋ฅผ ํํฐ์ ํค๋ก ์ฌ์ฉ (์: status, type)
๋ฌธ์ : ์์์ ๊ฑฐ๋ํ ํํฐ์ ์์ฑ
ํด๊ฒฐ: ์นด๋๋๋ฆฌํฐ๊ฐ ๋์ ํ๋ ์ฌ์ฉ (์: user_id, sensor_id)
โข mongodump/mongorestore ์ฌ์ฉ (์๊ท๋ชจ)
โข ํ์ผ์์คํ ์ค๋ ์ท ํ์ฉ (๋๊ท๋ชจ)
โข Oplog ๊ธฐ๋ฐ Point-in-Time ๋ณต๊ตฌ
โข ์ ๊ธฐ์ ์ธ ๋ฐฑ์ ํ ์คํธ ํ์!
Cassandra:
โข nodetool snapshot์ผ๋ก ์ค๋ ์ท ์์ฑ
โข ์ฆ๋ถ ๋ฐฑ์ ํ์ฉ
โข ๋ค์ค ๋ฐ์ดํฐ์ผํฐ ๋ณต์ ๋ก ์ฌํด ๋ณต๊ตฌ
โข ์ฃผ๊ธฐ์ ์ธ ๋ณต๊ตฌ ํ๋ จ
โข TLS/SSL ์ํธํ ํต์
โข ๋คํธ์ํฌ ๊ฒฉ๋ฆฌ (VPC, ๋ฐฉํ๋ฒฝ)
โข ์ ๊ธฐ์ ์ธ ๋ณด์ ํจ์น
โข ๊ฐ์ฌ ๋ก๊ทธ ํ์ฑํ
โข ์ต์ ๊ถํ ์์น ์ ์ฉ
โข ๋น๋ฐ๋ฒํธ ์ ์ฑ ๊ฐํ
โข ํผํฌ ์๊ฐ๋ ํธ๋ํฝ ๋ถ์
โข ์ฌ์ ์ฉ๋ ํ๋ณด (์ต์ 30%)
โข ์๋ ์ค์ผ์ผ๋ง ์ค์
โข ์ ๊ธฐ์ ์ธ ์ฉ๋ ๋ฆฌ๋ทฐ
โข ์์นด์ด๋น ์ ๋ต ์๋ฆฝ
MongoDB Atlas๋ AWS, Azure, GCP๋ฅผ ๋๋๋ค๋ฉฐ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ ์ ์์ด. ํด๋ผ์ฐ๋ ๋ฒค๋ ์ข ์์ ํผํ๊ณ ์ต์ ์ ํ๊ฒฝ์ ์ ํํ ์ ์์ง!
2. ์๋ฒ๋ฆฌ์ค ์ธ์คํด์ค
์ฌ์ฉํ ๋งํผ๋ง ๋น์ฉ์ ์ง๋ถํ๋ ์๋ฒ๋ฆฌ์ค MongoDB๊ฐ ๋ฑ์ฅํ์ด. ํธ๋ํฝ ๋ณ๋์ด ํฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๋ฒฝํด!
3. ์๊ณ์ด ์ปฌ๋ ์
MongoDB 5.0๋ถํฐ ์๊ณ์ด ๋ฐ์ดํฐ๋ฅผ ์ํ ์ต์ ํ๋ ์ปฌ๋ ์ ํ์ ์ด ์ถ๊ฐ๋์ด. IoT, ๋ชจ๋ํฐ๋ง ๋ฐ์ดํฐ ์ฒ๋ฆฌ๊ฐ ํจ์ฌ ํจ์จ์ ์ด์ผ!
4. ๋ค์ดํฐ๋ธ ๋ถ์ ๊ธฐ๋ฅ
MongoDB Atlas Data Lake๋ฅผ ํตํด S3์ ๋ฐ์ดํฐ๋ ์ง์ ์ฟผ๋ฆฌํ ์ ์์ด. ๋ณ๋์ ETL ์์ด ๋ถ์์ด ๊ฐ๋ฅํด์ก์ง!
โข Virtual Tables๋ก ์์คํ ์ ๋ณด ์กฐํ ๊ฐ์
โข Audit Logging ๊ธฐ๋ณธ ์ง์
โข Zero Copy Streaming์ผ๋ก ๋ ธ๋ ์ถ๊ฐ ์๋ ํฅ์
โข Full Query Logging์ผ๋ก ๋๋ฒ๊น ๊ฐํ
2. Kubernetes ๋ค์ดํฐ๋ธ
K8ssandra ํ๋ก์ ํธ๋ฅผ ํตํด Kubernetes์์ Cassandra๋ฅผ ์ฝ๊ฒ ์ด์ํ ์ ์์ด. ์ปจํ ์ด๋ ํ๊ฒฝ์ ์ต์ ํ!
3. DataStax Astra
์์ ๊ด๋ฆฌํ Cassandra ์๋น์ค๋ก, ์ด์ ๋ถ๋ด ์์ด Cassandra์ ๊ฐ๋ ฅํจ์ ํ์ฉํ ์ ์์ด!
4. ๋จธ์ ๋ฌ๋ ํตํฉ
Cassandra์ ์ ์ฅ๋ ๋๊ท๋ชจ ๋ฐ์ดํฐ๋ฅผ Spark์ ์ฐ๋ํด์ ์ค์๊ฐ ML ํ์ดํ๋ผ์ธ ๊ตฌ์ถ์ด ๊ฐ๋ฅํด!
2. AI/ML ํตํฉ
๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ฒด์ AI ๊ธฐ๋ฅ์ด ํตํฉ๋๊ณ ์์ด. ์๋ ์ธ๋ฑ์ฑ, ์ฟผ๋ฆฌ ์ต์ ํ, ์ด์ ํ์ง ๋ฑ!
3. ์ฃ์ง ์ปดํจํ ์ง์
IoT ๊ธฐ๊ธฐ์์ ์ง์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๊ณ , ํ์ํ ๊ฒ๋ง ์ค์์ผ๋ก ์ ์ก. ์ง์ฐ ์๊ฐ ์ต์ํ!
4. ํ๋ผ์ด๋ฒ์ ๊ฐํ
GDPR, CCPA ๋ฑ ๊ฐ์ธ์ ๋ณด ๋ณดํธ ๊ท์ ์ ๋์ํ๋ ๊ธฐ๋ฅ๋ค์ด ๊ฐํ๋๊ณ ์์ด. ๋ฐ์ดํฐ ์ํธํ, ์ ๊ทผ ์ ์ด, ๊ฐ์ฌ ๋ก๊ทธ ๋ฑ!
ํต์ฌ์ ์ ๋ฆฌํ์๋ฉด:
โข ํ๋ถํ ์ฟผ๋ฆฌ ๊ธฐ๋ฅ
โข ๋ฌธ์ ์งํฅ ๋ฐ์ดํฐ ๋ชจ๋ธ
โข ์ค์๊ท๋ชจ๋ถํฐ ๋๊ท๋ชจ๊น์ง ํ์ฅ ๊ฐ๋ฅ
๐ Cassandra๋:
โข ์ด๋๊ท๋ชจ ์ฐ๊ธฐ ์ฑ๋ฅ
โข ์ ํ์ ํ์ฅ์ฑ
โข ๋ง์คํฐ๋ฆฌ์ค ์ํคํ ์ฒ๋ก ๊ณ ๊ฐ์ฉ์ฑ
โข ์๊ณ์ด ๋ฐ์ดํฐ์ ์ต์ ํ
NoSQL ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ๊ณ์ ์งํํ๊ณ ์์ด. ์๋ก์ด ๊ธฐ๋ฅ๋ค์ด ์ถ๊ฐ๋๊ณ , ์ฑ๋ฅ์ด ๊ฐ์ ๋๊ณ , ์ฌ์ฉํ๊ธฐ ์ฌ์์ง๊ณ ์์ง. ์ง์์ ์ผ๋ก ํ์ตํ๊ณ ์คํํ๋ฉด์ ์ต์ ์ ์๋ฃจ์ ์ ์ฐพ์๊ฐ๋ ๊ฒ ์ค์ํด!
โข ๋ฐฑ์ ๊ณผ ๋ณต๊ตฌ ์ ๋ต์ ๋จผ์ ์๋ฆฝํ๊ณ ์์ํด!
โข ๋ชจ๋ํฐ๋ง๊ณผ ์๋ฆผ ์์คํ ์ ๊ผญ ๊ตฌ์ถํด!
โข ๋ฌธ์ํ๋ฅผ ๊ฒ์๋ฆฌํ์ง ๋ง ๊ฒ!
โข ์ปค๋ฎค๋ํฐ๋ฅผ ์ ๊ทน ํ์ฉํด!
์ด ๊ธ์ด NoSQL ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ดํดํ๊ณ ํ์ฉํ๋ ๋ฐ ๋์์ด ๋์๊ธฐ๋ฅผ ๋ฐ๋ผ! ๊ถ๊ธํ ์ ์ด ์๋ค๋ฉด ์ธ์ ๋ ์ง ์ปค๋ฎค๋ํฐ์ ์ง๋ฌธํ๊ณ , ๊ณ์ ํ์ตํ๋ฉด์ ์ฑ์ฅํด๋๊ฐ์. ํ์ดํ ! ๐โจ
Happy Coding! ๐
๋๊ท๋ชจ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ์ธ๊ณ๋ก ์ฌ๋ฌ๋ถ์ ์ด๋ํฉ๋๋ค!
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.