IlmHamroh
JavaScript Full-stack/27-qism. Ma'lumotlar bazasi chuqur: Postgres, ORM, Mongo, Redis39/65-dars18 daqiqa
Mundarija (27)

Seed: dev va test bazasini idempotent, deterministik ma'lumot bilan to'ldirish

Qisqacha: Seed (urug' ma'lumot) — bo'sh dev yoki test bazasini bir buyruqda ishlashga yaroqli ma'lumot bilan to'ldiradigan skript. Yaxshi seed uch shartga javob beradi: idempotent (ikki marta ishga tushsa ham dublikat va xato yo'q), deterministik (har safar aynan bir xil ma'lumot — urug' qiymati bilan) va prodakshnda ishlamaydi. Kanon prisma db seed: 20 foydalanuvchi, 281 vazifa, 286 izoh — 775 ms; ikkinchi ishga tushirishda hash bir xil; NODE_ENV=production da to'xtadi.

Bu darsda

  • Seed, migratsiya va test fixture'ining farqini tushuntira olasiz.
  • Seed'ni idempotent qilishning ikki usulini qo'llaysiz: ON CONFLICT va "o'zinikini o'chirib qayta yozish".
  • Urug' (seed) qiymati bilan deterministik ma'lumot yaratasiz — JS'da va SQL'da.
  • Prisma'da prisma db seed ni sozlab, kanon seed.ts ni o'qiysiz.
  • Prodakshn himoyasi va katta sintetik to'plam yaratishni bilasiz.

Oldin bilishingiz kerak: Migratsiya tushunchasi, UPSERT va MERGE, Prisma Client CRUD, Asosiy murakkablik sinflari.

1. Nega bu kerak?

Malika jamoaga qo'shildi. Repo'ni klonladi, npm ci, npm run db:migrate — baza tayyor. Ilovani ochadi: ro'yxat bo'sh. Biror sahifani tekshirish uchun u qo'lda 5 foydalanuvchi, 30 vazifa, izohlar yaratadi. Yarim kun ketdi. Ertaga Sardor ham xuddi shuni qiladi — o'z ma'lumotlari bilan. Xato topilganda: "menda ishlayapti, senda ishlamayapti" — chunki ikkalasining bazasi har xil.

Seed bu muammoni hal qiladi: bitta buyruq — npm run db:seed — va har dasturchining bazasida aynan bir xil realistik ma'lumot. Skrinshotlar bir xil, xato qayta yaratiladi, yangi odam birinchi kuni ishlay boshlaydi.

O'xshatish: dehqonning urug'lik zaxirasi. Har bahor bir xil navli urug' ekiladi — hosil oldindan ma'lum. Har yili tasodifiy urug' sepsa, nima unib chiqishini hech kim bilmaydi.

Bu so'z kursda ikki ma'noda kelgan: 12-qismda urug' (seed) — tasodifiy sonlar generatorining boshlang'ich qiymati (Nozik xatolar: flaky test). Bu yerda ham ikkala ma'no birga ishlaydi: seed skripti ichida urug' qiymati turadi.

2. Seed, migratsiya va fixture

Uchta tushuncha tez-tez aralashtiriladi:

Migratsiya Seed Test fixture
Nima Sxema (va ma'lumotnoma) Demo/dev ma'lumot Bitta test uchun ma'lumot
Qayerda ishlaydi Hamma muhit, prodakshn ham Lokal, dev, staging Test ichida
Necha marta Bir marta, holat jadvali bilan Istalgancha Har test oldidan
Misol CREATE TABLE, ustuvorlik enum 20 foydalanuvchi, 281 vazifa "egasi 2 ta vazifali foydalanuvchi"

Ma'lumotnoma (reference data) — ilova ishlashi uchun shart bo'lgan, hamma muhitda bir xil ma'lumot: valyutalar ro'yxati, viloyatlar, rollar, bizda — ustuvorlik qiymatlari (past, orta, yuqori, 004 migratsiyasida enum). U migratsiyada bo'ladi, chunki prodakshnga ham kerak. Demo foydalanuvchilar esa — seed'da: ular prodakshnga hech qachon tushmasligi kerak.

Test fixture'lar — 27-qismning oxirgi darslarida (Factory va fixture'lar): har test o'zining kichik ma'lumotini yaratadi va boshqa testlarga tayanmaydi.

