Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Qulflar qisqacha
- 3. Kanon o'lchovi: kim qancha to'sadi
- 3.1 Tajriba
- 3.2 ADD COLUMN: metama'lumot yoki qayta yozish
- 3.3 Tur o'zgarishi
- 4. Indeks: CONCURRENTLY
- 4.1 Tranzaksiyada ishlamaydi
- 4.2 Yiqilsa — INVALID indeks qoladi
- 4.3 REINDEX CONCURRENTLY
- 5. Cheklovlar: NOT VALID va VALIDATE
- 5.1 Ikki qadam — ikki xil qulf
- 5.2 NOT NULL qo'shish
- 6. Qulf navbati va lock_timeout
- 6.1 Eng yashirin xavf
- 6.2 lock_timeout — tez voz kechish
- 7. Tekshiruv ro'yxati va squawk
- 7.1 Migratsiyani chiqarishdan oldin
- 7.2 squawk
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson)
- 2-mashq (o'rta)
- 3-mashq (qiyin)
- 10. Loyiha qadami
- 11. Real ishda
- Xulosa
- Manbalar
Xavfli migratsiyalar: PostgreSQL qulflari, CONCURRENTLY va lock_timeout
Qisqacha: Ba'zi migratsiyalar jadvalni qulflab, ilovaning yozuvlarini soniyalab to'xtatadi. Kanon 500 000 qatorli jadvalda o'lchadi: o'zgaruvchan default'li
ADD COLUMN(clock_timestamp()) yozuvlarni 11.8 soniya to'sdi, doimiy default'li ('a') esa 34 ms. OddiyCREATE INDEXyozuvlarni 284 ms to'sdi,CONCURRENTLY— 46 ms. Eng yashirin xavf — qulf navbati: uzun so'rov ochiq paytda hatto tezkorALTERham hamma yozuvni 1.7 s ushlab turdi. Himoya: xavfsiz shakllar (CONCURRENTLY,NOT VALID+VALIDATE) va har migratsiyadalock_timeout.
Bu darsda
- Qaysi
ALTERjadvalni qayta yozishini va qaysi biri faqat metama'lumotni o'zgartirishini ajrata olasiz. CREATE INDEX CONCURRENTLYqanday ishlashini va yiqilsa nima qolishini bilasiz.NOT VALID+VALIDATEbilan cheklovni to'xtovsiz qo'shasiz.- Qulf navbati xavfini tushunib,
lock_timeoutni to'g'ri qo'yasiz. - Migratsiyani prodakshnga chiqarishdan oldin tekshiruv ro'yxatidan o'tkazasiz.
Oldin bilishingiz kerak: ALTER TABLE va sxemani o'zgartirish, Qulflar: row, table va advisory, Prisma Migrate chuqur, Indeks nima: B-tree ichidan.
1. Nega bu kerak?
Jasur aka "Bahor" buyurtma tizimiga kichik migratsiya yozdi: buyurtmalar jadvaliga qabul_vaqti timestamptz DEFAULT clock_timestamp() ustuni. Lokal bazada 200 qator — 3 ms. Staging'da 5 000 qator — sezilmadi. Prodakshnda 8 million qator. Deploy boshlandi va… kassadagi planshetlar "Buyurtma saqlanmadi" deya boshladi. 40 soniyadan keyin hammasi tiklandi. Jasur aka hech narsani o'zgartirmagan edi — migratsiya tugagan edi, xolos.
Bu — qulf (lock) muammosi. Migratsiya bajarilayotgan paytda jadvalga ACCESS EXCLUSIVE qulfi olindi: o'qish ham, yozish ham to'xtadi. Lokal bazada bu 3 ms, prodakshnda — 40 soniya. Migratsiya xatosiz o'tgani muhim emas. Muhimi — shu vaqt ichida foydalanuvchi nima ko'rdi.
O'xshatish: ko'priq ta'miri. Yo'l chizig'ini bo'yash — bitta yo'lakni 10 daqiqa yopish, mashinalar o'tib turadi. Ko'priqning plitasini almashtirish — butun ko'priqni yopish. Muhandis qaysi ish qaysi turga kirishini oldindan biladi va ikkinchisini tunda, aylanma yo'l bilan qiladi.
2. Qulflar qisqacha
Qulflar: row, table va advisory darsida jadval qulflarini ko'rgan edik. Migratsiya uchun eng muhim uchtasi:
| Qulf | Kim oladi | Nimani to'sadi |
|---|---|---|
ACCESS EXCLUSIVE |
Ko'p ALTER TABLE, DROP, oddiy ADD CONSTRAINT |
Hammasini, oddiy SELECT ni ham |
SHARE |
CREATE INDEX (oddiy) |
Yozuvlarni (INSERT/UPDATE/DELETE); o'qish o'tadi |
SHARE UPDATE EXCLUSIVE |
CREATE INDEX CONCURRENTLY, VALIDATE CONSTRAINT, VACUUM |
Faqat o'xshash DDL'larni; o'qish va yozish o'tadi |
Qoida: migratsiyada iloji boricha jadvaldagi eng zaif qulf bilan ishlaydigan shaklni tanlaysiz.
O'z bazamizda (holat 36, 48 vazifa — biz ishga tushirdik, PostgreSQL 17.6) oddiy indeks qanday qulf olishini ko'ramiz:
BEGIN;
CREATE INDEX vazifalar_muddat_idx ON vazifalar (muddat);
SELECT mode FROM pg_locks
WHERE relation = 'vazifalar'::regclass AND granted;
ROLLBACK;Natija:
BEGIN
CREATE INDEX
mode
-----------
ShareLock
(1 row)
ROLLBACKShareLock — yozuvlar indeks qurilguncha kutadi. 48 qatorda bir zumda, 500 000 da — kanon o'lchovida 265 ms.
3. Kanon o'lchovi: kim qancha to'sadi
3.1 Tajriba
Kanon katta bazada (500 000 vazifa, PostgreSQL 18.6, Docker, Windows 11) haqiqiy yuklama yaratdi: 4 ta fon ishchi har 5 ms da UPDATE qiladi — xuddi ishlab turgan ilova. Har DDL tranzaksiyada bajarilib ROLLBACK qilindi (CONCURRENTLY — tranzaksiyasiz). O'lchandi: DDL vaqti va fon yozuvlarining eng uzoq kutishi. Ikkinchisi — foydalanuvchi his qiladigan raqam. Fonning o'zi (DDL'siz): eng uzoq kutish 30 ms.
- ADD COLUMN DEFAULT 'a'metama'lumot34 ms
- CREATE INDEX CONCURRENTLY46 ms
- ADD CHECK NOT VALID + VALIDATEVALIDATE qadami16 ms
- ADD CHECK (oddiy)121 ms
- CREATE INDEX (oddiy)284 ms
- Navbat: ADD COLUMN, uzun tranzaksiya ortida1 723 ms
- ADD COLUMN DEFAULT clock_timestamp()jadval qayta yozildi11 797 ms
Manba: IlmHamroh kanon o'lchovi: PostgreSQL 18.6, 500 000 qator, 4 ishchi × UPDATE har 5 ms, 2026-10-25
Oxirgi ustun qolganlarini "yo'q" qilib yubordi — bu grafikning asosiy xabari. To'liq jadval (kanon o'lchovi, IlmHamroh, 2026-10):
| DDL | DDL vaqti | Eng uzoq kutish |
|---|---|---|
ADD COLUMN x int (default'siz) |
14 ms | 19 ms |
ADD COLUMN … DEFAULT 'a' NOT NULL |
26 ms | 34 ms |
ADD COLUMN … DEFAULT clock_timestamp() |
11 832 ms | 11 797 ms |
CREATE INDEX |
265 ms | 284 ms |
CREATE INDEX CONCURRENTLY |
425 ms | 46 ms |
ADD CHECK |
122 ms | 121 ms |
ADD CHECK … NOT VALID |
13 ms | 20 ms |
NOT VALID + VALIDATE |
135 ms | 16 ms |
E'tibor bering: CONCURRENTLY sekinroq (425 ms vs 265 ms) — lekin foydalanuvchi uchun deyarli sezilmaydi (46 ms). Xavfsiz migratsiyaning mohiyati shu: umumiy vaqtni emas, to'sish vaqtini kamaytirish.
Diqqat: Bu — bitta mashina, bitta o'lchov, 500 000 qator (410 MB baza). Haqiqiy prodakshnda vaqtlar jadval hajmiga proporsional: 50 million qatorda
clock_timestamp()misoli o'n daqiqalar bo'lishi mumkin. Muhimi — nisbat va qaysi buyruq qaysi turga kirishi.
3.2 ADD COLUMN: metama'lumot yoki qayta yozish
Avval bitta so'z. Metama'lumot (metadata) — jadvalning o'zi haqidagi ma'lumot: ustunlar ro'yxati, turlari, default'lari. U tizim jadvallarida (pg_attribute kabi) turadi. Xuddi kitob mundarijasi: bitta qatorni o'zgartirish uchun butun kitobni qayta chop etish shart emas.
ALTER TABLE darsida ko'rgan edik: PostgreSQL 11 dan beri doimiy default ('a', 0, false) faqat metama'lumotga yoziladi. O'zgaruvchan default (clock_timestamp(), random(), gen_random_uuid()) har qatorga boshqa qiymat beradi — jadval qayta yoziladi.
Buni qatorlarni sanamasdan ham ko'rish mumkin. Jadval qayta yozilsa, PostgreSQL uni yangi faylga yozadi — pg_relation_filenode o'zgaradi. Natijada ikkita fayl_ozgarmadi qiymatiga qarang — biri 'a' dan keyin, biri clock_timestamp() dan keyin:
SELECT pg_relation_filenode('vazifalar') AS oldin \gset
BEGIN;
ALTER TABLE vazifalar ADD COLUMN a text NOT NULL DEFAULT 'a';
SELECT pg_relation_filenode('vazifalar') = :oldin AS fayl_ozgarmadi;
ALTER TABLE vazifalar
ADD COLUMN b timestamptz DEFAULT clock_timestamp();
SELECT pg_relation_filenode('vazifalar') = :oldin AS fayl_ozgarmadi;
ROLLBACK;Natija:
BEGIN
ALTER TABLE
fayl_ozgarmadi
----------------
t
(1 row)
ALTER TABLE
fayl_ozgarmadi
----------------
f
(1 row)
ROLLBACKNima bo'ldi? \gset — psql buyrug'i: so'rov natijasini :oldin o'zgaruvchisiga saqlaydi. 'a' dan keyin fayl o'sha (t), clock_timestamp() dan keyin — yangi fayl (f): butun jadval qayta yozildi va shu vaqt davomida ACCESS EXCLUSIVE ushlab turildi.
Xavfsiz yo'l: ustunni default'siz qo'shish (bir zumda), keyin ALTER COLUMN … SET DEFAULT clock_timestamp() (faqat yangi qatorlarga, bir zumda), eski qatorlarni esa partiyalab to'ldirish — Zero-downtime: expand-contract.
3.3 Tur o'zgarishi
ALTER COLUMN … TYPE deyarli doim jadvalni qayta yozadi (int → bigint), indekslarni qayta quradi. Kanon buni o'lchamoqchi bo'ldi — va kutilmagan xatoga duch keldi. Bizda ham:
BEGIN;
ALTER TABLE vazifalar ALTER COLUMN versiya TYPE bigint;
ROLLBACK;Natija:
BEGIN
ERROR: cannot alter type of a column used by a view or rule
DETAIL: rule _RETURN on view faol_vazifalar depends on column "versiya"
ROLLBACKTarjimasi: "ko'rinish yoki qoida ishlatayotgan ustun turini o'zgartirib bo'lmaydi. Tafsilot: faol_vazifalar ko'rinishining _RETURN qoidasi versiya ustuniga bog'liq". faol_vazifalar — 26-qismdagi SELECT * ko'rinish (View va materialized view). Tur o'zgarishi uchun ko'rinishni o'chirish va qayta yaratish kerak — migratsiya kattalashadi. Katta jadvalda esa umuman boshqa yo'l: yangi ustun + bosqichma-bosqich ko'chirish (expand-contract).
Tekshirib ko'ring:
ADD COLUMN izoh text(default'siz,NULLga ruxsat) — xavflimi?
Javob
Deyarli yo'q: kanon o'lchovida 14 ms, yozuvlar 19 ms kutdi. Faqat metama'lumot o'zgaradi. Lekin u ham ACCESS EXCLUSIVE oladi — ya'ni qulfni olish uchun navbatga turadi. Navbat xavfi — «Qulf navbati» bo'limida: aynan shunday "bezarar" buyruq hamma yozuvni 1.7 s to'sdi.
4. Indeks: CONCURRENTLY
4.1 Tranzaksiyada ishlamaydi
CREATE INDEX CONCURRENTLY jadvalni ikki marta o'qiydi va oraliqdagi yozuvlarni hisobga oladi — yozuvlar to'xtamaydi. Buning narxi: u tranzaksiya ichida ishlay olmaydi.
BEGIN;
CREATE INDEX CONCURRENTLY vazifalar_muddat_idx ON vazifalar (muddat);
ROLLBACK;Natija:
BEGIN
ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block
ROLLBACKMigratsiya vositalari uchun bu muhim: TypeORM (sukut — hammasi bitta tranzaksiyada), node-pg-migrate (sukut — bitta tranzaksiya) bunday faylni yiqitadi; maxsus "tranzaksiyasiz" belgi kerak (ORM'siz migratsiya vositalari). Prisma migrate deploy faylni o'zi tranzaksiyaga o'ramaydi — shuning uchun CONCURRENTLY u yerda ishlaydi, lekin faylda faqat shu buyruq bo'lgani ma'qul.
4.2 Yiqilsa — INVALID indeks qoladi
CONCURRENTLY yarmida yiqilsa (noyoblik buzildi, ulanish uzildi, lock_timeout), u o'zidan keyin yaroqsiz indeks qoldiradi. Biz noyob bo'lmagan ustunga noyob indeks qurib ko'rdik:
CREATE UNIQUE INDEX CONCURRENTLY vazifalar_ustuvorlik_uq
ON vazifalar (ustuvorlik);
SELECT c.relname, i.indisvalid FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname = 'vazifalar_ustuvorlik_uq';
DROP INDEX vazifalar_ustuvorlik_uq;Natija:
ERROR: could not create unique index "vazifalar_ustuvorlik_uq"
DETAIL: Key (ustuvorlik)=(yuqori) is duplicated.
relname | indisvalid
-------------------------+------------
vazifalar_ustuvorlik_uq | f
(1 row)
DROP INDEXIndeks yaratilmadi — lekin pg_index da u bor, indisvalid = f. U so'rovlarga yordam bermaydi, lekin har yozuvda yangilanib turadi (ortiqcha ish). Va migratsiyani qayta ishga tushirsangiz — relation … already exists. Shuning uchun:
- Yiqilgan
CONCURRENTLYdan keyin:DROP INDEX CONCURRENTLY IF EXISTS …, keyin qayta urinish. - Migratsiyada
CREATE INDEX CONCURRENTLY IF NOT EXISTS— ehtiyot bo'ling: u yaroqsiz indeksni ham "bor" deb o'tkazib yuboradi.
4.3 REINDEX CONCURRENTLY
Indeks shishib ketgan (bloat — VACUUM, autovacuum va bloat) yoki yaroqsiz bo'lsa — REINDEX INDEX CONCURRENTLY nom (PostgreSQL 12+). U yangi nusxani yonma-yon quradi va almashtiradi; oddiy REINDEX esa yozuvlarni to'sadi. Bu ham tranzaksiyadan tashqarida.
5. Cheklovlar: NOT VALID va VALIDATE
5.1 Ikki qadam — ikki xil qulf
Oddiy ADD CONSTRAINT … CHECK hamma qatorni ACCESS EXCLUSIVE ostida tekshiradi (kanon: 121 ms to'siq). Ikki qadamli yo'l (ALTER TABLE darsida tanishgan edik):
ALTER TABLE vazifalar ADD CONSTRAINT vazifalar_muddat_check
CHECK (muddat > '2020-01-01') NOT VALID;Natija:
ALTER TABLENOT VALID — eski qatorlar tekshirilmaydi, faqat yangi yozuvlar. Bir zumda. Endi ikkinchi qadam qanday qulf olishini ko'ramiz:
BEGIN;
ALTER TABLE vazifalar VALIDATE CONSTRAINT vazifalar_muddat_check;
SELECT mode FROM pg_locks
WHERE relation = 'vazifalar'::regclass AND granted;
ROLLBACK;
ALTER TABLE vazifalar DROP CONSTRAINT vazifalar_muddat_check;Natija:
BEGIN
ALTER TABLE
mode
--------------------------
ShareUpdateExclusiveLock
(1 row)
ROLLBACK
ALTER TABLEVALIDATE — SHARE UPDATE EXCLUSIVE: tekshiruv uzoq bo'lsa ham, o'qish va yozish davom etadi. Kanon o'lchovi: VALIDATE qadamida yozuvlar eng ko'pi 16 ms kutdi (oddiy ADD CHECK — 121 ms).
5.2 NOT NULL qo'shish
SET NOT NULL ham hamma qatorni skanerlaydi va ACCESS EXCLUSIVE oladi. NULL bo'lsa — xato:
BEGIN;
ALTER TABLE vazifalar ALTER COLUMN muddat SET NOT NULL;
ROLLBACK;Natija:
BEGIN
ERROR: column "muddat" of relation "vazifalar" contains null values
ROLLBACKKatta jadvalda xavfsiz yo'l — uch qadam:
ADD CONSTRAINT x_not_null CHECK (ustun IS NOT NULL) NOT VALID— bir zumda.VALIDATE CONSTRAINT x_not_null— yozuvlarni to'smaydi.ALTER COLUMN ustun SET NOT NULL— PostgreSQL 12+ validatsiyalanganCHECK (ustun IS NOT NULL)ni ko'rib, jadvalni skanerlamaydi (PostgreSQL hujjati,ALTER TABLE).
Kanon 108-toliq-ism-contract.sql aynan shunday qilgan — keyingi darsda ko'ramiz. PostgreSQL 18 da yangi imkoniyat bor — NOT NULL cheklovini ham NOT VALID bilan qo'shish; 17 da bu sintaksis xatosi, shuning uchun kanon ham CHECK yo'lini tanlagan.
6. Qulf navbati va lock_timeout
6.1 Eng yashirin xavf
Bu bo'limni yaxshilab o'qing — u eng ko'p prodakshnni to'xtatgan sabab. Kanon tajribasi: bir tranzaksiya jadvalni o'qib, ochiq turibdi (masalan uzoq hisobot). Shu payt migratsiya eng tez buyruqni yuboradi — default'siz ADD COLUMN (o'zi 14 ms). Natija: ADD COLUMN 1 716 ms kutdi va shu vaqt ichida hamma fon yozuvlari to'xtadi (eng uzoq kutish 1 723 ms).
sequenceDiagram
participant H as Uzun tranzaksiya
participant M as Migratsiya ALTER
participant I as Ilova yozuvlari
H->>H: jadvalni o'qidi, ochiq
M-->>H: ACCESS EXCLUSIVE kerak, kutadi
I-->>M: navbatda M ortida
H->>H: COMMIT
M->>M: 14 ms bajarildi
M->>I: endi yozish mumkinXuddi shu voqea vaqt bo'yicha, uch sessiya yonma-yon (kanon tajribasi, 11-qadam):
| Qadam | A: hisobot | B: migratsiya | C: ilova yozuvlari |
|---|---|---|---|
| 1 | BEGIN; SELECT … — ochiq |
— | UPDATE lar o'tadi |
| 2 | ochiq | ALTER — ACCESS EXCLUSIVE kutadi |
yangi UPDATE B ortida kutadi |
| 3 | ochiq (~1.7 s) | kutadi | hamma yozuv kutadi |
| 4 | COMMIT |
14 ms da bajariladi | navbat ochiladi, eng uzoq kutish 1 723 ms |
Nima bo'ldi? I (C ustuni) hisobot bilan to'qnashmagan — hisobot ham, yozuvlar ham faqat o'qish/yozish qulfini oladi, ular bir-biriga mos. Lekin navbatda ACCESS EXCLUSIVE kutayotgan ALTER bor, va keyingi har so'rov uning ortida turadi. Tez buyruq + sekin navbat = to'xtagan sayt.
6.2 lock_timeout — tez voz kechish
Himoya: migratsiyaga "qulfni shuncha vaqtda ololmasang — voz kech" deyish. Kanon xuddi shu holatni SET LOCAL lock_timeout = '200ms' bilan takrorladi:
12) xuddi shu, SET LOCAL lock_timeout = '200ms' (xato → qayta urinish) DDL 208 ms | yozuvlar 152, max kutish 215 ms, >100 ms: 4 XATO: canceling statement due to lock timeoutALTER 208 ms da voz kechdi (canceling statement due to lock timeout — "qulf kutish vaqti tugagani uchun buyruq bekor qilindi"), yozuvlar eng ko'pi 215 ms kutdi — 1 723 ms o'rniga. Migratsiya yiqildi — bu yaxshi: uni keyinroq, uzun tranzaksiya tugagach qayta urinasiz. Sayt esa ishlab turdi.
Migratsiya faylida:
SET LOCAL lock_timeout = '3s'; -- tranzaksiyali vosita uchun
-- yoki, tranzaksiyasiz (Prisma migrate deploy):
SET lock_timeout = '3s';
ALTER TABLE …;SET LOCAL faqat joriy tranzaksiya uchun; tranzaksiya yo'q bo'lsa — ta'sirsiz (PostgreSQL ogohlantirish beradi). Kanon 108 fayli Prisma variantida aynan shu sabab SET lock_timeout ishlatadi ("Prisma migratsiyani tranzaksiyaga o'ramaydi").
Qo'shimcha himoya: statement_timeout — buyruqning o'zi uzoq ketsa (masalan kutilmagan jadval qayta yozilishi) to'xtatadi. Lekin uni migratsiyada ehtiyot bilan qo'ying: CREATE INDEX CONCURRENTLY katta jadvalda daqiqalab ishlashi normal.
Tekshirib ko'ring:
lock_timeoutni qanchaga qo'yish kerak — 50 ms, 3 s yoki 60 s?
Javob
Yagona javob yo'q, lekin mantiq aniq: bu — foydalanuvchilar chidab turadigan to'xtash vaqti. 60 s — juda ko'p (ilova timeout'lari ishga tushadi). 50 ms — juda kam: oddiy yuklamada ham qulf ololmay, migratsiya doim yiqiladi. Odatda bir necha yuz millisekunddan bir necha soniyagacha, va qayta urinish bilan (masalan 5 marta, oraliq bilan). Kanon 108 faylida — 3 s.
7. Tekshiruv ro'yxati va squawk
7.1 Migratsiyani chiqarishdan oldin
Har migratsiya fayliga besh savol:
- Qaysi qulf olinadi va qancha vaqt? (
ALTER TABLE→ACCESS EXCLUSIVE.) - Jadval qayta yoziladimi? (O'zgaruvchan default, tur o'zgarishi,
SET NOT NULLskan.) lock_timeoutbormi?- Indeks —
CONCURRENTLYva alohida faylda, tranzaksiyasizmi? - Katta ma'lumot o'zgarishi (
UPDATEmillion qatorga) — migratsiyada emas, partiyalab alohidami?
7.2 squawk
Bu savollarni avtomatik beradigan vositalar bor. Eng mashhuri — squawk: PostgreSQL migratsiya fayllari uchun linter. U .sql faylni o'qib, xavfli naqshlarni ko'rsatadi: adding-required-field, require-concurrent-index-creation, constraint-missing-not-valid, changing-column-type, require-timeout-settings va boshqalar (squawk hujjati, squawkhq.com, 2026-10). CI'da migratsiya fayllariga ishga tushiriladi. Biz uni sinamadik (Rust binari, kanonga kirmagan) — qoidalar ro'yxatini yuqoridagi besh savol bilan solishtirsangiz, ular deyarli bir xil.
8. Ko'p uchraydigan xatolar
1. canceling statement due to lock timeout. Bu xato — himoya ishladi. Tuzatish: uzun tranzaksiyani toping (pg_stat_activity, xact_start, Monitoring) va migratsiyani qayta urining.
2. CREATE INDEX CONCURRENTLY cannot run inside a transaction block. Vosita faylni tranzaksiyaga o'radi. Tuzatish: vositaning "tranzaksiyasiz" belgisi (TypeORM transaction = false, node-pg-migrate pgm.noTransaction()), indeks — alohida faylda.
3. Yaroqsiz (indisvalid = f) indeks qoldi. Tuzatish: DROP INDEX CONCURRENTLY IF EXISTS, sababni tuzatib qayta qurish.
4. cannot alter type of a column used by a view or rule. Tuzatish: kichik jadvalda — ko'rinishni o'chirib, tur o'zgartirib, qayta yaratish (bitta tranzaksiyada); katta jadvalda — expand-contract.
5. "Lokal bazada tez edi". Lokal 48 qator — prodakshn 8 million. Tuzatish: migratsiyani prodakshn hajmidagi nusxada sinash yoki hech bo'lmasa qaysi buyruq qayta yozishini bilish (yuqoridagi filenode usuli).
6. Default'li ustun va eski versiya. PostgreSQL 10 va eskisida doimiy default ham jadvalni qayta yozardi. Eski serverdagi loyihada PG 11+ qoidasiga tayanmang.
9. Mashqlar
Yechimlar tekshirilgan (biz ishga tushirdik, PostgreSQL 17.6, holat 36 bazasi).
1-mashq (oson)
Har buyruq uchun: jadval qayta yoziladimi (ha/yo'q) va yozuvlarni to'sadimi? (a) ADD COLUMN reyting int DEFAULT 0; (b) ADD COLUMN token uuid DEFAULT gen_random_uuid(); (c) CREATE INDEX CONCURRENTLY …; (d) ALTER COLUMN versiya TYPE bigint (ko'rinish yo'q deb hisoblang).
Yechim
(a) Yo'q — doimiy default, metama'lumot; ACCESS EXCLUSIVE qisqa (navbat xavfi bor). (b) Ha — gen_random_uuid() o'zgaruvchan, har qatorga yangi qiymat; butun qayta yozish vaqtida hamma narsa to'siladi. (c) Yo'q — mavjud jadval qayta yozilmaydi, indeks yoniga quriladi; yozuvlar to'silmaydi (SHARE UPDATE EXCLUSIVE). (d) Ha — int → bigint har qatorni qayta yozadi va indekslarni qayta quradi; ACCESS EXCLUSIVE butun vaqt davomida.
2-mashq (o'rta)
filenode usuli bilan tekshiring: varchar(50) ustunli jadvalda (a) TYPE varchar(200), (b) TYPE varchar(20), (c) butun son ustuni TYPE bigint — qaysilari jadvalni qayta yozadi? Har birini alohida BEGIN … ROLLBACK da. vazifalar dan foydalanmang — undagi trigger va ko'rinish tur o'zgarishini umuman taqiqlaydi; o'zingiz generate_series bilan 1 000 qatorli jadval yasang (Ifodalar va o'rnatilgan funksiyalar). Ishora: «ADD COLUMN: metama'lumot yoki qayta yozish» bo'limidagi \gset usuli.
Yechim
CREATE TABLE sinov AS
SELECT g AS n, ('vazifa ' || g)::varchar(50) AS matn
FROM generate_series(1, 1000) g;
SELECT pg_relation_filenode('sinov') AS oldin \gset
BEGIN;
ALTER TABLE sinov ALTER COLUMN matn TYPE varchar(200);
SELECT pg_relation_filenode('sinov') = :oldin AS fayl_ozgarmadi;
ROLLBACK;
BEGIN;
ALTER TABLE sinov ALTER COLUMN matn TYPE varchar(20);
SELECT pg_relation_filenode('sinov') = :oldin AS fayl_ozgarmadi;
ROLLBACK;
BEGIN;
ALTER TABLE sinov ALTER COLUMN n TYPE bigint;
SELECT pg_relation_filenode('sinov') = :oldin AS fayl_ozgarmadi;
ROLLBACK;
DROP TABLE sinov;Natija:
SELECT 1000
BEGIN
ALTER TABLE
fayl_ozgarmadi
----------------
t
(1 row)
ROLLBACK
BEGIN
ALTER TABLE
fayl_ozgarmadi
----------------
f
(1 row)
ROLLBACK
BEGIN
ALTER TABLE
fayl_ozgarmadi
----------------
f
(1 row)
ROLLBACK
DROP TABLE(a) Uzunlikni oshirish — qayta yozish yo'q: eski qiymatlar yangi chegaraga baribir sig'adi. (b) Kamaytirish — qayta yozildi: har qiymat yangi chegaraga tekshirilib, yangi faylga yozildi. (c) integer → bigint — har qator 4 baytdan 8 baytga, qayta yozildi. Bizning birinchi taxminimiz ham shu bilan sinaldi: text → varchar(200) ham qayta yozdi (biz sinab ko'rdik) — "qaysi o'zgarish arzon" degan savolga taxmin bilan emas, filenode bilan javob bering.
3-mashq (qiyin)
vazifalar.muddat ga "muddat 2020-yildan keyin" cheklovini prodakshnda xavfsiz qo'shadigan migratsiyani yozing: lock_timeout, NOT VALID, VALIDATE. Keyin cheklov haqiqatan validatsiyalanganini pg_constraint.convalidated bilan tekshiring va oxirida cheklovni o'chiring (dars bazasini toza qoldirish uchun).
Yechim
BEGIN;
SET LOCAL lock_timeout = '3s';
ALTER TABLE vazifalar ADD CONSTRAINT vazifalar_muddat_check
CHECK (muddat > '2020-01-01') NOT VALID;
COMMIT;
SELECT convalidated FROM pg_constraint
WHERE conname = 'vazifalar_muddat_check';
BEGIN;
SET LOCAL lock_timeout = '3s';
ALTER TABLE vazifalar VALIDATE CONSTRAINT vazifalar_muddat_check;
COMMIT;
SELECT convalidated FROM pg_constraint
WHERE conname = 'vazifalar_muddat_check';
ALTER TABLE vazifalar DROP CONSTRAINT vazifalar_muddat_check;Natija:
BEGIN
SET
ALTER TABLE
COMMIT
convalidated
--------------
f
(1 row)
BEGIN
SET
ALTER TABLE
COMMIT
convalidated
--------------
t
(1 row)
ALTER TABLEIkki qadam — ikki alohida tranzaksiya. Bitta tranzaksiyada bo'lsa, birinchi qadamning ACCESS EXCLUSIVE qulfi VALIDATE tugaguncha ushlanib qolardi va butun ma'no yo'qolardi. NULL muddatlar cheklovni buzmaydi: NULL > '2020-01-01' — NULL, CHECK esa NULL ni "buzilmadi" deb qabul qiladi (NULL mantiqi).
10. Loyiha qadami
Bu dars vazifalar-api ga kod qo'shmaydi — kanon tajribasi (500 000 qatorli katta baza). Lekin uning xulosalari keyingi uch darsning migratsiyalariga to'g'ridan-to'g'ri kiradi:
107-toliq-ism-expand.sql— ustun default'siz qo'shiladi (kanon o'lchovi: 14 ms),CHECK … NOT VALIDbilan.108-toliq-ism-contract.sql—lock_timeout = '3s',VALIDATE(yozuvlar o'tadi),SET NOT NULLvalidatsiyalanganCHECKbilan (skansiz).
Bu darsda repoga hech narsa qo'shilmaydi va commit yo'q (107/108 commit'i — keyingi darsda). Amaliy natija — «Migratsiyani chiqarishdan oldin» bo'limidagi besh savol. Ularni o'z eslatmangizga ko'chiring: keyingi darsda 107/108 migratsiyalarini yozayotganda har biriga shu besh savolni berib chiqasiz.
11. Real ishda
- Katta kompaniyalarda migratsiya fayllari alohida "DB review" dan o'tadi; squawk kabi linter CI'da majburiy.
- Deploy vaqti — og'ir migratsiyalar kam yuklamali soatlarga qo'yiladi; lekin
lock_timeoutsiz kechasi ham xavfli (uzun hisobot so'rovi istalgan vaqtda bo'lishi mumkin). - Hodisa tahlili (postmortem) larda eng ko'p uchraydigan sabablar: o'zgaruvchan default, oddiy
CREATE INDEX, va qulf navbati. - Intervyuda: "Ishlab turgan 100 GB jadvalga indeks qanday qo'shasiz?", "
NOT VALIDnima uchun kerak?", "lock_timeoutnega muhim?".
Xulosa
- Jadval qulfining turi va davomiyligi muhim:
ACCESS EXCLUSIVEhammasini,SHAREyozuvlarni,SHARE UPDATE EXCLUSIVEdeyarli hech narsani to'smaydi. - Kanon:
DEFAULT clock_timestamp()yozuvlarni 11.8 s to'sdi, doimiy default — 34 ms; oddiy indeks 284 ms,CONCURRENTLY46 ms. CONCURRENTLYtranzaksiyada ishlamaydi va yiqilsaindisvalid = findeks qoldiradi.- Cheklov —
NOT VALID+ alohida tranzaksiyadaVALIDATE;NOT NULL— validatsiyalanganCHECKorqali. - Qulf navbati tezkor
ALTERni ham xavfli qiladi (1.7 s to'siq);lock_timeout+ qayta urinish — majburiy.
Keyingi dars: Zero-downtime: expand-contract — ustun nomini to'xtovsiz o'zgartirish: ikki tomonlama trigger, partiyalab backfill va kanon holat 38.
Manbalar
- PostgreSQL 18 hujjati — Explicit Locking (jadval qulf rejimlari): https://www.postgresql.org/docs/18/explicit-locking.html
- PostgreSQL 18 hujjati — ALTER TABLE (
NOT VALID,VALIDATE,SET NOT NULLva CHECK, qayta yozish): https://www.postgresql.org/docs/18/sql-altertable.html - PostgreSQL 18 hujjati — CREATE INDEX (
CONCURRENTLY, yaroqsiz indeks): https://www.postgresql.org/docs/18/sql-createindex.html - squawk — PostgreSQL migratsiya linteri, qoidalar ro'yxati: https://squawkhq.com/docs/rules (2026-10, sinalmagan)
- Kanon o'lchovi (IlmHamroh, 2026-10; PostgreSQL 18.6, 500 000 qator, 2026-10-25); darsdagi SQL — biz ishga tushirdik (PostgreSQL 17.6, Windows 11, holat 36), 2026-10-27.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!