Tizim dizayni kursi · 6-mavzu · DEEP / SENIOR
TuzdiDinMuhammad

Database Sharding — Senior chuqurlik

Bu — 6-mavzuning 2-qatlami. Core darslikni o‘qib bo‘lgan deb hisoblanadi. Bu yerda eng qiyin narsalar: virtual buckets, downtime’siz resharding, Vitess/Citus, distributed query, global secondary index va hot shard splitting — junior’dan seniorgacha.

Daraja: SeniorMavzular: vBuckets · online resharding · Citus · GSIOld shart: 6-mavzu (Core)

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.

01
Deep · Buckets

Consistent hashing va virtual buckets

vBuckets · ring
🪣

Senior 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)];
}
key hash bucket (0..1023)O‘ZGARMAS map bucketMapresharding = shuni o‘zgartirish shard
Ikki bosqich: key→bucket (o‘zgarmas), bucket→shard (resharding shu yerda)
Nega bu kuchli

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.

02
Deep · Resharding

Downtime‘siz online resharding

backfill · dual-write · cutover
🔀

Eng qiyin operatsiya: tizimni to‘xtatmasdan ma’lumotni qayta taqsimlash. Bu — online schema migration’ga o‘xshash, nozik raqs.

Muammo

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. Yangi shard+ bucket belgila 2. Backfill(eski → yangi) 3. Dual-write(ikkalasiga) 4. Verify+ cutover 5. Eski'nitozala
Backfill → dual-write → verify → cutover: uzilishsiz ko‘chirish

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.

Tayyor vositalar buni qiladi

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.

03
Deep · Tools

Vitess va Citus

vtgate · distributed table
🧰

Senior 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‘rovQo‘lda yozasanAvtomatik
ReshardingJuda qiyin, xavfliTayyor (VReplication / rebalance)
NazoratTo‘liqCheklangan, lekin yetarli
MurakkablikYuqori (sen ko‘tarasan)O‘rganish + operatsiya
Qaror

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.

04
Deep · Query

Distributed query va colocation

pushdown · keyset · colocation
🧮

Bir 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

Nega OFFSET yomon

"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
}
Colocation — cross-shard’dan qochish

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).

05
Deep · Index

Global secondary index

non-key lookup
🗂️

Shard key bitta atribut (masalan user_id) bo‘yicha. Lekin hayotda boshqa atributlar bo‘yicha ham qidirasan (email, telefon). Bu — sharding’ning yashirin muammosi.

Muammo

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
}
email so‘rov 1-hop email_indexemail bo‘yicha shardlangan 2-hop users sharduser_id bo‘yicha
email → (index) → user_id → (asl shard) — scatter yo‘q, 2 ta aniq hop
Izchillik va muqobil

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.

06
Deep · Hotspot

Hot shard / hot key splitting

split · sub-shard
🔥

Yaxshi 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_id o‘rniga user_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.
Hot shard 🔥b: 0..340 bucket’larni ko‘chir Shard (eski)b: 0..170 Yangi shardb: 171..340
Bucket dizayni hot shardni bo‘lishni osonlashtiradi
Bucket dizayni — yana g‘alaba

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.

07
Deep · Operations

Global unique, Snowflake, migration

snowflake · version skew

Sharding 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

Operatsion qiyinchiliklar

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).

Monitoring

Har shard bo‘yicha alohida kuzat: hajm, QPS, kechikish, ulanishlar, disk. Notekislik (bir shard boshqalardan katta/band) — resharding yoki hot shard signali.

08
Deep · Arxitektura

Yig‘ma ko‘rinish va senior cheklist

Senior sharding arxitekturasi — yig‘ma ko‘rinish

Yetuk shardlangan tizim odatda shu qismlardan iborat:

QismNima beradiBo‘lim
Virtual buckets (key→bucket→shard)Oson resharding1
Online resharding (dual-write)Downtime’siz o‘sish2
Colocation + reference tablesLokal JOIN, cross-shard kam4
Global secondary indexNon-key bo‘yicha qidiruv5
Snowflake IDGlobal noyob, tartibli7
Vitess / Citus (imkon bo‘lsa)Yuqoridagilarni tayyor beradi3

Senior tayyorlik cheklisti

Savol / amaliyotBo‘lim
hash%N o‘rniga virtual bucket / consistent hashing ishlataman1
Downtime’siz resharding (backfill + dual-write + cutover) o‘tkaza olaman2
DIY o‘rniga Vitess/Citus’ni baholayman; Citus’da distribute/colocate qila olaman3
Cross-shard agregatsiya/pagination chegaralarini bilaman; colocation bilan qochaman4
Non-shard-key qidiruv uchun global secondary index yoki ES quraman5
Hot shard/key’ni aniqlab, split/sub-shard/cache bilan yumshataman6
Snowflake ID, cross-shard FK/unique, N-shard migration’ni boshqaraman7

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.