Bu qatlamda nima bor
Core darslik asoslar va shard key tanlashni berdi. Bu Deep qatlam senior darajadagi chuqur mexanikani ochadi: virtual buckets bilan oson resharding, downtime’siz online resharding, tayyor yechimlar (Vitess/Citus), cross-shard so‘rovlarni boshqarish, global secondary index va hot shard splitting. Oxirida — yig‘ma arxitektura va senior cheklist.
Consistent hashing va virtual buckets
vBuckets · ringSenior haqiqat: Core’dagi hash(key) % Nning asl muammosi — resharding. Shard qo‘shsang N o‘zgaradi, deyarli hamma kalit ko‘chadi. Yechim — kalit bilan shard o‘rtasiga o‘zgarmas qatlam qo‘yish.
Consistent hashing vs Virtual buckets
Consistent hashing (4 va 5-mavzularda ko‘rgan halqa + virtual node’lar) — kesh va LB uchun ajoyib. Lekin ma’lumotlar bazasi shardingida amaliyroq yondashuv — virtual buckets (logik shardlar): kalitni katta, fiksal sondagi bucket’ga (masalan 1024) xeshlaysan, bucket’larni esa fizik shardlarga taqsimlaysan.
// VIRTUAL BUCKETS: kalit -> bucket (O'ZGARMAS) -> shard (o'zgaruvchan MAP)
const TOTAL_BUCKETS = 1024; // ko'p, fiksal son (shard sonidan katta)
function keyToBucket(key: string): number {
return crc32(key) % TOTAL_BUCKETS; // bu hech qachon o'zgarmaydi
}
// Bucket -> shard MAP'i. Resharding = shu map'ni o'zgartirish (bucket'larni ko'chirish),
// kalitlarni qayta-hash qilish EMAS.
const bucketMap: Record<number, string> = {
// 0..340 -> 'shard-0'
// 341..681 -> 'shard-1'
// 682..1023-> 'shard-2'
};
function shardForKey(key: string): string {
return bucketMap[keyToBucket(key)];
}
Shard qo‘shganda kalitlarni qayta-hash qilmaysan — shunchaki ba’zi bucket’larni yangi shardga ko‘chirasan. Faqat o‘sha bucket’dagi ma’lumot harakatlanadi (masalan, 1024 dan 100 ta bucket), qolgani joyida qoladi. Vitess, Cassandra (vnodes), Couchbase (vBuckets) aynan shunday ishlaydi.
Downtime‘siz online resharding
backfill · dual-write · cutoverEng qiyin operatsiya: tizimni to‘xtatmasdan ma’lumotni qayta taqsimlash. Bu — online schema migration’ga o‘xshash, nozik raqs.
Yangi shard qo‘shding. Endi bucket’larning bir qismini unga ko‘chirish kerak — lekin ayni paytda tizim ishlab, o‘sha ma’lumotga yozib-o‘qib turibdi. Ko‘chirish jarayonida ma’lumot yo‘qolmasligi yoki ikkilanmasligi kerak.
Online resharding bosqichlari
1. Tayyorlash: yangi shardni qo‘sh, ko‘chiriladigan bucket’larni belgila. 2. Backfill: mavjud ma’lumotni fonda eski sharddan yangisiga nusxalashni boshla. 3. Dual-write: backfill davomida yangi yozuvlarni ikkala shardga yoz (ma’lumot orqada qolmasin):
// CUTOVER paytida DUAL-WRITE: migratsiya davomida ikkala shardga ham yozamiz
async write(key: string, data: any) {
const bucket = keyToBucket(key);
if (this.migratingBuckets.has(bucket)) {
// bu bucket ko'chirilmoqda -> ESKI va YANGI shardga birga yoz
await Promise.all([
this.oldShard(bucket).upsert(data),
this.newShard(bucket).upsert(data),
]);
} else {
await this.shardForKey(key).upsert(data); // normal
}
}
4. Verify + cutover: ikkala shard mosligini tekshir, so‘ng bucketMap’ni atomik yangilab, o‘qishni yangi shardga o‘tkaz. 5. Cleanup: dual-write’ni to‘xtatib, eski sharddagi ko‘chirilgan ma’lumotni o‘chir.
Vitess VReplication aynan shu jarayonni (backfill + binlog kuzatuvi + cutover) avtomatlashtiradi. O‘zing qo‘lda yozish — xatoga juda moyil, shuning uchun mumkin bo‘lsa tayyor yechimga tayan.
Vitess va Citus
vtgate · distributed tableSenior maslahat: sharding’ni o‘zing noldan yozma — bu eng xatoga moyil ish. Yetuk, sinovdan o‘tgan yechimlar bor.
Vitess — MySQL uchun (YouTube, Slack, GitHub)
Vitess MySQL ustiga shardlash qatlamini qo‘yadi. vtgate — so‘rov routeri (ilova bitta nuqtaga ulanadi, sharding’ni sezmaydi), VReplication — downtime’siz resharding, cross-shard so‘rov, ulanish pooling — hammasini boshqaradi. Ulkan masshtabda isbotlangan.
Citus — PostgreSQL uchun (sizning stack‘ingizga mos)
Citus — PostgreSQL kengaytmasi: oddiy Postgres’ni taqsimlangan bazaga aylantiradi. Coordinator node so‘rovlarni worker node’larga tarqatadi. Eng kuchli tomoni — siz baribir oddiy SQL yozasiz, Citus uni shardlar bo‘ylab bajaradi:
-- CITUS (PostgreSQL extension) — sharding'ni Postgres ICHIDA beradi
-- jadvalni taqsimlash kaliti (distribution column) bo'yicha shardlash
SELECT create_distributed_table('orders', 'user_id');
-- bog'liq jadvalni BIR XIL kalit bo'yicha -> ular bir shardda -> LOKAL JOIN ishlaydi
SELECT create_distributed_table('order_items', 'user_id', colocate_with => 'orders');
-- kichik, kam o'zgaradigan jadval -> HAMMA node'ga nusxa (reference table)
SELECT create_reference_table('countries');
| DIY (o‘zing) | Vitess / Citus | |
|---|---|---|
| Cross-shard so‘rov | Qo‘lda yozasan | Avtomatik |
| Resharding | Juda qiyin, xavfli | Tayyor (VReplication / rebalance) |
| Nazorat | To‘liq | Cheklangan, lekin yetarli |
| Murakkablik | Yuqori (sen ko‘tarasan) | O‘rganish + operatsiya |
Postgres’da bo‘lsangiz va sharding kerak bo‘lsa — avval Citusni ko‘rib chiqing (yoki uning boshqariladigan varianti). O‘z routing kodingizni faqat oddiy holatlar yoki maxsus talablar uchun yozing.
Distributed query va colocation
pushdown · keyset · colocationBir shardga tushadigan so‘rov tez. Lekin cross-shard so‘rovlar — sharding’ning eng katta solig‘i. Senior ularni kamaytirishni va to‘g‘ri bajarishni biladi.
Pushdown va qisman agregatsiya
Cross-shard agregatsiyada ishni har shardga itarib (pushdown), natijalarni app tomonida birlashtirasan (map-reduce uslubi). Lekin har agregatsiya teng emas:
- Oson (bo‘linadigan):
COUNT,SUM,MIN,MAX— har sharddan olib qo‘shasan. - O‘rtacha:
AVG = SUM/COUNT— alohida yig‘ib hisoblaysan (har shard AVG’ini o‘rtachalab bo‘lmaydi!). - Qiyin:
DISTINCT COUNT(HyperLogLog bilan taxminiy), median/percentile — to‘liq ma’lumotsiz aniq hisoblab bo‘lmaydi.
// PUSHDOWN: filtr/agregatsiyani HAR shardda bajarib, natijani app'da birlashtir
// count, sum, min, max -> bo'linadigan (decomposable) -> oson
async totalRevenue(): Promise<number> {
const partials = await Promise.all(
this.shards.all().map((db) => db.order.aggregate({ _sum: { total: true } })),
);
return partials.reduce((acc, p) => acc + (p._sum.total ?? 0), 0);
}
// AVG = SUM/COUNT (alohida yig'ib hisobla). DISTINCT COUNT, median, percentile -> QIYIN.
Cross-shard saralash va pagination — eng qiyin
"10-sahifa"ni olish uchun har sharddan OFFSET 90 LIMIT 10 qilib bo‘lmaydi — global tartibda 10-sahifa har shardda turlicha. To‘g‘ri yo‘l: har sharddan yetarlicha olib, app’da merge-sort qilish (qimmat). Yaxshiroq — keyset (cursor) pagination:
// CROSS-SHARD PAGINATION: OFFSET yomon (har sharddan offset+limit olib, merge kerak).
// KEYSET (cursor) pagination -> har sharddan "cursor'dan keyingi N" ni ol, merge-sort qil
async page(cursor: string, limit: number) {
const perShard = await Promise.all(
this.shards.all().map((db) =>
db.order.findMany({
where: { createdAt: { lt: cursor } },
orderBy: { createdAt: 'desc' },
take: limit, // har sharddan limit ta
}),
),
);
return mergeSort(perShard.flat()).slice(0, limit); // birlashtirib, kesib ber
}
Eng yaxshi cross-shard so‘rov — umuman bo‘lmagani. Bog‘liq jadvallarni bir xil shard key bilan shardla (orders + order_items, ikkalasi user_id bo‘yicha) — shunda ular bir shardda bo‘lib, lokal JOIN ishlaydi. Kichik o‘zgarmas jadvallarni esa hamma shardga nusxala (reference tables).
Global secondary index
non-key lookupShard key bitta atribut (masalan user_id) bo‘yicha. Lekin hayotda boshqa atributlar bo‘yicha ham qidirasan (email, telefon). Bu — sharding’ning yashirin muammosi.
users jadvali user_id bo‘yicha shardlangan. "Email bo‘yicha foydalanuvchini top" so‘rovi — email shard key emas, demak qaysi shardda ekanini bilmaysan, hamma shardga so‘rov yuborasan (scatter-gather). Login kabi tez-tez so‘rov uchun bu halokat.
Global secondary index
Yechim — alternativ kalit bo‘yicha shardlangan alohida index jadval: u alternativ kalitdan asl shard key’ga (email → user_id) yo‘l ko‘rsatadi. Ikki "hop" bo‘ladi, lekin scatter-gather o‘rniga aniq nishon:
// MUAMMO: orders user_id bo'yicha shardlangan. Lekin EMAIL bo'yicha qidirish kerak.
// email shard key emas -> har so'rov hamma shardga borishi kerak (scatter) ✕
// YECHIM: global secondary index — EMAIL bo'yicha shardlangan alohida jadval,
// u user_id'ni qaytaradi. Keyin asl shardga boramiz (2 hop, lekin scatter yo'q).
async findUserByEmail(email: string) {
const idxShard = this.shardForEmail(email); // index email bo'yicha shardlangan
const row = await idxShard.emailIndex.findUnique({ where: { email } });
if (!row) return null; // 1-hop: email -> userId
const dataShard = this.shardForUser(row.userId);
return dataShard.user.findUnique({ where: { id: row.userId } }); // 2-hop
}
Index jadval asosiy bilan sinxron yangilanishi kerak (aks holda eskirib qoladi) — odatda outbox/event bilan (3-mavzu), pirovard izchillik. Muqobil: ixtiyoriy/murakkab qidiruvlar uchun butun ma’lumotni Elasticsearchga ko‘chirish (CQRS o‘qish modeli, 9-mavzu) — bu yerda sharding’ni o‘zi hal qiladi.
Hot shard / hot key splitting
split · sub-shardYaxshi shard key tanlasang ham, hayot hotspot keltiradi: "yulduz" foydalanuvchi (millionlab follower), viral mahsulot, qora juma. Bitta shard cho‘kadi, qolganlar bo‘sh.
Aniqlash → yumshatish
Avval aniqla: per-shard va per-key metrikalar (QPS, CPU, ulanish). Keyin yumshatish strategiyalari:
- Hot shardni bo‘lish (split): bucket dizayni tufayli oson — hot sharddagi bucket’larning bir qismini bo‘sh/yangi shardga ko‘chirasan (2-bo‘limdagi online resharding).
- Hot key’ni sub-shardlash: bitta juda issiq kalitni yanada bo‘lish —
user_ido‘rnigauser_id + sub_bucket(yulduz foydalanuvchining ma’lumoti bir necha shardga tarqaladi). - Kesh oldiga qo‘yish: hot key’ni Redis/L1’ga (4-mavzu) — Redis’ga ham bormaslik.
- Hot shardga replika: o‘qish yukini replikalarga (7-mavzu) taqsimlash.
Bucket’lar bo‘lmasa, hot shardni bo‘lish — og‘riqli qayta-hash. Bucket’lar bilan esa shunchaki ba’zi bucket’larni boshqa shardga ko‘chirasan. Shuning uchun 1-bo‘limdagi dizayn shunchalik muhim.
Global unique, Snowflake, migration
snowflake · version skewSharding faqat so‘rov emas — butun operatsiya o‘zgaradi. Senior bularni oldindan rejalashtiradi:
Global unique va foreign key
Global noyoblik (email unique) — bitta shardda tekshirib bo‘lmaydi. Yechim: 5-bo‘limdagi global indexni unique sifatida ishlatish yoki app darajasida tekshirish. Foreign key shardlar aro ishlamaydi — bog‘liqlikni app majburlaydi yoki colocation bilan bir shardda saqlaysan.
ID generatsiyasi — Snowflake
Auto-increment shardlarda konflikt beradi. UUID — global noyob, lekin tasodifiy (indeks fragmentatsiyasi, tartibsiz). Snowflake ID — global noyob va vaqt bo‘yicha tartibli (yaxshi indeks lokalligi):
// SNOWFLAKE ID (64-bit): koordinatsiyasiz global noyob + vaqt bo'yicha SARALANADI
// [ 41 bit: timestamp ][ 10 bit: worker/machine ][ 12 bit: sequence ]
// -> ~69 yil -> 1024 ta worker -> ms ichida 4096 ta ID
// auto-increment'dan farqli: shardlar koordinatsiya qilmaydi, lekin ID'lar tartibli.
Schema migration va backup
Migration: har o‘zgarishni N ta shardga qo‘llash kerak; rollout davomida shardlar har xil versiyada bo‘lishi mumkin (version skew) — backward-compatible o‘zgartirishlar shart. Backup: har shard alohida; lekin barcha shardlar bo‘yicha bir paytdagi izchil snapshot olish qiyin (taqsimlangan vaqt muammosi).
Har shard bo‘yicha alohida kuzat: hajm, QPS, kechikish, ulanishlar, disk. Notekislik (bir shard boshqalardan katta/band) — resharding yoki hot shard signali.
Yig‘ma ko‘rinish va senior cheklist
Senior sharding arxitekturasi — yig‘ma ko‘rinish
Yetuk shardlangan tizim odatda shu qismlardan iborat:
| Qism | Nima beradi | Bo‘lim |
|---|---|---|
| Virtual buckets (key→bucket→shard) | Oson resharding | 1 |
| Online resharding (dual-write) | Downtime’siz o‘sish | 2 |
| Colocation + reference tables | Lokal JOIN, cross-shard kam | 4 |
| Global secondary index | Non-key bo‘yicha qidiruv | 5 |
| Snowflake ID | Global noyob, tartibli | 7 |
| Vitess / Citus (imkon bo‘lsa) | Yuqoridagilarni tayyor beradi | 3 |
Senior tayyorlik cheklisti
| Savol / amaliyot | Bo‘lim |
|---|---|
| hash%N o‘rniga virtual bucket / consistent hashing ishlataman | 1 |
| Downtime’siz resharding (backfill + dual-write + cutover) o‘tkaza olaman | 2 |
| DIY o‘rniga Vitess/Citus’ni baholayman; Citus’da distribute/colocate qila olaman | 3 |
| Cross-shard agregatsiya/pagination chegaralarini bilaman; colocation bilan qochaman | 4 |
| Non-shard-key qidiruv uchun global secondary index yoki ES quraman | 5 |
| Hot shard/key’ni aniqlab, split/sub-shard/cache bilan yumshataman | 6 |
| Snowflake ID, cross-shard FK/unique, N-shard migration’ni boshqaraman | 7 |
Bu 6-mavzuning Deep/Senior qatlami edi — Core bilan birga, sharding endi to‘liq.
Keyingi mavzu: “7-mavzu: Database Replication” — sharding’ning tabiiy jufti (nusxalash, HA, o‘qish masshtabi). Core’dan boshlaymiz.