20 β NoSQL ma'lumot modellashtirish¶
β¬ οΈ Oldingi: 19 β Multi-tenancy dizayni va RLS Β· π README Β· Keyingi: 21 β Analitik dizayn va ma'lumotlar ombori β‘οΈ
Bu bobda: shu paytgacha biz relyatsion (PostgreSQL) dunyoda yashadik β normalizatsiya, FK, JOIN. Endi NoSQL ga chiqamiz: document (MongoDB), key-value (Redis), wide-column (Cassandra), graph (Neo4j) β har biri qachon to'g'ri tanlov. Eng muhim aqliy siljish β relyatsion fikrlashdan voz kechib, kirish naqshi (access pattern) bo'yicha dizayn qilish: avval qanday so'rashingni bil, keyin modellashtir. Document modellashtirishning yuragi β
embedvareferenceqarori, denormalizatsiya, 16MB chegarasi, massiv o'sishi muammosi. Redis kalit nomlash va TTL ni, CAP/BASE ni qoplaymiz. Har joyda relyatsion ekvivalent bilan taqqoslaymiz. Redis va PostgreSQL JSONB misollari haqiqatan ishga tushirilgan; MongoDB sintaksisi (shell yo'qligi sababli) illustrativ deb belgilangan.
0. Bu bob qayerda turadi¶
SQL kitobi va shu kitobning hamma boblari relyatsion modelni o'rgatdi. Bu bob butunlay boshqa dunyoga β NoSQL ga β kirib chiqadi. Maqsad sizni MongoDB yoki Cassandra mutaxassisi qilish emas (bu alohida kitoblar); maqsad β dizayn nuqtai nazaridan:
- NoSQL turlarini ajrata olish va qaysi muammoga qaysi tur mosligini bilish;
- relyatsion odatdan voz kechib, NoSQL uslubida β kirish naqshi bo'yicha β modellashtira olish;
- "Bizga NoSQL kerakmi?" degan savolga halol javob bera olish (ko'pincha javob β "yo'q, PostgreSQL yetadi").
NoSQL nima emas: "No SQL" emas, "Not only SQL". U relyatsion bazani o'ldirmaydi β uni to'ldiradi. Ko'p hollarda eng to'g'ri arxitektura β PostgreSQL ni asos qilib, kerakli joyga NoSQL qo'shish (
polyglot persistence, pastda).
1. NoSQL nega paydo bo'ldi va nima vada qiladi¶
Relyatsion model 1970-lardan beri hukmron va u ajoyib: yaxlitlik, JOIN, tranzaksiya, deklarativ so'rov. Lekin 2000-lar oxirida ikkita bosim paydo bo'ldi:
- Masshtab. Bir veb-sayt millionlab foydalanuvchiga yetganda, bitta kuchli serverga (vertikal masshtab) sig'maydi. Ko'p arzon serverga gorizontal tarqatish kerak. Relyatsion JOIN va tranzaksiya esa ko'p tugunga tarqalganda qiyinlashadi.
- Moslashuvchanlik. Har bir hujjat boshqacha maydonlarga ega bo'lishi mumkin (mahsulot katalogi: kitobning sahifasi bor, futbolkaning o'lchami bor). Qat'iy sxema bunga qarshilik qiladi.
NoSQL bazalar shu ikki bosimga javob sifatida tug'ildi. Lekin ular bepul kelmaydi β almashinuv (trade-off) bor:
| Nimani olasiz | Nimani berasiz |
|---|---|
| Gorizontal masshtab, yuqori mavjudlik | Kuchli izchillik (ko'pincha "eventual") |
| Moslashuvchan/sxemasiz | Bazaviy yaxlitlik (FK, CHECK yo'q yoki kuchsiz) |
| Ma'lum naqsh uchun tezlik | Ad-hoc so'rov/JOIN qiyin yoki yo'q |
Asosiy fikr: NoSQL "yaxshiroq baza" emas β u boshqacha baza, boshqa trade-off bilan. To'g'ri tanlov β muammoga qarab. Ko'p loyiha NoSQL ni "moda" deb tanlab, keyin JOIN va tranzaksiya yo'qligidan aziyat chekadi.
2. To'rt NoSQL turi va har biri qachon¶
NoSQL β bitta narsa emas, balki to'rtta tamoman boshqa oila. Har biri boshqa ma'lumot modelini va boshqa kirish naqshini optimallashtiradi.
2.1 Document (hujjat) β MongoDB¶
Birlik β hujjat: JSON ga o'xshash, ichma-ich joylangan struktura. Bog'liq ma'lumot bitta hujjatda embed (joylangan) bo'ladi.
- Qachon: bitta agregatni (masalan, butun buyurtmani satrlari bilan) bir o'qishda olasiz; sxema moslashuvchan; katalog, foydalanuvchi profili, CMS, buyurtma.
- Relyatsion ekvivalent: PostgreSQL
JSONBustun (3-bo'limda real misol).
2.2 Key-value (kalit-qiymat) β Redis¶
Birlik β kalit β qiymat lug'ati. Eng sodda model, eng tez.
- Qachon: kalit bo'yicha O(1) tezkor kirish; kesh, sessiya, navbat, reyting, hisoblagich;
TTL(avto-eskirish) kerak. - Relyatsion ekvivalent: odatda kesh qatlami sifatida PostgreSQL ustiga qo'yiladi, uning o'rniga emas.
2.3 Wide-column (keng-ustun) β Cassandra¶
Birlik β partition key + clustering key bilan tashkillangan qator. Jadval so'rov atrofida loyihalanadi (denormalizatsiya majburiy).
- Qachon: ulkan yozish hajmi (write-heavy); vaqt qatorlari, IoT, loglar, metrikalar; gorizontal masshtab va yuqori mavjudlik (AP, pastda).
- Ehtiyot: ad-hoc so'rov va JOIN yo'q β jadvalni avvaldan kerak bo'ladigan so'rovga moslab tuzasiz.
2.4 Graph (graf) β Neo4j¶
Birlik β tugun (node) va qirra (edge). Bog'lanishning o'zi birinchi darajali fuqaro.
- Qachon: chuqur bog'lanish izlash ("do'stimning do'stining do'sti"); tavsiya tizimi, firibgarlik aniqlash, marshrut; relyatsionda 5-6 darajali JOIN og'irlashganda.
- Ehtiyot: oddiy CRUD uchun ortiqcha.
Dizayn qoidasi: turni tanlashdan oldin kirish naqshini yoz: "men ma'lumotni qanday so'rayman?" Agar javob "kalit bo'yicha bitta narsa" bo'lsa β key-value. "Butun agregatni birga" β document. "Chuqur bog'lanish bo'ylab yurish" β graph. "Ulkan yozish, oddiy so'rov" β wide-column. "Murakkab ad-hoc so'rov, yaxlitlik" β relyatsion (PostgreSQL).
3. Relyatsion fikrlashdan voz kechish: access-pattern bo'yicha dizayn¶
Bu bobning eng muhim aqliy siljishi. Relyatsion dunyoda dizayn tartibi shunday:
1. Ma'lumotni normallashtir (anomaliyani yo'qot).
2. Jadvallarni tuz, FK bilan bog'la.
3. Keyin har qanday so'rovni JOIN bilan yoz.
Relyatsionda model ma'lumot tuzilishi atrofida quriladi, so'rov esa keyin keladi β JOIN istalgan savolga javob beradi.
NoSQL da bu teskari:
1. Avval KIRISH NAQSHLARINI yoz: aniq qaysi so'rovlar bo'ladi?
("foydalanuvchining oxirgi 20 buyurtmasi", "buyurtma + satrlari ID bo'yicha")
2. Har bir so'rov BITTA o'qishda javob beradigan qilib ma'lumotni JOYLA.
3. Kerak bo'lsa, ma'lumotni TAKRORLA (denormalizatsiya).
NoSQL da JOIN yo'q yoki qimmat. Shuning uchun "birga so'raladigan narsa β birga saqlanadi". Bu denormalizatsiya NoSQL da norma ekanini anglatadi: bir xil ma'lumot bir necha joyda nusxalanadi, toki har so'rov tez bo'lsin.
Hayotiy o'xshatish: relyatsion baza β kutubxona katalogi: har kitob bir marta, JOIN bilan istalgan tartibda topasiz. NoSQL β restoran menyusi: "nonushta to'plami" da tuxum va non birga chop etilgan, "tushlik to'plami" da yana non bor β non ikki joyda takrorlangan, lekin mijoz bitta sahifada hamma kerakini ko'radi.
3.1 Denormalizatsiyaning narxi¶
Bepul emas: ma'lumot ikki joyda bo'lsa, biri o'zgarganda ikkalasini ham yangilash kerak. Relyatsionda buni FK + bitta yangilash hal qiladi; NoSQL da bu ilova kodining mas'uliyati. Savol shunday: "bu ma'lumot qancha tez-tez o'zgaradi?" Kam o'zgarsa (masalan, buyurtmadagi mahsulot nomi β buyurtma vaqtidagi snapshot) β denormalizatsiya xavfsiz. Tez o'zgarsa (joriy narx, balans) β reference yaxshiroq.
4. Document modellashtirish (MongoDB): EMBED vs REFERENCE¶
Eslatma β illustrativ kod: bu muhitda MongoDB ning
mongod.exebinari bor, lekinmongosh/mongoshell o'rnatilmagan. Shu sababli quyidagi MongoDB sintaksisi bloklari illustrativ (ko'rsatma) β ular MongoDB hujjatlaridagi standart sintaksis, lekin shu yerda ishga tushirib tasdiqlanmagan. Buning o'rniga 3-bo'limdagi g'oyani PostgreSQL JSONB da haqiqatan ishga tushiramiz (5-bo'lim) β JSONB document modelining bir xil "embed" mantig'ini namoyish etadi.
Document modellashtirishning markaziy qarori: bola-ma'lumotni ota-hujjat ichiga joylash (embed) yoki ota faqat unga ishora saqlash (reference)?
4.1 EMBED β ichiga joylash¶
Butun agregat bitta hujjatda. Misol: buyurtma + uning satrlari.
// EMBED β buyurtma satrlari hujjat ichida (illustrativ, MongoDB)
db.orders.insertOne({
_id: ObjectId("..."),
mijoz: { id: 7, ism: "Oqil", shahar: "Toshkent" },
holat: "tolangan",
items: [
{ mahsulot: "Klaviatura", narx: 250000, soni: 1 },
{ mahsulot: "Sichqoncha", narx: 120000, soni: 2 }
]
})
// Bitta o'qishda butun buyurtma keladi β JOIN kerak emas:
db.orders.find({ _id: ObjectId("...") })
Embed qachon to'g'ri:
- bola ota bilan birga o'qiladi ("bir marta o'qisang hammasi kerak");
- bola chegarali (massiv cheksiz o'smaydi);
- bola mustaqil so'ralmaydi (buyurtma satrini buyurtmasiz qidirmaysiz);
- atomik yozuv kerak (butun buyurtma bir operatsiyada saqlanadi).
4.2 REFERENCE β ishora¶
Faqat ID saqlanadi, bola alohida kolleksiyada.
// REFERENCE β buyurtma faqat userId ga ishora qiladi (illustrativ, MongoDB)
db.users.insertOne({ _id: 7, ism: "Oqil", shahar: "Toshkent" })
db.orders.insertOne({ _id: ObjectId("..."), userId: 7, holat: "tolangan" })
// O'qish uchun 2-so'rov yoki $lookup (JOIN ga o'xshash) kerak:
db.orders.aggregate([
{ $match: { _id: ObjectId("...") } },
{ $lookup: { from: "users", localField: "userId",
foreignField: "_id", as: "mijoz" } }
])
Reference qachon to'g'ri:
- bola cheksiz o'sadi (mahsulotning komment oqimi, postning like'lari);
- bola ko'p egada ulashiladi (bitta foydalanuvchi ko'p buyurtmada β N:M);
- bola mustaqil so'raladi yoki tez-tez o'zgaradi (joriy narx β bir joyda turishi kerak).
4.3 Qaror jadvali¶
| Mezon | EMBED | REFERENCE |
|---|---|---|
| Birga o'qiladimi? | Ha β embed | Yo'q β reference |
| Bola cheksiz o'sadimi? | Yo'q β embed | Ha β reference |
| Ko'p egada ulashiladimi? | Yo'q (1:N egalik) β embed | Ha (N:M) β reference |
| Tez-tez mustaqil o'zgaradimi? | Yo'q β embed | Ha β reference |
| O'qish tezligi | Tez (1 so'rov) | Sekinroq ($lookup/2-so'rov) |
Hibrid (subset) naqsh: ko'pincha eng yaxshi yechim β ikkovini aralashtirish. Masalan, mahsulot hujjatiga oxirgi 5 ta sharhni embed qilib, qolganini alohida kolleksiyada reference saqlash. Sahifa darhol bir o'qishda chiqadi, "barcha sharhlar" tugmasi qo'shimcha so'rov qiladi.
4.4 16MB hujjat chegarasi va massiv o'sishi muammosi¶
MongoDB da bitta hujjat 16MB dan oshmaydi. Bu cheklov embed qarorini bevosita boshqaradi: agar embed qilingan massiv cheksiz o'ssa (masalan, mashhur postga million like), siz bu chegaraga urilasiz. Undan oldin ham muammo bor: massiv o'sganda MongoDB hujjatni qayta-qayta diskda ko'chirishi kerak bo'ladi, bu yozishni sekinlashtiradi va indekslarni qayta yozadi.
Qoida: cheksiz o'sadigan narsani hech qachon embed qilmang. "Bu massiv eng ko'pi bilan nechta element bo'ladi?" deb so'rang. Javob "cheksiz" yoki "minglab" bo'lsa β reference. "O'nlab, qattiq chegarali" bo'lsa β embed.
5. PostgreSQL JSONB: document modelining relyatsion ekvivalenti (REAL)¶
MongoDB shell yo'qligi sababli, document modelning bir xil g'oyasini PostgreSQL 18.4 (port 5434) da JSONB bilan haqiqatan ishga tushiramiz. Bu sizga ko'pincha "alohida document baza" kerak emasligini ham ko'rsatadi β PostgreSQL document va relyatsion modelni bitta bazada birlashtiradi.
Quyidagi bloklar
ch20sxemasida haqiqatan bajarildi va natijalar psql chiqishidan ko'chirildi.
5.1 EMBED β butun agregat bitta JSONB hujjatda¶
CREATE SCHEMA ch20;
SET search_path = ch20;
-- "Document-store" naqshi: bitta hujjat = bitta qator, butun agregat JSONB ichida.
CREATE TABLE buyurtmalar (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
hujjat jsonb NOT NULL
);
-- EMBED: buyurtma + mijoz + satrlar bitta hujjatda (MongoDB embed bilan bir xil g'oya)
INSERT INTO buyurtmalar (hujjat) VALUES
('{
"mijoz": {"id": 7, "ism": "Oqil", "shahar": "Toshkent"},
"holat": "tolangan",
"items": [
{"mahsulot": "Klaviatura", "narx": 250000, "soni": 1},
{"mahsulot": "Sichqoncha", "narx": 120000, "soni": 2}
]
}'),
('{
"mijoz": {"id": 9, "ism": "Laylo", "shahar": "Samarqand"},
"holat": "yangi",
"items": [ {"mahsulot": "Monitor", "narx": 1800000, "soni": 1} ]
}');
5.2 Hujjatdan o'qish (->, ->>)¶
SELECT id, hujjat->'mijoz'->>'ism' AS mijoz, hujjat->>'holat' AS holat
FROM buyurtmalar ORDER BY id;
Haqiqiy natija:
-> JSONB qiymatini qaytaradi, ->> esa text qiymatini. Bu MongoDB ning find({}, {ism: 1}) proyeksiyasiga o'xshaydi.
5.3 Embed qilingan massiv ichini yoyish va agregat¶
-- Massiv ichidagi har satrni qatorga yoyamiz:
SELECT b.id,
it->>'mahsulot' AS mahsulot,
(it->>'narx')::numeric AS narx,
(it->>'soni')::int AS soni
FROM buyurtmalar b,
jsonb_array_elements(b.hujjat->'items') AS it
ORDER BY b.id;
Haqiqiy natija:
id | mahsulot | narx | soni
----+------------+---------+------
1 | Klaviatura | 250000 | 1
1 | Sichqoncha | 120000 | 2
2 | Monitor | 1800000 | 1
(3 rows)
-- Har hujjatning jami summasi (embed massiv ustida agregat):
SELECT b.id, SUM((it->>'narx')::numeric * (it->>'soni')::int) AS jami
FROM buyurtmalar b, jsonb_array_elements(b.hujjat->'items') AS it
GROUP BY b.id ORDER BY b.id;
Haqiqiy natija:
5.4 Containment qidiruv (@>) va GIN indeks¶
CREATE INDEX idx_buyurtma_gin ON buyurtmalar USING GIN (hujjat);
SELECT id FROM buyurtmalar WHERE hujjat @> '{"holat": "yangi"}';
Haqiqiy natija:
@> ("o'z ichiga oladi") β JSONB ning kuchli operatori: "hujjat ushbu kalit-qiymatni o'z ichiga oladimi". GIN indeks aynan shu turdagi qidiruvni tezlashtiradi.
Halol kuzatuv: bizning jadvalda atigi 2 qator bor, shuning uchun
EXPLAINplanner GIN indeks o'rnigaSeq Scanni tanlaydi β kichik jadvalda ketma-ket o'qish arzonroq. Bu xato emas: indeks faqat jadval kattalashganda foyda beradi (14-bobdagi qoida). Real ishlab chiqarishda minglab qator bo'lganda planner GIN indeksga o'tadi.
5.5 Hujjatni yangilash (jsonb_set)¶
UPDATE buyurtmalar
SET hujjat = jsonb_set(hujjat, '{holat}', '"yetkazildi"')
WHERE id = 1;
SELECT id, hujjat->>'holat' AS holat FROM buyurtmalar WHERE id = 1;
Haqiqiy natija:
5.6 REFERENCE β relyatsion (normallashtirilgan) ekvivalent¶
O'sha "buyurtma" ni endi reference uslubida β uchta normallashtirilgan jadval bilan β modellashtiramiz:
CREATE TABLE mijozlar (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
ism text NOT NULL, shahar text
);
CREATE TABLE buyurtma_ref (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
mijoz_id bigint NOT NULL REFERENCES mijozlar(id),
holat text NOT NULL
);
CREATE TABLE buyurtma_satr (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
buyurtma_id bigint NOT NULL REFERENCES buyurtma_ref(id),
mahsulot text NOT NULL, narx numeric(12,2) NOT NULL, soni int NOT NULL
);
-- ... ma'lumot kiritilgan ...
-- JOIN bilan o'sha "hujjat" ni qayta yig'amiz:
SELECT m.ism, br.holat, SUM(bs.narx*bs.soni) AS jami
FROM buyurtma_ref br
JOIN mijozlar m ON m.id = br.mijoz_id
JOIN buyurtma_satr bs ON bs.buyurtma_id = br.id
GROUP BY m.ism, br.holat ORDER BY m.ism;
Haqiqiy natija:
ism | holat | jami
-------+----------+------------
Laylo | yangi | 1800000.00
Oqil | tolangan | 490000.00
(2 rows)
E'tibor bering: bir xil jami (490000, 1800000). Farq β modelda, natijada emas:
| JSONB embed (document) | Normallashtirilgan (reference) | |
|---|---|---|
| O'qish | 1 satr, JOIN yo'q | 3 jadval JOIN |
| Yaxlitlik | ilova mas'ul (FK yo'q) | baza majburlaydi (FK) |
| Sxema | moslashuvchan | qat'iy |
| Mahsulot nomi o'zgarsa | har hujjatda alohida yangilash | bir joyda (lookup) |
| Mos vaziyat | snapshot, moslashuvchan agregat | yaxlitlik, ko'p ad-hoc so'rov |
Xulosa: PostgreSQL JSONB sizga "document tanasi" + "relyatsion qovurg'a" ni bitta bazada beradi. Alohida MongoDB ga o'tishdan oldin: PostgreSQL JSONB ehtiyojni qoplamaydimi? Ko'p hollda β qoplaydi.
6. Key-value modellashtirish (Redis): kalit nomlash, TTL, strukturalar¶
Redis β eng sodda NoSQL: kalit β qiymat. Lekin "qiymat" oddiy string emas, balki boy struktura bo'lishi mumkin: hash, list, set, sorted set. Modellashtirishning kaliti β to'g'ri kalit nomlash konvensiyasi va to'g'ri struktura tanlash.
Quyidagi misollar Redis serverida (port 6380) haqiqatan ishga tushirildi (
redis-cli), natijalar chiqishdan ko'chirildi.
6.1 Kalit nomlash konvensiyasi¶
Redis da "jadval" yo'q β faqat tekis kalit fazosi. Tashkilotni kalit nomi beradi. Standart konvensiya β ikki nuqta (:) bilan ierarxiya:
Misol:
user:123:profile -- 123-foydalanuvchi profili (hash)
user:123:feed -- uning oqimi (list)
session:abc123 -- sessiya (string + TTL)
post:987:views -- ko'rishlar hisoblagichi (string/int)
post:987:likes -- yoqtirganlar (set)
leaderboard -- global reyting (sorted set)
Nega muhim: yaxshi nomlash sizga
KEYS user:123:*(yoki ishlab chiqarishdaSCAN) bilan bog'liq kalitlarni topish, mantiqiy guruhlash va xatolarni kamaytirish imkonini beradi. Yomon nomlash (u123p,data1) β relyatsiondagi "magic ustun nomi" anti-naqshining (13-bob) Redis versiyasi.
6.2 String + TTL (sessiya)¶
SET session:abc123 "user42" EX 3600 # 3600 soniya = 1 soat TTL
GET session:abc123 # -> "user42"
TTL session:abc123 # -> qancha soniya qoldi
Haqiqiy natija:
TTL β dizayn vositasi: Redis kalitga avto-eskirish (
EX) qo'ya oladi. Sessiya, kesh, bir martalik kod (OTP), rate-limit oynasi β hammasi TTL bilan o'zini avtomatik tozalaydi. Relyatsionda bunicronyokiWHERE expires_at < now()bilan qo'lda qilasiz.
6.3 Hash (profil β maydonli obyekt)¶
HSET user:123:profile name "Oqil" city "Toshkent" plan "pro"
HGETALL user:123:profile
HGET user:123:profile plan
Haqiqiy natija:
Hash β relyatsion qatorga o'xshaydi: maydonlar to'plami bitta kalit ostida. Bitta maydonni o'qish/yozish butun obyektni o'qimasdan mumkin (HGET/HSET).
6.4 String INCR (atomik hisoblagich)¶
Haqiqiy natija:
Bu Redis ning eng kuchli tomonlaridan biri:
INCRatomik β minglab parallel so'rov bir vaqtda kelsa ham hisob to'g'ri qoladi, qulf kerak emas. Relyatsionda buniUPDATE ... SET views = views + 1qiladi, lekin Redis da bu xotirada, juda tez.
6.5 List, Set, Sorted Set¶
# LIST β tartibli (oqim, navbat). LPUSH boshiga qo'shadi:
LPUSH user:123:feed "post:1" "post:2" "post:3"
LRANGE user:123:feed 0 -1 # -> post:3, post:2, post:1 (oxirgi birinchi)
# SET β takrorsiz to'plam (kim like bosgan):
SADD post:987:likes user:1 user:2 user:2 user:9
SCARD post:987:likes # -> 3 (user:2 takror hisoblanmaydi)
SISMEMBER post:987:likes user:2 # -> 1 (bor)
# SORTED SET (zset) β ball bo'yicha tartiblangan reyting:
ZADD leaderboard 1500 user:1 2300 user:2 1800 user:9
ZREVRANGE leaderboard 0 -1 WITHSCORES # eng yuqori balldan
Haqiqiy natija (asosiy qatorlar):
SCARD post:987:likes -> 3
SISMEMBER ... user:2 -> 1
ZREVRANGE leaderboard:
user:2 2300
user:9 1800
user:1 1500
| Struktura | Relyatsion ekvivalent | Tipik ishlatish |
|---|---|---|
| String / int | bitta ustun qiymati | kesh, sessiya, hisoblagich |
| Hash | jadval qatori | obyekt/profil |
| List | tartibli ustun (ORDER BY pos) |
oqim, navbat, oxirgi N |
| Set | DISTINCT to'plam |
teglar, like'lar, a'zolik |
| Sorted set | ORDER BY ball DESC |
reyting (leaderboard), top-N |
Asosiy fikr: Redis "kalit β string" emas β u tarkibida kichik ma'lumot strukturalarining serveri. To'g'ri struktura tanlash β Redis modellashtirishning yuragi. Reyting kerakmi? Sorted set β
ZADD/ZREVRANGEbir buyruqda hal qiladi; relyatsionda buni har so'rovdaORDER BY ... LIMITqilasiz.
7. Wide-column (Cassandra) β qisqacha¶
Wide-column bazada (Cassandra, ScyllaDB) jadval so'rov atrofida loyihalanadi. Ikki qismli birlamchi kalit:
- Partition key β ma'lumot qaysi tugunda saqlanishini belgilaydi (taqsimot). Bir partition ichidagi hamma ma'lumot birga turadi.
- Clustering key β partition ichida qatorlar qanday tartiblanishini belgilaydi.
-- Illustrativ (Cassandra CQL β bu muhitda Cassandra yo'q):
-- "Foydalanuvchining xabarlarini vaqt bo'yicha" so'roviga MOSLAB tuziladi:
CREATE TABLE xabarlar_user_boyicha (
user_id uuid,
vaqt timestamp,
xabar text,
PRIMARY KEY ((user_id), vaqt) -- partition=user_id, clustering=vaqt DESC
) WITH CLUSTERING ORDER BY (vaqt DESC);
Bu yerda dizayn falsafasi keskin farq qiladi: agar sizga "xabarni ID bo'yicha" ham, "vaqt bo'yicha" ham kerak bo'lsa, Cassandra da ikki alohida jadval tuzasiz va ma'lumotni ikkalasiga ham yozasiz (denormalizatsiya majburiy). JOIN yo'q, ad-hoc WHERE yo'q. Buning evaziga β ulkan write hajmi va deyarli cheksiz gorizontal masshtab.
Qachon: vaqt qatorlari, IoT sensor oqimi, hodisa loglari, metrikalar β yozish ko'p, so'rov naqshi oldindan ma'lum va sodda. PostgreSQL bunday hajmga (partitioning bilan, 22-bob) bir muddat dosh beradi; haqiqiy "petabayt + ko'p datacenter" miqyosida wide-column kerak bo'ladi.
8. Graph (Neo4j) β qisqacha¶
Graf baza ma'lumotni tugun (entity) va qirra (bog'lanish) sifatida ko'radi. Qirralarning o'zi atributga ega bo'lishi mumkin.
// Illustrativ (Neo4j Cypher β bu muhitda Neo4j yo'q):
// "Do'stimning do'sti, lekin men bilan hali do'st emas" (tavsiya):
MATCH (men:User {id: 7})-[:DUST]->(dost)-[:DUST]->(tavsiya)
WHERE NOT (men)-[:DUST]->(tavsiya) AND tavsiya <> men
RETURN tavsiya.ism, count(*) AS umumiy_dostlar
ORDER BY umumiy_dostlar DESC LIMIT 5
Relyatsionda bu so'rov chuqurlik oshgani sayin og'irlashadi: "do'stning do'sti" β ikkita self-JOIN; "5 darajagacha" β recursive CTE va katta jadvalda sekin. Graf bazada esa "qirra bo'ylab yurish" tabiiy va doimiy tezlikda.
Qachon: ijtimoiy tarmoq, tavsiya tizimi, firibgarlik aniqlash (g'ayritabiiy bog'lanish naqshlari), bilim grafi, marshrut/logistika. Oddiy 1:N yoki N:M bog'lanish uchun graf baza ortiqcha β relyatsion junction jadval (4-bob) yetadi. 17-bobda graf strukturasini relyatsionda (qirralar jadvali, recursive CTE) qanday modellashtirishni ko'rgansiz; graf baza faqat chuqur bog'lanish izlash hukmron bo'lganda kerak.
9. Polyglot persistence: har ish uchun to'g'ri baza¶
Real katta tizimlar bitta bazaga tayanmaydi. Polyglot persistence β har bir vazifa uchun eng mos bazani ishlatish g'oyasi.
Misol β bitta marketplace:
| Vazifa | Baza | Nega |
|---|---|---|
| Buyurtma, to'lov, hisob | PostgreSQL | ACID, FK, tranzaksiya β pul xato bo'lmasin |
| Kesh, sessiya, reyting, savat | Redis | tezlik, TTL, atomik hisoblagich |
| Mahsulot to'liq-matn qidiruv | Elasticsearch | nisbiy reyting, til tahlili |
| "Bu mahsulotni olganlar shuni ham oldi" | Neo4j / graf | bog'lanish bo'ylab tavsiya |
| Hodisa loglari, analitika | wide-column / ombor | ulkan write, 21-bob |
Ehtiyot β polyglot bepul emas: har qo'shilgan baza β yangi operatsion yuk (zaxira, monitoring, mutaxassis), va bazalar orasidagi izchillikni saqlash qiyin (Redis keshi PostgreSQL bilan moslashmay qolishi mumkin). Qoida: bitta yaxshi sozlangan PostgreSQL dan boshlang (u JSONB, to'liq-matn qidiruv, GIN, hatto navbat ham qila oladi). Yangi bazani faqat real, o'lchangan og'riq paydo bo'lganda qo'shing β "balki kerak bo'lar" deb emas.
10. CAP teoremasi va BASE (vs ACID)¶
NoSQL ni tushunish uchun ikki tushuncha kerak: CAP va BASE.
10.1 CAP teoremasi¶
Taqsimlangan bazada (ma'lumot bir necha tugunda) uchta xususiyatdan ikkitasini olasiz, uchchovini birga emas:
- C β Consistency (izchillik): har o'qish eng so'nggi yozuvni qaytaradi (yoki xato).
- A β Availability (mavjudlik): har so'rov javob oladi (eng so'nggi bo'lmasa ham).
- P β Partition tolerance (bo'linishga chidamlilik): tugunlar orasidagi tarmoq uzilsa ham tizim ishlaydi.
Tarmoq bo'linishi (P) real dunyoda muqarrar β kabel uziladi, tugun yiqiladi. Shuning uchun taqsimlangan tizimda haqiqiy tanlov C va A orasida (P ni tashlab bo'lmaydi):
- CP (izchillik + bo'linish): bo'linish bo'lsa, eskirgan javob berishdan ko'ra javob bermaslik ni tanlaydi. Misol: MongoDB (sozlamaga ko'ra), HBase. Bank balansi, inventar β eski son xavfli.
- AP (mavjudlik + bo'linish): bo'linish bo'lsa, eskirgan javob bersa ham ishlashda davom etadi. Misol: Cassandra, DynamoDB. Savat, ijtimoiy oqim β bir oz eskilik tolerantli.
Bitta tugundagi PostgreSQL β taqsimlanmagan, demak P muammosi yo'q; u CA da, qat'iy ACID izchillik bilan ishlaydi. CAP faqat ma'lumot ko'p tugunga tarqalganda (replikatsiya/sharding, 22-bob) ahamiyat kasb etadi.
10.2 ACID vs BASE¶
| ACID (relyatsion) | BASE (ko'p NoSQL) | |
|---|---|---|
| Izchillik | Atomicity, Consistency, Isolation, Durability β doim to'g'ri | Eventual (oxir-oqibat to'g'ri) |
| Falsafa | "har lahzada izchil" | Basically Available, Soft state, Eventual consistency |
| Narx | masshtab qiyinroq | masshtab oson, lekin "eski o'qish" mumkin |
| Mos | pul, buyurtma, yaxlitlik muhim | feed, like soni, "taxminan to'g'ri yetadi" |
Eventual consistency misoli: postga like bossangiz, do'stingiz uni 2 soniyadan keyin ko'rishi mumkin β bu yetarli. Lekin bank o'tkazmasida 2 soniya "eski balans" β qabul qilinmaydi (ACID kerak).
Dizayn savoli: "bu ma'lumot uchun bir necha soniyalik eskilik xavflimi?" Ha β ACID/CP (PostgreSQL). Yo'q β BASE/AP qabul qilinadi, masshtab uchun. Ko'p tizim ikkalasini ishlatadi: pul β PostgreSQL (ACID), like soni β Redis/AP.
Mashqlar¶
Quyidagi masalalar dizayn masalalari β kod yozish emas, qaror qabul qilish. Har birida sababini ayting.
Oson¶
- Tur tanlash. Quyidagi har bir ehtiyojga qaysi NoSQL turi (yoki PostgreSQL) eng mos: (a) foydalanuvchi sessiyasi 30 daqiqa TTL bilan; (b) ijtimoiy tarmoqda "umumiy do'stlar"; (c) IoT sensorlardan soniyasiga 100000 o'lchov; (d) bank hisob-kitobi.
- Embed yoki reference (1). Blog tizimi:
Postva uningCommentlari. Bir postga minglab komment kelishi mumkin. Embed qilasizmi yoki reference? Nega? - Embed yoki reference (2). Foydalanuvchi va uning
manzili(uy manzili, bittadan kam, kamdan-kam o'zgaradi). Embed yoki reference? - Kalit nomlash. Redis da quyidagilar uchun kalit nomlarini taklif qiling: 42-foydalanuvchining profili; 42-foydalanuvchining xarid savati; "kun mahsuloti" global keshi.
- TTL. Quyidagilardan qaysilariga Redis
TTLmos: (a) parolni tiklash kodi; (b) foydalanuvchi profili; (c) rate-limit "daqiqada 100 so'rov" oynasi; (d) doimiy buyurtma tarixi. - Struktura tanlash. Redis da "maqolaning yoqtirganlari soni va kim yoqtirgani" ni saqlash uchun qaysi struktura? "Eng ko'p ball to'plagan 10 o'yinchi" uchun-chi?
O'rta¶
- Access-pattern dizayni. Taksi ilovasi MongoDB da. Asosiy so'rovlar: "haydovchining bugungi safarlari ro'yxati" va "safar tafsiloti ID bo'yicha". Hujjat(lar)ni qanday modellashtirasiz (embed/reference, qaysi kolleksiya)?
- Relyatsion β document o'tkazish. Quyidagi normallashtirilgan sxemani MongoDB hujjatiga aylantiring va embed/reference qarorini asoslang:
- 16MB / massiv o'sishi. Mashhur Instagram-uslubidagi post uchun "like bosganlar" ni post hujjatiga embed qilish nega xavfli? Buning o'rniga qanday modellashtirasiz?
- CAP qarori. Onlayn-do'kon savati va to'lov jarayoni. Qaysi qism uchun CP (izchillik), qaysi qism uchun AP (mavjudlik) mos? Nega?
- Polyglot loyihasi. Yetkazib berish ilovasi uchun: (a) buyurtma/to'lov; (b) jonli kuryer joylashuvi keshi; (c) "shu yaqindagi restoranlar" qidiruvi. Har biriga baza tanlang va asoslang.
- Denormalizatsiya narxi. Document bazada buyurtma hujjatiga mahsulot nomi va narxini nusxalab embed qildingiz. Mahsulot narxi o'zgarsa nima bo'ladi? Bu yerda denormalizatsiya to'g'rimi yoki xato?
Qiyin¶
- PostgreSQL JSONB vs MongoDB. Bir CMS loyihasida jamoa "MongoDB ga o'tamiz, chunki bizga moslashuvchan sxema kerak" deydi. Qachon bu to'g'ri qaror, qachon PostgreSQL JSONB yetarli? Aniq mezonlar bilan tahlil qiling.
- Hibrid (subset) naqsh. Mahsulot sahifasida darhol oxirgi 3 sharh ko'rinishi kerak, "barchasi" tugmasi qolganini yuklaydi. MongoDB da bu hibrid embed+reference naqshini qanday loyihalaysiz? Sharh yangilanganda izchillik muammosi qanday yuzaga keladi?
- Wide-column dizayni. Cassandra da chat ilovasi uchun: "suhbatdagi xabarlarni vaqt bo'yicha" va "foydalanuvchi yozgan barcha xabarlarni" so'rovlari kerak. Nega bitta jadval yetmaydi? Partition/clustering kalitlarini tanlang.
- Eventual consistency tuzog'i. Redis da mahsulot omborini (
stock) keshlaysiz, PostgreSQL haqiqiy manba. Mijoz oxirgi 1 dona mahsulotni Redis keshiga qarab sotib oladi, lekin u allaqachon sotilgan. Bu muammoni dizayn darajasida qanday oldini olasiz?
Yechimlar¶
Yechim β 1
(a) Redis β kalit-qiymat + TTL sessiya uchun ideal (SET session:... EX 1800). (b) Graph (Neo4j) β "umumiy do'stlar" bog'lanish bo'ylab yurish, graf bazaning klassik kuchli tomoni. (c) Wide-column (Cassandra) β ulkan write hajmi, vaqt qatori, sodda so'rov naqshi. (d) PostgreSQL (relyatsion, ACID) β pul uchun qat'iy izchillik va tranzaksiya majburiy; bu yerda NoSQL xato bo'lardi.
Yechim β 2
Reference. Komment massivi cheksiz o'sadi (minglab) β embed qilsangiz 16MB chegarasiga yaqinlashasiz va massiv o'sgani sayin hujjat qayta ko'chiriladi (sekinlik). Kommentlar alohida comments kolleksiyasida postId reference bilan turishi kerak. Hibrid variant: postga oxirgi 2-3 kommentni embed qilib, qolganini reference (14-mashqqa qarang).
Yechim β 3
Embed. Manzil: (1) doim foydalanuvchi bilan birga o'qiladi; (2) chegarali (1 ta yoki bir nechta, cheksiz emas); (3) mustaqil so'ralmaydi (manzilni egasiz qidirmaysiz); (4) kamdan-kam o'zgaradi. To'rt mezon ham embed tarafida. user: { manzil: { kocha, shahar, indeks } }.
Yechim β 4
- Profil:
user:42:profile(hash βHSET ... name ... email ...). - Savat:
user:42:cart(hash yoki list, masalanHSET user:42:cart product:7 2). - Global kun mahsuloti keshi:
cache:product-of-day(string yoki hash) + TTLEX 86400.
Konvensiya: <turi>:<id>:<atribut>, global narsa uchun cache: prefiksi. Bu SCAN user:42:* bilan bir foydalanuvchining hamma kalitini topish imkonini beradi.
Yechim β 5
TTL mos: (a) parol tiklash kodi β ha (EX 600, 10 daqiqa); (c) rate-limit oynasi β ha (EX 60). TTL mos emas: (b) profil β doimiy ma'lumot, eskirmasligi kerak; (d) buyurtma tarixi β doimiy, hech qachon avto-o'chmasligi kerak (bu umuman Redis emas, PostgreSQL da turishi kerak). Qoida: TTL faqat vaqtinchalik, qayta yaratish mumkin bo'lgan ma'lumot uchun.
Yechim β 6
- Yoqtirganlar to'plami: Set β
SADD post:987:likes user:1(takrorsiz), soniSCARD, "men bosdimmi"SISMEMBER. Set takrorlanishni o'zi oldini oladi. - Top-10 o'yinchi: Sorted set β
ZADD leaderboard 2300 user:2, keyinZREVRANGE leaderboard 0 9 WITHSCORES. Sorted set ball bo'yicha tartibni doimiy saqlaydi, har so'rovda saralash kerak emas.
Yechim β 7
Asosiy kolleksiya β trips (safarlar), har safar bitta hujjat: { _id, haydovchi_id, vaqt, manzil_dan, manzil_gacha, narx, holat }. Safar mustaqil so'raladi (ID bo'yicha) va haydovchiga reference (haydovchi_id), embed emas β chunki haydovchining safarlari cheksiz o'sadi.
"Haydovchining bugungi safarlari" so'rovi uchun haydovchi_id + vaqt ga indeks. Haydovchini har safarga embed qilmaymiz (denormalizatsiya nusxasini yangilash og'ir bo'lardi). Eslatma: haydovchining nomi kabi kam o'zgaradigan maydonni safar hujjatiga snapshot sifatida embed qilish mumkin (tarixiy to'g'rilik uchun).
Yechim β 8
// kitob hujjati (illustrativ, MongoDB)
{
_id: ObjectId("..."),
sarlavha: "Ma'lumotlar bazasi dizayni",
muallif: { id: 5, ism: "Oqil" }, // EMBED: muallif kam o'zgaradi, birga o'qiladi
sharhlar: [ // EMBED yoki REFERENCE β sharh soniga bog'liq
{ matn: "Zo'r kitob", ball: 5 },
{ matn: "Foydali", ball: 4 }
]
}
Qaror: muallif β embed (snapshot: ism kam o'zgaradi, kitob bilan birga o'qiladi). sharhlar β agar kitobga oz (o'nlab) sharh kelsa embed; agar minglab kelsa reference (alohida reviews kolleksiya, kitob_id bilan) β chunki cheksiz o'sish + 16MB. Real loyihada sharhlar odatda reference, chunki ularning soni oldindan noma'lum.
Yechim β 9
Xavf: mashhur postga millionlab like keladi. Embed qilsangiz: (1) hujjat 16MB chegarasiga uriladi; (2) har yangi like'da massiv o'sib, MongoDB butun hujjatni diskda qayta ko'chiradi β yozish sekinlashadi; (3) postni o'qiganda million elementli massivni ham yuklaysiz, garchi faqat sonni ko'rsatsangiz ham.
Yechim: like'ni reference qiling. (a) Like sonini post hujjatida hisoblagich sifatida saqlang (likes_count: 1234567); (b) kim like bosgani alohida likes kolleksiyada ({ post_id, user_id }) yoki Redis Set'da. PostgreSQL/Redis da: Redis SADD post:987:likes user:1 + SCARD soni uchun β bu eng tez yechim.
Yechim β 10
- Savat β AP (mavjudlik). Savat bir oz eskirgan bo'lsa fojea emas; mijoz har doim savatga qo'sha olsin (mavjudlik muhim). Eventual consistency qabul qilinadi. Redis (AP-uslub kesh) mos.
- To'lov β CP (izchillik). Pul ikki marta yechilmasligi, balans aniq bo'lishi shart. Bo'linish bo'lsa, noaniq javob berishdan ko'ra operatsiyani rad etish afzal. PostgreSQL ACID tranzaksiya majburiy.
Bu polyglot misoli: bir ilovada ikki qism β ikki xil izchillik talabi, ikki xil baza/yondashuv.
Yechim β 11
- (a) Buyurtma/to'lov β PostgreSQL: ACID, FK, tranzaksiya (pul, yaxlitlik).
- (b) Jonli kuryer joylashuvi β Redis: tez-tez yangilanadigan, vaqtinchalik ma'lumot;
GEOstrukturasi yoki oddiy hash + TTL. Eskilik tolerantli (AP). - (c) "Yaqindagi restoranlar" β PostgreSQL + PostGIS (geografik indeks) yoki Elasticsearch (geo qidiruv). Aslida bu yerda alohida NoSQL shart emas β PostgreSQL PostGIS bilan geo-qidiruvni a'lo qiladi.
Xulosa: PostgreSQL ikki vazifani (a, c) qoplaydi; faqat tez-o'zgaruvchi jonli joylashuv (b) uchun Redis qo'shiladi. Bu "kerakli joyda NoSQL" tamoyiliga mos β uchta alohida baza shart emas.
Yechim β 12
Mahsulot narxi o'zgarsa, eski buyurtmalardagi nusxa o'zgarmaydi β va bu to'g'ri! Buyurtma narxi β sotib olish paytidagi narx (snapshot), joriy narx emas. Agar reference qilsangiz va joriy narxni ko'rsatsangiz, eski cheklar buzilardi.
Bu to'g'ri denormalizatsiya: buyurtma satriga mahsulot_nomi va narx ni atayin nusxalaysiz, chunki ular tarixiy fakt. Bu hatto relyatsion dizaynda ham tavsiya etiladi (12-bobdagi "snapshot" naqshi). Aksincha, mahsulot.tavsifi kabi "joriy" ma'lumotni reference qilish kerak. Qoida: tarixiy qiymat β nusxala; joriy qiymat β reference.
Yechim β 13
PostgreSQL JSONB yetarli, qachon: - Ma'lumotning faqat bir qismi moslashuvchan, qolgani strukturali (PostgreSQL JSONB ustun + oddiy ustunlar aralash). - Sizga JOIN, tranzaksiya, FK, yaxlitlik ham kerak (CMS odatda foydalanuvchi, ruxsat, kategoriya bilan bog'liq β bularga relyatsion kerak). - Jamoa allaqachon PostgreSQL biladi, qo'shimcha operatsion baza istamaydi. - Hajm bitta PostgreSQL serveriga sig'adi (millionlab, milliardlab emas).
MongoDB to'g'ri, qachon: - Ma'lumot tabiatan hujjat β har yozuv tamoman boshqa struktura, deyarli hech qachon JOIN kerak emas. - Gorizontal masshtab (sharding) birinchi kundanoq talab β petabayt darajasi. - Kirish naqshi sof "agregatni ID bo'yicha ol/yoz".
Tahlil mezoni: "Bizga JOIN va yaxlitlik kerakmi?" Ha bo'lsa β PostgreSQL JSONB (ikkala dunyoni beradi). "Faqat moslashuvchan sxema" uchun MongoDB ga o'tish β ko'pincha noto'g'ri sabab, chunki JSONB aynan shuni beradi, qolgan kuchni yo'qotmasdan. Ko'p loyiha MongoDB ni tanlab, keyin JOIN/tranzaksiya yo'qligidan qayta PostgreSQL ga ko'chgan.
Yechim β 14
Subset (hibrid) naqsh:
- products kolleksiyasida har mahsulotga oxirgi 3 sharhni embed qiling: recent_reviews: [...] (massivni 3 ta bilan cheklang).
- Barcha sharhlar alohida reviews kolleksiyada product_id reference bilan.
- Mahsulot sahifasi bir o'qishda mahsulot + 3 sharhni oladi (tez); "barchasi" tugmasi reviews dan qo'shimcha so'rov qiladi.
Izchillik muammosi: yangi sharh kelganda ikki joyni yangilash kerak β reviews ga insert va products.recent_reviews ni yangilab, eng eski 4-sharhni massivdan chiqarib tashlash ($push + $slice: -3). Agar biri muvaffaqiyatli, ikkinchisi xato bo'lsa β nomuvofiqlik. MongoDB da buni bitta hujjatga tegsa atomik, lekin ikki kolleksiyaga tegsa tranzaksiya (yoki eventual yangilash) kerak. Bu denormalizatsiya narxi β tezlik evaziga izchillik mas'uliyati ilovaga o'tadi.
Yechim β 15
Nega bitta jadval yetmaydi: Cassandra da WHERE faqat partition key (va clustering) bo'yicha ishlaydi β ad-hoc WHERE yo'q. "Suhbat bo'yicha" so'rov partition=suhbat_id talab qiladi; "foydalanuvchi bo'yicha" so'rov partition=user_id talab qiladi. Bir jadval bir partition kalitiga ega β ikki xil so'rov ikki xil partition kalitini talab qiladi.
Yechim β ikki jadval (denormalizatsiya):
Har xabar yozilganda ikkala jadvalga ham yoziladi. Bu Cassandra falsafasi: "so'rov uchun jadval tuz, ma'lumotni takrorla". Diskdan ko'ra so'rov tezligi muhim.Yechim β 16
Bu klassik eventual consistency tuzog'i: kesh (Redis) haqiqiy manbadan (PostgreSQL) orqada qoladi va ikki mijoz bir vaqtda oxirgi dona uchun "bor" javobini oladi.
Dizayn yechimi: muhim, cheklangan resurs (oxirgi dona) uchun keshga ishonmang β sotib olish lahzasida haqiqiy manbada atomik tekshiring:
-- PostgreSQL: faqat zaxira bor bo'lsa kamaytir (atomik, qulfsiz):
UPDATE mahsulotlar SET stock = stock - 1
WHERE id = :id AND stock > 0; -- 0 qator o'zgarsa -> sotuvga ulgurmadi
DECR ni atomik rezervatsiya sifatida ishlatib, manfiyga tushsa orqaga qaytaring. Qoida: keshni ko'rsatish uchun (ro'yxatda "bor/yo'q") ishlating, lekin qaror (sotib olish) ni har doim haqiqiy manbada atomik amalga oshiring. "Eski o'qish" tolerantli joy β kesh; "to'g'ri yozish" majburiy joy β ACID baza.
β¬ οΈ Oldingi: 19 β Multi-tenancy dizayni va RLS Β· π README Β· Keyingi: 21 β Analitik dizayn va ma'lumotlar ombori β‘οΈ