3. Idempotent seed

3.1 Muammo

Sequelize darsida ko'rdik: oddiy seeder ikkinchi marta ishga tushganda yiqildi — Key (email)=(ali@example.com) already exists. Seed'ni bir necha marta ishga tushirish esa tabiiy: yangi kod tortdingiz, seed o'zgardi, qayta ishga tushirasiz. Seed idempotent bo'lishi kerak: necha marta bajarilsa ham, natija bir xil.

3.2 Usul 1: ON CONFLICT

Holat 39 bazasida (biz ishga tushirdik, PostgreSQL 17.6, Windows 11). Uch seed foydalanuvchini ikki marta qo'shamiz:

sql
INSERT INTO foydalanuvchilar (email, toliq_ism) VALUES
  ('seed1@seed.example', 'Sadoqat Tohirova'),
  ('seed2@seed.example', 'Muhtor Nosirov'),
  ('seed3@seed.example', 'Nodira Rashidova')
ON CONFLICT ((lower(email))) DO NOTHING;
INSERT INTO foydalanuvchilar (email, toliq_ism) VALUES
  ('seed1@seed.example', 'Sadoqat Tohirova'),
  ('seed2@seed.example', 'Muhtor Nosirov'),
  ('seed3@seed.example', 'Nodira Rashidova')
ON CONFLICT ((lower(email))) DO NOTHING;
SELECT count(*) FROM foydalanuvchilar
WHERE email LIKE '%@seed.example';

Natija:

text
INSERT 0 3
INSERT 0 0
 count 
-------
     3
(1 row)

Ikkinchi INSERT — 0 0: hech narsa qo'shilmadi, xato ham yo'q. ON CONFLICT ((lower(email))) — qo'shaloq qavslarga e'tibor bering: 26-qismda email noyobligi ustunga emas, ifodaga (lower(email)) qurilgan edi, shuning uchun to'qnashuv nishoni ham ifoda (UPSERT va MERGE).

DO NOTHING — "bor bo'lsa, tegma". Seed qiymati o'zgargan bo'lsa (foydalanuvchi ismini tuzatdingiz) — DO UPDATE SET toliq_ism = EXCLUDED.toliq_ism kerak. Ma'lumotnoma jadvallari uchun bu ayniqsa foydali: migratsiya har safar ro'yxatni yangilaydi.

3.3 Usul 2: o'zinikini o'chirib qayta yozish

Ko'p jadvalli seed'da (foydalanuvchi → vazifalar → izohlar) har jadvalga ON CONFLICT yozish og'ir. Kanon boshqa yo'lni tanlagan: seed faqat o'zi yaratgan yozuvlarni taniydi — email …@seed.example — ularni o'chiradi va qaytadan yaratadi. vazifalar va izohlar ON DELETE CASCADE bilan birga o'chadi:

sql
BEGIN;
DELETE FROM foydalanuvchilar WHERE email LIKE '%@seed.example';
SELECT count(*) AS namuna FROM foydalanuvchilar;
ROLLBACK;

Natija:

text
BEGIN
DELETE 3
 namuna 
--------
      8
(1 row)

ROLLBACK

