IlmHamroh
JavaScript Full-stack/31-qism. Prodakshn backend va AI integratsiya6/34-dars21 daqiqa
Mundarija (32)

Sharding va taqsimlangan SQL: ma'lumotni serverlarga bo'lish

Qisqacha: Sharding (bo'laklash) — ma'lumotni bir nechta mustaqil bazaga (shardga) bo'lish: har foydalanuvchining vazifalari faqat bitta shardda turadi. Yozish va hajm serverlar orasida bo'linadi, lekin narxi katta: kalitsiz so'rov hamma shardga boradi, noyoblik faqat shard ichida ishlaydi, yangi shard qo'shilganda ma'lumot ko'chadi. Kanon o'lchovida 2 → 3 shardga o'tishda oddiy hash % N kalitlarning 66.7 % ini ko'chirdi, consistent hashing — 33.7 % ini. Xulosa: vazifalar uchun sharding hozircha kerak emas — bu oxirgi chora.

Bu darsda

  • Sharding partitioning va replikadan nimasi bilan farq qilishini tushuntirasiz.
  • Shard kalitini tanlash mezonlarini bilasiz.
  • Ilova darajasidagi sharding'ni kanon tajribasi orqali ko'rasiz: marshrutlash va scatter-gather.
  • hash % N va consistent hashing'ni brauzerda modellab, ko'chadigan kalitlarni sanaysiz.
  • Shardlararo cheklovlar (noyoblik, ID, tranzaksiya) va taqsimlangan SQL bazalarini bilasiz.
  • "Sharding'ga hali ertami?" degan savolga o'lchov bilan javob berasiz.

Oldin bilishingiz kerak: Partitioning, Replikatsiya va read replica'lar, Hash table ichidan, crypto 1: tasodif, hash va HMAC.

Oldingi darsda katta jadvalni bitta server ichida bo'limlarga bo'ldik. Endi bir qadam nariga o'tamiz: bo'limlar boshqa-boshqa serverlarda tursa nima bo'ladi?

1. Nega bu kerak?

Tasavvur qiling: vazifalar-api juda mashhur bo'ldi. 50 mln foydalanuvchi, sekundiga 30 000 yozuv. Siz hamma narsani qildingiz: indekslar, ulanish hovuzi, o'qish uchun replikalar, katta jadvallar bo'limlarga bo'lingan. Lekin baribir:

  • Yozish bitta primary'da. Replika yozishni tezlashtirmaydi — har yozuv baribir primary'dan o'tadi (Replikatsiya darsidagi xulosa).
  • Hajm bitta serverning diskiga, xotirasiga sig'maydi.
  • Eng katta serverni sotib oldingiz (vertikal masshtablash) — keyingi qadam yo'q.

Shunda ma'lumotni bir nechta serverga bo'lish kerak bo'ladi. O'xshatish: katta shahar pochtasi. Bitta bo'lim butun shahar xatlarini ko'tara olmaydi. Shahar tumanlarga bo'linadi, har tumanning o'z pochta bo'limi bor. Xat qaysi bo'limga borishini manzildagi indeks hal qiladi. Lekin "shahardagi hamma xatlarni sanang" degan topshiriq endi har bo'limni aylanib chiqishni talab qiladi.

2. Sharding asoslari

2.1 Atamalar

  • Sharding (bo'laklash) — ma'lumotni bir nechta mustaqil bazaga bo'lish. Bu so'zni Playwright II: auth, tarmoq, trace va CI darsidan eslarsiz — u yerda e2e testlar mashinalarga bo'laklangan edi, g'oya bir xil.
  • Shard — shu bo'laklardan biri: o'z serveri, o'z diski, o'z indekslari bor to'liq Postgres bazasi. Inglizcha so'zning o'zi "siniq parcha" degani: butun idish sinib, har parcha alohida yotadi.
  • Shard kaliti (shard key) — qator qaysi shardga tegishli ekanini hal qiladigan ustun. vazifalar uchun tabiiy tanlov — egasi_id.
  • Marshrutlash (routing) — so'rovni to'g'ri shardga yuborish. Buni kim qiladi: ilova o'zi, alohida proksi yoki bazaning o'zi.

2.2 Partitioning, replika va sharding

Partitioning Replika Sharding
Nima bo'linadi jadval, bitta server ichida hech nima — to'liq nusxa ma'lumot, serverlar orasida
Yozishni tezlatadimi qisman yo'q ha
Hajm chegarasi bitta disk bitta disk ko'p disk
Murakkablik past o'rta yuqori

Partitioning — bitta bazaning ichki ishi: so'rov, tranzaksiya, cheklovlar hamon bitta Postgres'da. Sharding'da esa har shard alohida baza. Ular bir-birini bilmaydi.

2.3 Shard kalitini tanlash

Yaxshi shard kaliti uch shartga javob beradi:

  1. Ko'p so'rovda bor. "Azizning vazifalari" — egasi_id bilan; so'rov bitta shardga boradi.
  2. Tekis taqsimlanadi. Bitta qiymat butun yukni olmasin. Agar shard kaliti viloyat bo'lsa, Toshkent shardi qolganlaridan bir necha baravar og'ir bo'ladi — bu issiq nuqta (hot spot).
  3. O'zgarmaydi. Kalit o'zgarsa, qatorni boshqa shardga ko'chirish kerak.

egasi_id uchalasiga javob beradi: foydalanuvchi faqat o'z vazifalarini ko'radi, foydalanuvchilar ko'p, ID o'zgarmaydi. Yomon tanlov — yaratilgan_vaqt: hamma yangi yozuv oxirgi shardga tushadi, qolganlari bo'sh turadi.

Tekshirib ko'ring: Telegram bot uchun xabarlar jadvali. Shard kaliti chat_id mi yoki xabar_id mi?

Javob

chat_id. Eng ko'p so'rov — "shu chatning oxirgi 50 xabari": chat_id bilan bu bitta shard. xabar_id bo'yicha bo'linsa, bitta chat xabarlari hamma shardga sochiladi va har chat ochilishi scatter-gather bo'ladi. Xavfi: juda katta guruh chati issiq nuqtaga aylanishi mumkin — buni alohida hal qilishadi (masalan, katta chatni alohida shardga).

3. Ilova darajasida sharding: kanon tajribasi

3.1 Tajriba sxemasi

Kanon eng oddiy variantni sinadi: sharding'ni ilovaning o'zi qiladi. Ikki shard — shard_a va shard_b. Muhim cheklov: ikkalasi ham bitta Postgres serveri ichidagi ikki alohida baza. Haqiqiy alohida serverlar emas — tarmoq kechikishi yo'q. Bu tajriba tezlikni emas, mantiqni va so'rovlar sonini ko'rsatadi.

flowchart LR
  I["Ilova"] --> M{"sha256(egasi_id) % 2"}
  M -->|"0"| A[("shard_a")]
  M -->|"1"| B[("shard_b")]
  I -.->|"kalitsiz so'rov"| A
  I -.->|"kalitsiz so'rov"| B

Uzluksiz strelkalar — oddiy yo'l: ilova egasi_id xeshini hisoblaydi va bitta shardni tanlaydi. Uzuq strelkalar — shard kaliti yo'q so'rov: ilova ikkala shardga boradi va natijalarni o'zi birlashtiradi.

3.2 Kod

Kanon skriptining yadrosi (qatorlar telefon uchun bo'lindi, CREATE DATABASE qismi olib tashlandi):

js
import pg from "pg";
import { createHash } from "node:crypto";

const SHARDLAR = ["shard_a", "shard_b"];
const pool = Object.fromEntries(SHARDLAR.map((b) => [b,
  new pg.Pool({
    connectionString: `postgres://postgres@127.0.0.1:5496/${b}`,
    max: 4,
  })]));
const h32 = (s) =>
  createHash("sha256").update(String(s)).digest().readUInt32BE(0);
const shardMod = (egasi, n = SHARDLAR.length) =>
  SHARDLAR[h32(egasi) % n];

for (const p of Object.values(pool)) {
  await p.query(`CREATE TABLE vazifalar (
    id bigint GENERATED ALWAYS AS IDENTITY, egasi_id bigint NOT NULL,
    matn text NOT NULL, PRIMARY KEY (id), UNIQUE (egasi_id, matn))`);
}
for (let e = 1; e <= 200; e++) {
  const p = pool[shardMod(e)];
  await p.query(
    "INSERT INTO vazifalar (egasi_id, matn) " +
      "SELECT $1, 'vazifa ' || g FROM generate_series(1, 50) g",
    [e],
  );
}

h32 — SHA-256 xeshning birinchi 4 baytini butun songa aylantiradi. shardMod — shu sonning shardlar soniga bo'linishidan qolgan qoldiq. Har egasining 50 ta vazifasi faqat uning shardiga yoziladi. Har shardda jadval bir xil: shard — to'liq baza, faqat ma'lumotning bir qismi bilan. Bo'lingan nusxani biz o'z PostgreSQL 17.6 bazamizda ikki baza bilan ishga tushirdik — taqsimot kanondagidek chiqdi: 4 450 va 5 550 qator.

Keyin ikki xil so'rov o'lchandi — shard kaliti bilan va kalitsiz:

js
// shard kaliti bor: bitta shardga
await pool[shardMod(e)].query(
  "SELECT count(*) FROM vazifalar WHERE egasi_id = $1", [e]);

// kalitsiz (scatter-gather): hamma shardga parallel
await Promise.all(SHARDLAR.map((b) => pool[b].query(
  "SELECT count(*) FROM vazifalar WHERE matn = 'vazifa 7'")));

Scatter-gather (sochib-yig'ish) — so'rovni hamma shardga yuborish va javoblarni ilovada birlashtirish. Har biri 200 marta takrorlandi.

3.3 Natija

text
1) 200 egasi × 50 vazifa yozildi (1528 ms); taqsimot: shard_a=4450, shard_b=5550
   bitta egasi so'rovi (shard kaliti bor) × 200: 1.54 ms/so'rov, 1 shard
   shard kalitisiz so'rov (scatter-gather, 2 shard parallel) × 200: 2.21 ms/so'rov — har so'rov 2 ta bazaga

Ikki xulosa. Birinchisi — taqsimot: 4 450 ga 5 550, ya'ni 89 va 111 egasi. Xesh "tekis" deyiladi, lekin bu katta sonlarda. 200 egasida 45 % ga 55 % bo'ldi. Ikkinchisi — kalitsiz so'rov har safar ikkala bazaga bordi. 2 ta shardda bu sezilmaydi. 20 ta shardda har bunday so'rov 20 ta bazani band qiladi, eng sekin shard javobini kutadi.

3.4 Taqsimotni brauzerda takrorlaymiz

Kanon node:crypto ishlatgan. Brauzerda xuddi shu SHA-256 — crypto.subtle.digest (Web Crypto g'oyasi). Kodni ishga tushiring — natija kanon bilan bir xil chiqishi kerak:

js
async function h32(s) {
  const bayt = new TextEncoder().encode(String(s));
  const xesh = await crypto.subtle.digest("SHA-256", bayt);
  return new DataView(xesh).getUint32(0);
}

const SHARDLAR = ["shard_a", "shard_b"];
const sanoq = { shard_a: 0, shard_b: 0 };
for (let egasi = 1; egasi <= 200; egasi++) {
  const shard = SHARDLAR[(await h32(egasi)) % SHARDLAR.length];
  sanoq[shard]++;
}
console.log(sanoq);
console.log("vazifalar:", sanoq.shard_a * 50, sanoq.shard_b * 50);

Konsolda:

text
{ shard_a: 89, shard_b: 111 }
vazifalar: 4450 5550

getUint32(0) — readUInt32BE(0) ning brauzerdagi juftligi: dastlabki 4 baytni katta tartibda (big-endian) o'qiydi. Xesh bir xil, demak taqsimot ham bir xil: 4 450 va 5 550.

3.5 Natijani halol o'qish

1.54 va 2.21 ms ni solishtirmang. Ikki so'rov boshqa-boshqa ish qiladi. Birinchisi egasi_id bo'yicha qidiradi — UNIQUE (egasi_id, matn) indeksi bor. Ikkinchisi matn = 'vazifa 7' bo'yicha — faqat matn ga indeks yo'q, har shardda butun jadval o'qiladi. Ya'ni vaqt farqining bir qismi — scatter-gather'dan emas, indeks yo'qligidan. To'g'ri solishtirish uchun bir xil so'rovni bir va ikki shardga yuborish kerak edi.

Tarmoq yo'q. Ikkala "shard" bitta serverda. Haqiqiy shardlar alohida mashinalarda bo'lsa, har qo'shimcha shard qo'shimcha tarmoq aylanishi (round trip) degani.

Tajribadan oladigan aniq fakt — so'rovlar soni: kalit bilan 1 ta baza, kalitsiz 2 ta. Bu raqam shardlar soni bilan birga o'sadi.

4. Shard qo'shish: hash % N va consistent hashing

4.1 hash % N muammosi

Ikki shard to'ldi, uchinchisini qo'shamiz. shardMod(egasi, 3) endi boshqa javob beradi. Qancha egasi boshqa shardga ko'chishi kerak?

js
async function h32(s) {
  const bayt = new TextEncoder().encode(String(s));
  const xesh = await crypto.subtle.digest("SHA-256", bayt);
  return new DataView(xesh).getUint32(0);
}

const N = 10000;
let kochdi = 0;
for (let egasi = 1; egasi <= N; egasi++) {
  const h = await h32(egasi);
  if (h % 2 !== h % 3) kochdi++;
}
console.log(`hash % N, 2 → 3: ${(100 * kochdi / N).toFixed(1)} %`);

Konsolda:

text
hash % N, 2 → 3: 66.7 %

Kanon ham aynan shu raqamni oldi: 66.7 %. Uchta egasidan ikkitasining ma'lumoti boshqa serverga ko'chishi kerak. Sabab matematik: xesh 2 ga va 3 ga bo'linganda bir xil qoldiq berishi — taxminan uchdan bir holatda. Ko'chish paytida ilova qaysi egasi qayerda ekanini bilmay qoladi — bu eng xavfli payt.

4.2 Consistent hashing

Consistent hashing (izchil xeshlash) — shardlarni va kalitlarni bitta "halqaga" joylash usuli. Halqa — 0 dan 2³² gacha sonlar, oxiri boshiga ulangan. Har shard halqada bir nechta nuqtani egallaydi. Kalit xeshidan boshlab soat yo'nalishida yuramiz — birinchi uchragan shard nuqtasi kalitning egasi.

flowchart LR
  K["egasi 42: xesh = 1.9 mlrd"] --> Q["halqada keyingi nuqta"]
  Q --> A["a#17: 2.0 mlrd"]
  A --> S[("shard a")]
  Y["yangi shard c"] -.->|"faqat o'z nuqtalari oldidagi kalitlarni oladi"| Q

Yangi shard qo'shilsa, u halqaga o'z nuqtalarini qo'yadi. Faqat shu nuqtalardan oldingi kalitlar unga o'tadi, qolganlari joyida qoladi. Bitta shard halqada faqat bitta nuqtada bo'lsa, oraliqlar notekis chiqadi. Shuning uchun har shard ko'p virtual tugun (virtual node) bilan qo'yiladi: a#0, a#1, … a#99.

Kanon algoritmi — 100 virtual tugun bilan:

js
async function h32(s) {
  const bayt = new TextEncoder().encode(String(s));
  const xesh = await crypto.subtle.digest("SHA-256", bayt);
  return new DataView(xesh).getUint32(0);
}

async function halqa(tugunlar, v = 100) {
  const nuqtalar = [];
  for (const n of tugunlar) {
    for (let i = 0; i < v; i++) {
      nuqtalar.push({ n, h: await h32(`${n}#${i}`) });
    }
  }
  return nuqtalar.sort((a, b) => a.h - b.h);
}

function egasi(halqaNuqtalari, h) {
  const nuqta = halqaNuqtalari.find((x) => x.h >= h);
  return (nuqta ?? halqaNuqtalari[0]).n;
}

const r2 = await halqa(["a", "b"]);
const r3 = await halqa(["a", "b", "c"]);
let kochdi = 0;
for (let e = 1; e <= 10000; e++) {
  const h = await h32(e);
  if (egasi(r2, h) !== egasi(r3, h)) kochdi++;
}
console.log(`consistent hashing: ${(kochdi / 100).toFixed(1)} %`);

Konsolda:

text
consistent hashing: 33.7 %

Kanon natijasi bilan aynan bir xil: 33.7 %. Ideal — 33.3 %: uchta shard bo'lsa, yangisi har biridan teng ulush olishi kerak, ya'ni hamma kalitlarning uchdan biri. Consistent hashing idealga juda yaqin keldi va hash % N dan ikki baravar kam ko'chirdi.

nuqta ?? halqaNuqtalari[0] qatori — halqaning "ulanishi": xesh eng katta nuqtadan ham katta bo'lsa, birinchi nuqtaga qaytamiz.

2 → 3 shard: ko'chishi kerak bo'lgan kalitlar ulushi
  • hash % N66,7 %
  • consistent hashing (100 virtual tugun)33,7 %
  • ideal33,3 %

Manba: IlmHamroh kanon o'lchovi, 2026-11-03: 10 000 egasi, sha256 xeshining dastlabki 4 bayti; brauzer modelida biz ham shu natijani oldik (Node 24.21.0)

4.3 Virtual tugunlar va muvozanat

Virtual tugunlar soni muvozanatga qanday ta'sir qiladi? 10 000 egasini uchta shardga taqsimlab ko'ramiz:

js
async function h32(s) {
  const bayt = new TextEncoder().encode(String(s));
  const xesh = await crypto.subtle.digest("SHA-256", bayt);
  return new DataView(xesh).getUint32(0);
}

async function halqa(tugunlar, v) {
  const nuqtalar = [];
  for (const n of tugunlar) {
    for (let i = 0; i < v; i++) {
      nuqtalar.push({ n, h: await h32(`${n}#${i}`) });
    }
  }
  return nuqtalar.sort((a, b) => a.h - b.h);
}

const xeshlar = [];
for (let e = 1; e <= 10000; e++) xeshlar.push(await h32(e));
for (const v of [1, 10, 100]) {
  const r = await halqa(["a", "b", "c"], v);
  const sanoq = { a: 0, b: 0, c: 0 };
  for (const h of xeshlar) {
    sanoq[(r.find((x) => x.h >= h) ?? r[0]).n]++;
  }
  console.log(`v=${v}:`, sanoq);
}

Konsolda:

text
v=1: { a: 5585, b: 4050, c: 365 }
v=10: { a: 3492, b: 3076, c: 3432 }
v=100: { a: 3850, b: 2778, c: 3372 }

Bitta nuqta bilan (v=1) uchinchi shard deyarli bo'sh qoldi: 365 ta egasi, 4 % ga yaqin. 10 va 100 virtual tugun bilan taqsimot ancha tekislandi. Lekin e'tibor bering: bu yugurishda 10 tugun 100 tadan ham tekisroq chiqdi. Virtual tugunlar o'rtacha muvozanatni yaxshilaydi, har aniq holatda kafolat bermaydi. Real tizimlarda shuning uchun shard yukini kuzatib, kerak bo'lsa qo'lda qayta muvozanatlashadi.

Tekshirib ko'ring: Consistent hashing bilan ham uchinchi shard qo'shilganda 33.7 % kalit ko'chadi. Bu qayerdan "yutuq"?

Javob

Uchinchi shard hamma ma'lumotning uchdan birini olishi kerak — aks holda u bo'sh turadi. Shuning uchun 33.3 % — ko'chirishning eng kam chegarasi. Undan qochib bo'lmaydi. hash % N esa 66.7 % ko'chiradi: kerakli uchdan birdan tashqari, eski ikki shard o'rtasida ham keraksiz almashinuv bo'ladi (a dan b ga, b dan a ga). Consistent hashing faqat keraklisini ko'chiradi.

5. Shardlararo muammolar

5.1 Noyoblik faqat shard ichida

Kanon uchinchi sinovda marshrut xatosini modelladi: egasi 1 ning vazifasini ('vazifa 1') noto'g'ri shardga yozdi.

text
3) UNIQUE (egasi_id, matn) noto'g'ri shardga yozuvda: global nusxalar soni = 2 (cheklov faqat shard ichida — marshrut xatosi jim o'tadi)

Ikkala shardda ham UNIQUE (egasi_id, matn) bor. Lekin har shard faqat o'zini tekshiradi. Natija: bir xil vazifa ikki nusxada, hech qanday xato yo'q. Partitioning'da Postgres buni hech bo'lmasa xato bilan rad etardi (Partitioning darsidagi must include all partitioning columns). Sharding'da baza hatto bilmaydi. Tashqi kalit (FK) ham xuddi shunday: boshqa sharddagi qatorga FK qo'yib bo'lmaydi.

5.2 ID to'qnashuvi

Har shardda GENERATED ALWAYS AS IDENTITY — har biri o'z hisoblagichi bilan. Ikki "shard"ni bitta bazadagi ikki sxema bilan modellaymiz (biz ishga tushirdik, PostgreSQL 17.6):

sql
CREATE SCHEMA shard_a;
CREATE SCHEMA shard_b;
CREATE TABLE shard_a.vazifalar (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  egasi_id bigint NOT NULL, matn text NOT NULL);
CREATE TABLE shard_b.vazifalar (LIKE shard_a.vazifalar
  INCLUDING ALL);

INSERT INTO shard_a.vazifalar (egasi_id, matn)
  VALUES (2, 'Non olish');
INSERT INTO shard_b.vazifalar (egasi_id, matn)
  VALUES (5, 'Dars tayyorlash');
SELECT 'shard_a' AS shard, id, egasi_id FROM shard_a.vazifalar
UNION ALL
SELECT 'shard_b', id, egasi_id FROM shard_b.vazifalar;
text
CREATE SCHEMA
CREATE SCHEMA
CREATE TABLE
CREATE TABLE
INSERT 0 1
INSERT 0 1
  shard  | id | egasi_id
---------+----+----------
 shard_a |  1 |        2
 shard_b |  1 |        5
(2 rows)

Ikki xil vazifa, ikkalasining ham id si 1. /v1/vazifalar/1 qaysi biri? Yechimlar uchta. ID'ga shard raqamini qo'shish yoki har shardga alohida oraliq berish. Yoki markaziy hisoblagichsiz noyob ID: UUID (ayniqsa vaqt tartibli UUID v7). Yana bir yo'l — Twitter'ning Snowflake usuli: vaqt + mashina raqami + tartib raqami. Primary key tanlovi darsidagi UUID va son kalit bahsi sharding'da yangi ma'no oladi.

5.3 Shardlararo tranzaksiya

Aziz o'z vazifasini Malikaga o'tkazmoqchi. Aziz shard_a da, Malika shard_b da. BEGIN ... COMMIT faqat bitta baza ichida ishlaydi. Ikki bazada bir vaqtda yoki ikkalasida, yoki hech birida o'zgarish bo'lishi uchun ikki fazali commit (two-phase commit, 2PC) kerak. Postgres'da buning uchun PREPARE TRANSACTION va COMMIT PREPARED bor. Hujjat ularni oddiy ilovada ishlatishni tavsiya etmaydi — tranzaksiya menejeri uchun mo'ljallangan (sinalmagan, 2026-11). Amalda shardlararo amallar ko'pincha Transactional outbox va kompensatsiya bilan qilinadi: biri muvaffaqiyatli, ikkinchisi yiqilsa — birinchisini qaytaradigan amal.

5.4 Hisobotlar

"Bu oy jami nechta vazifa bajarildi?" — hamma shardga so'rov, keyin ilovada yig'indi. count va sum ni oddiy qo'shish mumkin. O'rtacha (avg) esa yo'q: har shard sum va count ni alohida qaytarishi kerak. Mediana va DISTINCT yanada qiyin. Shuning uchun katta tizimlarda hisobotlar alohida analitik bazada qilinadi: shardlardan ma'lumot u yerga CDC bilan oqib keladi.

6. Taqsimlangan SQL bazalari

Ilova darajasidagi sharding'da hamma og'irlik ilovada: marshrutlash, scatter-gather, ko'chirish. Buni bazaning o'ziga yuklaydigan tizimlar bor. Quyidagilar rasmiy hujjat bo'yicha (sinalmagan, 2026-11):

Tizim G'oya
Citus Postgres kengaytmasi: jadval "taqsimlangan" deb e'lon qilinadi, koordinator so'rovni shardlarga bo'lib yuboradi
CockroachDB Postgres protokoliga mos, ma'lumotni avtomatik oraliqlarga bo'lib tarqatadi, taqsimlangan tranzaksiyalar
YugabyteDB Postgres so'rov qatlami (PostgreSQL 15 kodi asosida) + taqsimlangan saqlash
Vitess MySQL uchun sharding qatlami (YouTube'da paydo bo'lgan)

Citus'da jadval bitta funksiya bilan taqsimlanadi: SELECT create_distributed_table('vazifalar', 'egasi_id');. Shard kalitini tanlash qoidasi o'zgarmaydi — bu tizimlar ham kalitsiz so'rovni hamma tugunga yuboradi. CockroachDB va YugabyteDB "Postgres'ga mos" deyiladi, lekin hammasi ham emas: ba'zi kengaytmalar va xususiyatlar yo'q. Ko'chishdan oldin o'z sxemangizni sinab ko'ring. Kanon ulardan hech birini sinamadi: har biri alohida klaster va sozlash talab qiladi.

7. Sharding'ga hali ertami?

Sharding — qaytarib bo'lmaydigan murakkablik. Har so'rov, har migratsiya, har hisobot endi "qaysi shardda?" degan savol bilan yoziladi. Shuning uchun undan oldin hamma arzon yo'llar sinaladi:

Qadam Nima beradi Kursda
Indeks va so'rovni tuzatish ko'pincha 10–100 baravar EXPLAIN ANALYZE
Ulanish hovuzi ko'p ulanish Connection pooling
Kesh o'qish yuki kamayadi Caching naqshlari
Read replica o'qish bo'linadi Replikatsiya
Partitioning katta jadval, tozalash Partitioning
Kattaroq server hamma narsa —
Sharding yozish va hajm bo'linadi shu dars

Kanon xulosasi: vazifalar uchun sharding kerak emas. Bitta yaxshi sozlangan Postgres va replika millionlab foydalanuvchini ko'taradi. Vazifalar ro'yxati — kichik yozuvlar, foydalanuvchi faqat o'zinikini ko'radi. Sharding kerak bo'lsa, shard kaliti tayyor — egasi_id. 28-qismda qo'yilgan RLS ham har so'rovni aynan shu ustun bilan cheklaydi.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Bitta shard qizib ketgan, qolganlari bo'sh shard kaliti notekis (viloyat, vaqt) kalitni o'zgartirish, katta qiymatni bo'lish
Har so'rov hamma shardga boradi so'rovlarda shard kaliti yo'q kalitni so'rov yo'liga qo'shish
Bir xil yozuv ikki shardda marshrut xatosi, cheklov faqat shard ichida marshrutni bitta joyda saqlash, global tekshiruv
Ikki xil yozuvning ID'si bir xil har shardda alohida hisoblagich UUID v7, Snowflake, oraliqlar
Yangi shard qo'shganda hamma narsa ko'chdi hash % N consistent hashing yoki oldindan ko'p mantiqiy shard

Oxirgi qatordagi "oldindan ko'p mantiqiy shard" — keng tarqalgan usul: boshidanoq, masalan, 256 ta mantiqiy shard qilinadi va ular 2 ta serverga taqsimlanadi. Server qo'shilganda butun mantiqiy shardlar ko'chadi, kalitlar qayta xeshlanmaydi.

9. Mashqlar

Mashqlar kurs/mashqlar/31/06-sharding/ da. 2- va 3-mashq yechimlari Node 24.21.0 da tekshirilgan (biz ishga tushirdik).

1-mashq (oson): bo'shliqlarni to'ldiring

  • Ma'lumotni bir nechta mustaqil bazaga bo'lish —
  • vazifalar uchun tabiiy shard kaliti —
  • So'rovni hamma shardga yuborib, natijani ilovada yig'ish —
  • Kanon o'lchovida hash % N bilan 2 → 3 shardda ko'chgan kalitlar — %
  • Halqadagi har shardning ko'p nuqtalari — virtual
Yechim
  • sharding — bo'laklash; har bo'lak — to'liq baza.
  • egasi_id — so'rovlarda bor, tekis, o'zgarmaydi.
  • scatter-gather — kalitsiz so'rovning narxi.
  • 66.7 — consistent hashing bilan 33.7.
  • tugun — virtual tugunlar taqsimotni tekislaydi.

2-mashq (o'rta): mantiqiy shardlar

256 ta mantiqiy shard bor: mantiqiy = xesh % 256. Ular jismoniy serverlarga xarita orqali taqsimlanadi. Avval 2 server: 0–127 — pg1, 128–255 — pg2. Uchinchi server qo'shildi: pg2 dagi 192–255 oraliq pg3 ga o'tdi. server(xesh, xarita) funksiyasini yozing: xarita — { dan, gacha, server } oraliqlar massivi. 10 000 kalit uchun (xesh sifatida k * 2654435761 % 2**32 — oddiy ko'paytirma xesh) nechta kalit server almashtirganini sanang. Ishora: find (Massivda qidirish va tekshirish).

Yechim
js
const ikki = [
  { dan: 0, gacha: 127, server: "pg1" },
  { dan: 128, gacha: 255, server: "pg2" },
];
const uch = [
  { dan: 0, gacha: 127, server: "pg1" },
  { dan: 128, gacha: 191, server: "pg2" },
  { dan: 192, gacha: 255, server: "pg3" },
];

function server(xesh, xarita) {
  const m = xesh % 256;
  return xarita.find((o) => m >= o.dan && m <= o.gacha).server;
}

let kochdi = 0;
const yangi = { pg1: 0, pg2: 0, pg3: 0 };
for (let k = 1; k <= 10000; k++) {
  const xesh = (k * 2654435761) % 2 ** 32;
  const s = server(xesh, uch);
  yangi[s]++;
  if (server(xesh, ikki) !== s) kochdi++;
}
console.log("ko'chdi:", kochdi);
console.log(yangi);

Konsolda:

text
ko'chdi: 2500
{ pg1: 5000, pg2: 2500, pg3: 2500 }

Faqat pg3 ga o'tgan kalitlar ko'chdi — taxminan chorak qism (64/256). pg1 umuman tegilmadi. Bu sxemada muvozanat ideal emas (pg1 yarmini ushlab turibdi), lekin keyin pg1 ning ham bir qismini pg3 ga berish mumkin — faqat xaritani o'zgartirib. Kalitlar qayta xeshlanmaydi, butun mantiqiy shardlar ko'chadi.

3-mashq (qiyin): scatter-gather bilan o'rtacha

Uchta shard "bu oy bajarilgan vazifalar"ning o'rtacha bajarilish vaqtini (daqiqada) hisoblashi kerak. Har shard o'z natijasini beradi. notogriOrtacha(natijalar) — har shard o'rtachasining o'rtachasi (xato usul). togriOrtacha(natijalar) — har shard { soni, yigindi } qaytaradi, ilova jami yig'indini jami soniga bo'ladi. Ikkalasini quyidagi ma'lumot bilan solishtiring va farqni tushuntiring. Ishora: reduce (reduce).

Yechim
js
const shardlar = [
  { soni: 1000, yigindi: 30000 },
  { soni: 10, yigindi: 1200 },
  { soni: 90, yigindi: 3600 },
];

const notogriOrtacha = (n) =>
  n.reduce((s, x) => s + x.yigindi / x.soni, 0) / n.length;

function togriOrtacha(n) {
  const jami = n.reduce(
    (s, x) => ({
      soni: s.soni + x.soni,
      yigindi: s.yigindi + x.yigindi,
    }),
    { soni: 0, yigindi: 0 },
  );
  return jami.yigindi / jami.soni;
}

console.log("noto'g'ri:", notogriOrtacha(shardlar).toFixed(1));
console.log("to'g'ri:", togriOrtacha(shardlar).toFixed(1));

Konsolda:

text
noto'g'ri: 63.3
to'g'ri: 31.6

Ikkinchi shardda atigi 10 ta vazifa, lekin o'rtachasi katta (120 daqiqa). O'rtachalarning o'rtachasida u birinchi shard bilan teng og'irlikka ega bo'lib qoldi va natijani deyarli ikki baravar oshirdi. To'g'ri usul — shardlardan "qo'shiladigan" qiymatlarni (soni, yigindi) olish. Citus kabi tizimlar ham avg ni ichida aynan shunday hisoblaydi: shardlardan yig'indi va sonni oladi, bo'lishni oxirida bajaradi.

10. Loyiha qadami: kanon tajribasi

Bu darsda vazifalar-api o'zgarmaydi va commit yo'q. Sabab — «Sharding'ga hali ertami?» bo'limidagi xulosa: loyiha bitta lokal bazada ishlaydi, foydalanuvchilar soni sharding'ni oqlamaydi. Sharding — infratuzilma va arxitektura qarori, uni hech kim "bir kunda" qo'shmaydi.

Kanon tajribasi: bitta Postgres 18.6 ichida ikki baza (shard_a, shard_b), 200 egasi × 50 vazifa, shard kaliti bilan va kalitsiz so'rovlar, 10 000 kalit uchun hash % N va consistent hashing, noto'g'ri shardga yozuv. Oxirida ikkala baza o'chirildi. Citus, CockroachDB, YugabyteDB va Vitess — sinalmagan.

O'zingiz takrorlaydigan qism Docker'siz. Darsdagi brauzer modellari kanon raqamlarini aynan qaytaradi — 89/111, 66.7 %, 33.7 %. Uchinchi modelga to'rtinchi shard d ni qo'shing (["a", "b", "c", "d"]) va 3 → 4 o'tishda qancha kalit ko'chishini hisoblang. Natijani oldindan taxmin qiling (ideal — chorak qism), keyin ishga tushiring.

11. Real ishda

  • Qayerda uchraydi: juda katta platformalar — messenjerlar, ijtimoiy tarmoqlar, to'lov tizimlari. Instagram foydalanuvchi bo'yicha Postgres sharding'i, YouTube — Vitess bilan MySQL sharding'i haqida ochiq yozishgan. O'zbekistondagi o'rta loyihalarning aksariyatiga bitta Postgres + replika yetadi.
  • Intervyu savollari: "Sharding va partitioning farqi?", "Shard kalitini qanday tanlaysiz?", "Consistent hashing nima uchun kerak?", "Shardlar orasida unique va tranzaksiyani qanday hal qilasiz?", "Sharding'dan oldin nimalarni sinab ko'rasiz?"
  • Tizim dizayni intervyusida: "Twitter/Telegram'ni loyihalang" savolida sharding deyarli doim chiqadi — shard kaliti va issiq nuqta haqida gapirishga tayyor bo'ling.
  • Kod review'da: "bu so'rovda egasi_id yo'q — sharding bo'lsa hamma shardga boradi", "bu ID ketma-ket son — shardlarda to'qnashadi".

Xulosa

  • Sharding — ma'lumotni mustaqil bazalarga bo'lish; yozish va hajmni bo'ladi, lekin har so'rovga "qaysi shardda?" savolini qo'shadi.
  • Shard kaliti — ko'p so'rovda bor, tekis, o'zgarmas; vazifalar uchun — egasi_id.
  • Kalitsiz so'rov — scatter-gather: hamma shardga boradi.
  • hash % N shard qo'shganda 66.7 % kalitni ko'chirdi, consistent hashing — 33.7 % (ideal 33.3 %).
  • Noyoblik, FK, ID va tranzaksiya — faqat shard ichida; ularni ilova hal qiladi.
  • Sharding — oxirgi chora: indeks, hovuz, kesh, replika va partitioning'dan keyin.

Keyingi dars: Ma'lumotlar bazasi xavfsizligi — bazaga ulanishni TLS bilan shifrlaymiz, ustunni pgcrypto bilan yashiramiz va ilova rolining huquqlarini tekshiramiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Sharding va taqsimlangan SQL: ma'lumotni serverlarga bo'lish — IlmHamroh