Seed'ning 3 foydalanuvchisi ketdi, namuna ma'lumoti (8 ta) joyida qoldi. (Biz ROLLBACK qildik — seed foydalanuvchilari keyingi bo'limda kerak.) Bu usulning kuchi — chegara: seed hech qachon begona ma'lumotga tegmaydi. example domeni — zaxiralangan, hech kimga tegishli emas, shuning uchun haqiqiy foydalanuvchi bilan to'qnashmaydi.

Diqqat: "Hamma jadvalni TRUNCATE qilib, keyin to'ldirish" — eng oddiy, lekin eng xavfli seed. U bir kun noto'g'ri DATABASE_URL bilan ishga tushadi. IlmHamroh'ning o'z platformasida aynan shunday holat bo'lgan: eski seed.ts jadvallarni tozalardi va uni prodakshnda ishga tushirish taqiqlangan; kanon seed.ts izohida ham shu voqeaga ishora bor.

Tekshirib ko'ring: Kanon seed'i @seed.example foydalanuvchilarini o'chiradi. Dasturchi seed foydalanuvchisi nomidan ilovada qo'lda vazifa qo'shgan bo'lsa, keyingi seed'da nima bo'ladi?

Javob

U vazifa ham o'chadi — ON DELETE CASCADE foydalanuvchi bilan birga hamma vazifalarini oladi. Bu kutilgan xatti-harakat: seed foydalanuvchilari "seed'ga tegishli", ular ichidagi hamma narsa har safar qayta yaratiladi. Saqlanishi kerak bo'lgan sinov ma'lumotini o'z (seed bo'lmagan) foydalanuvchingizda yarating.

4. Deterministik ma'lumot

4.1 Urug' bilan tasodif

Seed realistik bo'lishi uchun tasodifiy ma'lumot kerak: har foydalanuvchida 0–30 vazifa, 40 % i bajarilgan. Lekin har safar boshqacha tasodif — har dasturchida boshqacha baza. Yechim — urug'li generator: bir xil urug' — bir xil "tasodifiy" ketma-ketlik. Asosiy murakkablik sinflari darsidagi makeRandom aynan shunday:

js
function makeRandom(seed) {
  return () => (seed = (seed * 16807) % 2147483647);
}

const ISMLAR = ["Sadoqat", "Muhtor", "Nodira", "Jasur", "Malika"];
function foydalanuvchilarYasa(urug, soni) {
  const random = makeRandom(urug);
  return Array.from({ length: soni }, (_, i) => ({
    email: `foydalanuvchi${i + 1}@seed.example`,
    ism: ISMLAR[random() % ISMLAR.length],
    vazifalar: random() % 31,
  }));
}

console.log(foydalanuvchilarYasa(27, 3));
console.log(JSON.stringify(foydalanuvchilarYasa(27, 3)) ===
  JSON.stringify(foydalanuvchilarYasa(27, 3)));

Konsolda:

text
[
  {
    email: 'foydalanuvchi1@seed.example',
    ism: 'Malika',
    vazifalar: 21
  },
  {
    email: 'foydalanuvchi2@seed.example',
    ism: 'Muhtor',
    vazifalar: 11
  },
  {
    email: 'foydalanuvchi3@seed.example',
    ism: 'Nodira',
    vazifalar: 20
  }
]
true

Ikki chaqiruv — aynan bir xil natija (true). Urug'ni o'zgartirsangiz (28) — boshqa, lekin yana barqaror ketma-ketlik.

4.2 Faker

Haqiqiy loyihada ism, shahar, sana, telefon uchun faker kutubxonasi ishlatiladi. Kanon @faker-js/faker 10.6.0 ni oldi (faqat dev bog'liqlik) va o'zbekcha lotin lokalini:

ts
import { fakerUZ_UZ_latin as faker } from "@faker-js/faker";
faker.seed(27);
faker.person.fullName();          // "Sadoqat Tohirova"
faker.number.int({ min: 0, max: 30 });
faker.datatype.boolean({ probability: 0.4 });
faker.helpers.arrayElement(["past", "orta", "yuqori"]);

faker.seed(27) — urug'. Kanon seed'ining birinchi uch foydalanuvchisi (kanon o'lchovi, IlmHamroh, 2026-10):

text
Sadoqat Tohirova|foydalanuvchi1@seed.example
Muhtor Nosirov|foydalanuvchi2@seed.example
Nodira Rashidova V|foydalanuvchi3@seed.example

Kanon faker sifatida bir nechta kamchilikni qayd etgan — bilib qo'ying:

  • To\raqorgon—o'` o'rniga teskari apostrof (lokal ma'lumotidagi xato).
  • "Nodira Rashidova V" — familiyadan keyin ortiqcha harf (yuqorida ko'rinib turibdi).
  • lorem matnlari — lotincha, o'zbekcha emas. Shuning uchun kanon vazifa matnlarini o'z ro'yxatidan oladi ("Hisobot yozish", "Bozordan non olish" …).

faker.date.recent({ days: 30, refDate: "2026-10-25T00:00:00Z" }) — refDate bilan: aks holda "oxirgi 30 kun" bugungi sanaga bog'liq bo'ladi va determinizm buziladi.

4.3 SQL'da: setseed

Katta sintetik to'plam uchun JS'dan ko'ra SQL tezroq: ma'lumot tarmoqdan o'tmaydi. PostgreSQL'da urug' — setseed(). Seed foydalanuvchisiga 1 000 vazifa:

sql
SELECT setseed(0.27) \gset
WITH t AS (
  SELECT g, random() < 0.4 AS b FROM generate_series(1, 1000) g
)
INSERT INTO vazifalar (egasi_id, matn, bajarildi, bajarilgan_vaqt)
SELECT f.id, 'Vazifa ' || t.g, t.b,
  CASE WHEN t.b THEN timestamptz '2026-10-25 09:00+05'
    - make_interval(hours => t.g) END
FROM foydalanuvchilar f, t
WHERE f.email = 'seed1@seed.example';
SELECT count(*) AS jami,
  count(*) FILTER (WHERE bajarildi) AS bajarilgan,
  min(tartib) AS tartib_min, max(tartib) AS tartib_max
FROM vazifalar v JOIN foydalanuvchilar f ON f.id = v.egasi_id
WHERE f.email = 'seed1@seed.example';

Natija:

text
INSERT 0 1000
 jami | bajarilgan | tartib_min | tartib_max 
------+------------+------------+------------
 1000 |        400 |          1 |       1000
(1 row)

Bir necha tafsilot:

  • \gset — setseed ning bo'sh natija qatorini ekranga chiqarmaslik uchun (psql o'zgaruvchisiga yozadi).
  • bajarilgan_vaqt — bajarildi bilan birga: 26-qismdagi CHECK (bajarildi = (bajarilgan_vaqt IS NOT NULL)) (ALTER TABLE darsidagi 004 migratsiyasi). Birinchi urinishimizda biz uni unutdik va vazifalar_bajarilgan_vaqt_check xatosini oldik — seed ham bazaning hamma qoidalaridan o'tishi kerak.
  • tartib 1 dan 1000 gacha — biz bermadik, uni 013 trigger'i qo'ydi. Seed trigger'larni chetlab o'tmaydi — bu yaxshi: ma'lumot haqiqiy ilova yaratgandek.
  • matn da raqam (Vazifa 1, Vazifa 2) — 014 faol vazifalar matnini egasi bo'yicha noyob qiladi; kanon seed'i ham shu sabab raqam qo'shadi.

Shu blokni (avval vazifalarni o'chirib) qayta ishga tushirdik — natija aynan o'sha: 400 bajarilgan. Urug' ishladi.

sql
DELETE FROM vazifalar WHERE egasi_id =
  (SELECT id FROM foydalanuvchilar
   WHERE email = 'seed1@seed.example');
SELECT setseed(0.27) \gset
WITH t AS (
  SELECT g, random() < 0.4 AS b FROM generate_series(1, 1000) g
)
INSERT INTO vazifalar (egasi_id, matn, bajarildi, bajarilgan_vaqt)
SELECT f.id, 'Vazifa ' || t.g, t.b,
  CASE WHEN t.b THEN timestamptz '2026-10-25 09:00+05'
    - make_interval(hours => t.g) END
FROM foydalanuvchilar f, t
WHERE f.email = 'seed1@seed.example';
SELECT count(*) FILTER (WHERE bajarildi) AS bajarilgan
FROM vazifalar v JOIN foydalanuvchilar f ON f.id = v.egasi_id
WHERE f.email = 'seed1@seed.example';

Natija:

text
DELETE 1000
INSERT 0 1000
 bajarilgan 
------------
        400
(1 row)

Tekshirib ko'ring: Kanon katta to'plamni (500 000 vazifa) SQL bilan 71 soniyada qurgan, 1 000 foydalanuvchili Prisma seed'i esa 15 035 vazifani 29 soniyada. Nega SQL shunchalik tez?

Javob

Prisma seed'i har foydalanuvchi uchun alohida so'rovlar yuboradi (create, createManyAndReturn, createMany) — har biri Node ↔ baza aylanishi. INSERT … SELECT … generate_series esa butun ishni baza ichida, bitta buyruqda qiladi — tarmoq yo'q. Katta hajmda (COPY, batch va cursor) qoida: ma'lumotni bazaning o'zida yarating yoki COPY bilan oqim sifatida yuboring.

5. Prodakshn himoyasi

Seed'ning eng muhim qatori — birinchisi. Kanon seed.ts boshida:

ts
if (process.env["NODE_ENV"] === "production") {
  console.error(
    "seed: NODE_ENV=production — to'xtatildi " +
    "(seed faqat lokal/staging uchun)");
  process.exit(1);
}

Kanon sinovi:

text
$ NODE_ENV=production npm run db:seed
seed: NODE_ENV=production — to'xtatildi (seed faqat lokal/staging uchun)
[exit 1]

Bu himoya yetarlimi? To'liq emas: NODE_ENV noto'g'ri qo'yilgan staging serveri prodakshn bazasiga ulangan bo'lishi mumkin. Qo'shimcha qatlamlar:

  • Seed faqat o'zinikiga tegadi (@seed.example) — xato bilan ishga tushsa ham, haqiqiy foydalanuvchilar o'chmaydi.
  • Ulanish satrini tekshirish: DATABASE_URL da localhost/127.0.0.1 bo'lmasa — aniq tasdiq talab qilish.
  • Prodakshn bazasiga ulanish huquqi dasturchining kompyuterida umuman bo'lmasligi.

Himoyani kod bilan sinash mumkin — u toza funksiya:

js
function seedRuxsat(env) {
  if (env.NODE_ENV === "production") return "rad: production";
  const url = env.DATABASE_URL ?? "";
  if (url === "") return "rad: DATABASE_URL yo'q";
  const host = new URL(url).hostname;
  if (!["127.0.0.1", "localhost"].includes(host)) {
    return env.SEED_TASDIQ === "ha" ? "ruxsat (tasdiq bilan)"
      : `rad: ${host} lokal emas`;
  }
  return "ruxsat";
}

const lokal = "postgres://postgres@127.0.0.1:5498/vazifalar";
const uzoq = "postgres://app@db.example.com:5432/vazifalar";
console.log(seedRuxsat({ DATABASE_URL: lokal }));
console.log(seedRuxsat({
  NODE_ENV: "production", DATABASE_URL: lokal }));
console.log(seedRuxsat({ DATABASE_URL: uzoq }));
console.log(seedRuxsat({ DATABASE_URL: uzoq, SEED_TASDIQ: "ha" }));

Konsolda:

text
ruxsat
rad: production
rad: db.example.com lokal emas
ruxsat (tasdiq bilan)

new URL() ulanish satridan xostni ajratadi (URL va URLSearchParams) — postgres:// ham URL sifatida o'qiladi.

6. prisma db seed

Kanon seed'i uchala qoidani ketma-ket bajaradi. Diagrammani yuqoridan o'qing — har qadam bitta qoida:

flowchart TD
  A[npm run db:seed] --> B{NODE_ENV = production?}
  B -->|ha| C[To'xtash, exit 1]
  B -->|yo'q| D[faker.seed 27 — urug']
  D --> E[O'zinikini o'chirish: @seed.example]
  E --> F[Foydalanuvchi, vazifa, izoh yaratish]
  F --> G[Natija qatori va disconnect]

B — himoya, D — determinizm, E — idempotentlik. Shu uchtasi bo'lmasa, seed foydasidan zarari ko'p.

6.1 Sozlash

Prisma 7 da seed buyrug'i prisma.config.ts da (kanon holat 39):

ts
export default defineConfig({
  schema: "prisma/schema.prisma",
  // seed: `npx prisma db seed` (holat 39)
  migrations: {
    path: "prisma/migrations",
    seed: "node prisma/seed.ts",
  },
  ...(url === undefined ? {} : { datasource: { url } }),
});

seed: "node prisma/seed.ts" — Node 24 .ts ni type stripping bilan o'zi bajaradi. Ishga tushirish — ikki yo'l: npx prisma db seed yoki npm run db:seed (node --env-file-if-exists=.env prisma/seed.ts — Prisma 7 .env ni o'zi o'qimagani uchun).

6.2 Kanon seed.ts

Asosiy qismi (kanon prisma/seed.ts, izohlar qisqartirildi, uzun qatorlar bo'lindi):

ts
const SONI = Number(process.env["SEED_FOYDALANUVCHI"] ?? "20");
const db = new PrismaClient({
  adapter: new PrismaPg({ connectionString: url }) });
faker.seed(27);

const ochirildi = await db.foydalanuvchi.deleteMany({
  where: { email: { endsWith: "@seed.example" } } });
for (let i = 1; i <= SONI; i++) {
  const f = await db.foydalanuvchi.create({
    data: {
      email: `foydalanuvchi${String(i)}@seed.example`,
      toliqIsm: faker.person.fullName(),
      sozlamalar: { til: faker.helpers.arrayElement(
        ["uz-Latn", "uz-Cyrl", "ru", "en"]) },
    },
    select: { id: true },
  });
  const n = faker.number.int({ min: 0, max: 30 });
  // createMany — bitta INSERT; tartib trigger'i har qatorga
  const r = await db.vazifa.createManyAndReturn({
    data: Array.from({ length: n }, (_, j) => {
      const bajarildi =
        faker.datatype.boolean({ probability: 0.4 });
      return {
        egasiId: f.id,
        matn: `${faker.helpers.arrayElement(ISHLAR)} ` +
          `${String(j + 1)} ${faker.helpers.arrayElement(TEGLAR)}`,
        bajarildi,
        bajarilganVaqt: bajarildi ? faker.date.recent({
          days: 30, refDate: "2026-10-25T00:00:00Z" }) : null,
        ustuvorlik: faker.helpers.arrayElement(
          ["past", "orta", "orta", "yuqori"] as const),
      };
    }),
    select: { id: true },
  });
  // … izohlar: har vazifaga 0–2 ta, createMany bilan
}
await db.$disconnect();

Yuqoridagi uch qoida — hammasi shu yerda: deleteMany faqat @seed.example (idempotent, chegarali), faker.seed(27) va refDate (deterministik), fayl boshidagi NODE_ENV tekshiruvi (himoya). SEED_FOYDALANUVCHI — hajmni o'zgartirish uchun. Vazifa matnida teg (#uy, #ish) — 006 trigger'i teglarni matndan o'zi ajratadi.

6.3 Kanon natijasi

Kanon o'lchovi (Prisma 7.10.1, faker 10.6.0, PostgreSQL 18.6). Ikki raqamga qarang — yaratilgan qatorlar soni va vaqt:

text
$ npx prisma db seed
Running seed command `node prisma/seed.ts` ...
seed: o'chirildi 0 eski seed foydalanuvchi; yaratildi 20 foydalanuvchi, 281 vazifa, 286 izoh — 775 ms
The seed command has been executed.

Ikkinchi marta (npm run db:seed):

text
seed: o'chirildi 20 eski seed foydalanuvchi; yaratildi 20 foydalanuvchi, 281 vazifa, 286 izoh — 718 ms
determinizm (ikki ishga tushirish xeshi bir xilmi): HA (faed53457a548399f6a19a8695dd03d0)
ikkinchidan keyin: 20 seed foydalanuvchi (takrorlanmadi)

Kanon ikki ishga tushirishdan keyingi seed ma'lumotining hashini hisoblagan — ma'lumotning qisqa "barmoq izi": bitta harf farq qilsa ham, hash butunlay boshqacha bo'lardi. Ikkalasi bir xil chiqdi (chiqishdagi "xeshi" — kanon skriptining yozuvi, ma'nosi hash). Namuna (8 foydalanuvchi) har safar joyida qoldi. Katta hajm: SEED_FOYDALANUVCHI=1000 — 15 035 vazifa, 14 989 izoh, 29 069 ms.

7. Ko'p uchraydigan xatolar

1. already exists / duplicate key ikkinchi ishga tushirishda. Seed idempotent emas. Tuzatish: ON CONFLICT yoki "o'zinikini o'chirib qayta yozish".

2. Har dasturchida boshqa ma'lumot. Urug'siz Math.random()/faker. Tuzatish: faker.seed(N), refDate, SQL'da setseed.

3. Seed bazaning qoidalarini buzadi. vazifalar_bajarilgan_vaqt_check, 014 noyoblik — biz ham uchradik. Tuzatish: seed'ni ham migratsiyadan keyin, bazaning hamma cheklovlari bilan sinash — u ilova kabi yozishi kerak.

4. Demo ma'lumot migratsiyada. Prodakshnga test foydalanuvchilari tushadi. Tuzatish: migratsiyada — faqat ma'lumotnoma; demo — seed'da.

5. TRUNCATE li seed. Bir kun noto'g'ri bazada ishlaydi. Tuzatish: chegarali o'chirish va prodakshn himoyasi.

6. Seed sekin. Har qator — alohida so'rov. Tuzatish: createMany, partiyalar, katta hajm — SQL generate_series yoki COPY.

8. Mashqlar

Yechimlar tekshirilgan (biz ishga tushirdik, PostgreSQL 17.6, Node 24.21.0).

1-mashq (oson)

Har ma'lumot qayerga yoziladi — migratsiya, seed yoki test fixture? (a) O'zbekiston viloyatlari ro'yxati (manzil formasi uchun); (b) 20 ta demo foydalanuvchi; (c) "o'chirilgan vazifa ro'yxatda chiqmaydi" testining ikkita vazifasi; (d) admin roli.

Yechim

(a) Migratsiya — ilova ishlashi uchun shart, hamma muhitda bir xil (ma'lumotnoma). (b) Seed — faqat dev/staging. (c) Test fixture — shu test o'zi yaratadi va o'zi ishlatadi. (d) Migratsiya — rol tizimning bir qismi; lekin admin foydalanuvchining o'zi (email, parol) — na migratsiya, na seed: u prodakshnda alohida, xavfsiz yo'l bilan yaratiladi (28-qism).

2-mashq (o'rta)

makeRandom va ISMLAR dan foydalanib vazifalarYasa(urug, soni) funksiyasini yozing: har vazifa { matn, bajarildi }, matn — ISHLAR ro'yxatidan + tartib raqami ("Kitob o'qish 1"), bajarildi — taxminan 40 % (random() % 10 < 4). Urug' 27, 5 ta vazifa yarating va ikki chaqiruv bir xil ekanini tekshiring. Ishora: Array.from({ length }, fn) (flat, flatMap va Array.from).

Yechim
js
function makeRandom(seed) {
  return () => (seed = (seed * 16807) % 2147483647);
}
const ISHLAR = ["Hisobot yozish", "Bozordan non olish",
  "Kitob o'qish", "Mashq qilish"];
function vazifalarYasa(urug, soni) {
  const random = makeRandom(urug);
  return Array.from({ length: soni }, (_, i) => ({
    matn: `${ISHLAR[random() % ISHLAR.length]} ${i + 1}`,
    bajarildi: random() % 10 < 4,
  }));
}
const a = vazifalarYasa(27, 5);
for (const v of a) console.log(v.matn, v.bajarildi);
console.log(JSON.stringify(a) ===
  JSON.stringify(vazifalarYasa(27, 5)));

Konsolda:

text
Bozordan non olish 1 true
Mashq qilish 2 true
Hisobot yozish 3 false
Bozordan non olish 4 true
Mashq qilish 5 false
true

Matnga tartib raqami qo'shildi — xuddi kanon seed'idagidek, 014 noyoblik qoidasi uchun. 5 ta vazifadan 3 tasi bajarilgan (60 %) — kichik namunada 40 % aniq chiqmaydi, bu normal; urug' esa natijani har safar bir xil qiladi.

3-mashq (qiyin)

Ma'lumotnoma jadvali uchun idempotent migratsiya yozing: viloyatlar (kod text PRIMARY KEY, nom text NOT NULL). Uni ikki marta ishga tushiring: ikkinchi safar bitta viloyat nomi tuzatilgan bo'lsin ('Toshkent v.' → 'Toshkent viloyati') va bitta yangi viloyat qo'shilgan bo'lsin. Natijada dublikat yo'q, tuzatish qo'llangan bo'lsin. Oxirida jadvalni o'chiring. Ishora: ON CONFLICT (kod) DO UPDATE SET nom = EXCLUDED.nom (UPSERT va MERGE).

Yechim
sql
CREATE TABLE IF NOT EXISTS viloyatlar (
  kod text PRIMARY KEY, nom text NOT NULL);
INSERT INTO viloyatlar (kod, nom) VALUES
  ('TO', 'Toshkent v.'), ('SA', 'Samarqand viloyati')
ON CONFLICT (kod) DO UPDATE SET nom = EXCLUDED.nom;
CREATE TABLE IF NOT EXISTS viloyatlar (
  kod text PRIMARY KEY, nom text NOT NULL);
INSERT INTO viloyatlar (kod, nom) VALUES
  ('TO', 'Toshkent viloyati'), ('SA', 'Samarqand viloyati'),
  ('BU', 'Buxoro viloyati')
ON CONFLICT (kod) DO UPDATE SET nom = EXCLUDED.nom;
SELECT kod, nom FROM viloyatlar ORDER BY kod;
DROP TABLE viloyatlar;

Natija:

text
CREATE TABLE
INSERT 0 2
NOTICE:  relation "viloyatlar" already exists, skipping
CREATE TABLE
INSERT 0 3
 kod |        nom         
-----+--------------------
 BU  | Buxoro viloyati
 SA  | Samarqand viloyati
 TO  | Toshkent viloyati
(3 rows)

DROP TABLE

Ikkinchi INSERT — 0 3: bittasi qo'shildi, ikkitasi yangilandi (DO UPDATE ham sanaladi). Dublikat yo'q, TO ning nomi tuzatildi. Bu naqsh ma'lumotnoma uchun — prodakshnda ham xavfsiz, chunki faqat o'z kalitlariga tegadi. Haqiqiy loyihada bunday ro'yxat migratsiya faylida bo'ladi va har o'zgarish yangi migratsiya bilan keladi; DO UPDATE esa shu migratsiyani bir necha bazada qayta qo'llashni xavfsiz qiladi.

9. Loyiha qadami

9.1 Holat 39

Kanon commit'i:

bash
npm i -D --save-exact @faker-js/faker@10.6.0
git add prisma/seed.ts prisma.config.ts package.json
git add package-lock.json tsconfig.json
M='feat(db): prisma/seed.ts — deterministik (faker uz_UZ_latin,'
M+=" seed 27), idempotent, production'da ishlamaydi"
git commit -m "$M"

O'zgarishlar:

  • prisma/seed.ts — «Kanon seed.ts» bo'limidagi fayl to'liq (boshida NODE_ENV va DATABASE_URL tekshiruvlari, ISHLAR va TEGLAR ro'yxatlari, oxirida natija qatori).
  • prisma.config.ts — migrations.seed: "node prisma/seed.ts".
  • package.json — "db:seed": "node --env-file-if-exists=.env prisma/seed.ts", dev bog'liqlik @faker-js/faker 10.6.0 (prodakshnga kirmaydi).
  • tsconfig.json — include ga prisma/seed.ts (tsc strict uni ham tekshirsin).

9.2 Tekshiruv

bash
npm run db:migrate
npm run db:seed
npm run db:seed
NODE_ENV=production npm run db:seed
npm run check

Kutilgan: birinchi va ikkinchi seed — "yaratildi 20 foydalanuvchi, 281 vazifa, 286 izoh" (ikkinchisida "o'chirildi 20"), uchinchisi — "to'xtatildi", exit 1; tip/lint 0, testlar 99/99 (kanon tekshiruvi). Vaqt mashinangizga bog'liq.

10. Real ishda

  • Har loyihada README'da "ishga tushirish: npm ci, npm run db:migrate, npm run db:seed" — yangi dasturchining birinchi kuni shu uch buyruq.
  • Staging ko'pincha seed bilan emas, prodakshnning anonimlashtirilgan nusxasi bilan to'ldiriladi — haqiqiy hajm va taqsimot, lekin shaxsiy ma'lumotsiz (Ma'lumotni qisman ko'chirish).
  • Demo va savdo — seed'li stend mijozga ko'rsatiladi; deterministik bo'lgani uchun har safar bir xil manzara.
  • Intervyuda: "Seed va migratsiya farqi?", "Seed'ni qanday idempotent qilasiz?", "Prodakshnda seed ishga tushib ketmasligi uchun nima qilasiz?".

Xulosa

  • Seed — dev/test ma'lumoti; ma'lumotnoma — migratsiyada; test ma'lumoti — fixture'da.
  • Idempotent: ON CONFLICT DO NOTHING/DO UPDATE yoki faqat o'zinikini (@seed.example) o'chirib qayta yozish.
  • Deterministik: urug' — makeRandom, faker.seed(27) + refDate, SQL'da setseed; kanon hash'i ikki ishga tushirishda bir xil.
  • Himoya: NODE_ENV=production da to'xtash (kanon: exit 1), lokal bo'lmagan xostda tasdiq, chegarali o'chirish.
  • Seed ham bazaning cheklov va trigger'laridan o'tadi; katta hajm — SQL'da (generate_series) yoki COPY bilan.

Keyingi dars: Migratsiyani deploy jarayonida qo'llash — migratsiyani kim va qachon qo'llaydi: alohida qadam, advisory lock, server ishga tushishidagi tekshiruv va BOM tuzog'i.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Seed: dev va test bazasini idempotent, deterministik ma'lumot bilan to'ldirish — IlmHamroh