19-bo‘lim
Bo'laklash, VACUUM va zaxira nusxa
PARTITION BY RANGE va LIST, bo'lakni ajratish, o'lik qatorlar va VACUUM, autovacuum, transaction wraparound hamda pg_dump bilan zaxira.
Ushbu bo‘lim mundarijasi
Jadval millionlab qatorga yetganda va baza yillar davomida ishlaganda yangi savollar paydo bo'ladi: eski ma'lumotni qanday saqlash, joy nega to'lib boradi va yo'qotishdan qanday himoyalanish.
CREATE TABLE sotuv (
id int GENERATED ALWAYS AS IDENTITY,
sana date NOT NULL,
filial text NOT NULL,
summa numeric(10,2) NOT NULL
) PARTITION BY RANGE (sana);
CREATE TABLE sotuv_2024 PARTITION OF sotuv
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
CREATE TABLE sotuv_2025 PARTITION OF sotuv
FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');
CREATE TABLE sotuv_2026 PARTITION OF sotuv
FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');
INSERT INTO sotuv (sana, filial, summa) VALUES
('2024-03-12', 'Namangan', 850000.00),
('2024-11-05', 'Toshkent', 1200000.00),
('2025-02-18', 'Namangan', 640000.00),
('2025-07-30', 'Toshkent', 990000.00),
('2025-12-24', 'Samarqand', 730000.00),
('2026-01-09', 'Namangan', 410000.00);
Bo'laklash nima #
Bo'laklash (partitioning) - bitta mantiqiy jadvalni bir nechta fizik jadvalga bo'lish. Foydalanuvchi uchun u baribir bitta jadval bo'lib qolaveradi.
So'rov oddiy jadvaldagidek yoziladi:
SELECT sana, filial, summa FROM sotuv ORDER BY sana;
sana | filial | summa
------------+-----------+------------
2024-03-12 | Namangan | 850000.00
2024-11-05 | Toshkent | 1200000.00
2025-02-18 | Namangan | 640000.00
2025-07-30 | Toshkent | 990000.00
2025-12-24 | Samarqand | 730000.00
2026-01-09 | Namangan | 410000.00
(6 rows)
Lekin ma'lumot uchta alohida jadvalda yotibdi:
SELECT tableoid::regclass AS bolak, count(*) AS qatorlar
FROM sotuv
GROUP BY tableoid
ORDER BY bolak;
bolak | qatorlar
------------+----------
sotuv_2024 | 2
sotuv_2025 | 3
sotuv_2026 | 1
(3 rows)
tableoid - qator aslida qaysi jadvalda yotganini
ko'rsatuvchi yashirin ustun.
Bo'laklash turlari #
| Tur | Qachon | Misol |
|---|---|---|
RANGE | Oraliq bo'yicha | Sana, son oralig'i |
LIST | Aniq qiymatlar ro'yxati | Viloyat, mamlakat |
HASH | Teng taqsimlash | mijoz_id bo'yicha |
CREATE TABLE mijoz (
id int GENERATED ALWAYS AS IDENTITY,
ism text NOT NULL,
viloyat text NOT NULL
) PARTITION BY LIST (viloyat);
CREATE TABLE mijoz_vodiy PARTITION OF mijoz
FOR VALUES IN ('Namangan', 'Andijon', 'Fargona');
CREATE TABLE mijoz_boshqa PARTITION OF mijoz DEFAULT;
INSERT INTO mijoz (ism, viloyat) VALUES
('Husanboy', 'Namangan'),
('Malika', 'Andijon'),
('Kamola', 'Toshkent');
SELECT tableoid::regclass AS bolak, ism, viloyat
FROM mijoz ORDER BY ism;
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 3
bolak | ism | viloyat
--------------+----------+----------
mijoz_vodiy | Husanboy | Namangan
mijoz_boshqa | Kamola | Toshkent
mijoz_vodiy | Malika | Andijon
(3 rows)
DEFAULT bo'lak muhim: usiz ro'yxatda yo'q qiymat kelganda
INSERT xato beradi.
Eski ma'lumotni chiqarish #
Bo'laklashning eng katta foydasi shu yerda ko'rinadi:
ALTER TABLE sotuv DETACH PARTITION sotuv_2024;
SELECT count(*) AS asosiy_jadvalda FROM sotuv;
SELECT count(*) AS ajratilgan_jadvalda FROM sotuv_2024;
ALTER TABLE
asosiy_jadvalda
-----------------
4
(1 row)
ajratilgan_jadvalda
---------------------
2
(1 row)
DETACH - bu bir necha soniyalik amal. Jadvalning
o'zi joyida qoladi, shunchaki asosiy jadvaldan uziladi.
Bir yillik ma'lumotni o'chirishning ikki yo'li bor:
| Usul | Nima bo'ladi |
|---|---|
DELETE FROM sotuv WHERE sana < '2025-01-01' | Har qatorni o'qiydi, o'lik versiya qoldiradi, VACUUM kerak |
ALTER TABLE ... DETACH PARTITION + DROP TABLE | Fayl o'chadi, tozalash kerak emas |
Millionlab qatorda birinchi usul soatlab ishlashi va disk bandligini oshirib yuborishi mumkin - chunki o'chirilgan qatorlar darhol joy bo'shatmaydi.
Aynan shuning uchun vaqt bo'yicha o'sadigan jadvallarni - loglar, hodisalar, sotuvlar - sana bo'yicha bo'laklash tavsiya qilinadi.
O'lik qatorlar #
16-bobda ko'rganimizdek, UPDATE va DELETE eski versiyani
darhol o'chirmaydi - u o'lik qator bo'lib qoladi.
CREATE TABLE hisoblagich (id int PRIMARY KEY, qiymat int);
INSERT INTO hisoblagich VALUES (1, 0);
UPDATE hisoblagich SET qiymat = qiymat + 1;
UPDATE hisoblagich SET qiymat = qiymat + 1;
UPDATE hisoblagich SET qiymat = qiymat + 1;
SELECT pg_stat_force_next_flush();
SELECT count(*) AS korinadigan_qator FROM hisoblagich;
SELECT n_dead_tup AS olik_qator FROM pg_stat_user_tables
WHERE relname = 'hisoblagich';
CREATE TABLE
INSERT 0 1
UPDATE 1
UPDATE 1
UPDATE 1
pg_stat_force_next_flush
--------------------------
(1 row)
korinadigan_qator
-------------------
1
(1 row)
olik_qator
------------
3
(1 row)
Jadvalda bitta qator ko'rinadi, lekin diskda to'rtta versiya yotibdi: uchtasi o'lik, bittasi tirik.
pg_stat_force_next_flush() nima uchun kerakStatistika darhol yangilanmaydi - baza uni bir necha
soniyada bir marta yozadi. Shuning uchun UPDATE dan keyin
n_dead_tup hali 0 bo'lib turishi mumkin.
Bu funksiya statistikani shu zahoti yozishga majbur qiladi. U faqat shunday sinovlar uchun kerak - haqiqiy tizimda kutish yetarli.
VACUUM #
VACUUM o'lik qatorlarni topib, ularning joyini qayta
ishlatishga tayyorlaydi:
| Buyruq | Nima qiladi | Jadvalni qulflaydimi |
|---|---|---|
VACUUM | Joyni qayta ishlatishga ochadi | Yo'q |
VACUUM ANALYZE | Yuqoridagi + statistika | Yo'q |
VACUUM FULL | Jadvalni qaytadan yozadi, joy qaytadi | Ha, to'liq |
VACUUM FULL ishlab turgan tizimda ishlatilmaydiVACUUM FULL jadvalni butunlay qayta yozadi va shu davrda
ACCESS EXCLUSIVE qulf ushlaydi.
Bu degani: jadvalga hech kim tegolmaydi - hatto SELECT
ham. 100 GB jadvalda bu soatlab davom etishi mumkin.
Bundan tashqari, unga jadval hajmicha qo'shimcha bo'sh joy kerak - disk to'lganda muammoni hal qilish uchun ishlatib bo'lmaydi.
Odatiy VACUUM esa xavfsiz: u fonda ishlaydi va hech kimni
to'smaydi. Deyarli har doim aynan shu yetarli.
CREATE TABLE hisoblagich (id int PRIMARY KEY, qiymat int);
INSERT INTO hisoblagich VALUES (1, 0);
UPDATE hisoblagich SET qiymat = qiymat + 1;
UPDATE hisoblagich SET qiymat = qiymat + 1;
UPDATE hisoblagich SET qiymat = qiymat + 1;
SELECT pg_stat_force_next_flush();
SELECT n_dead_tup AS vacuumdan_oldin FROM pg_stat_user_tables
WHERE relname = 'hisoblagich';
VACUUM (ANALYZE) hisoblagich;
SELECT pg_stat_force_next_flush();
SELECT n_dead_tup AS vacuumdan_keyin FROM pg_stat_user_tables
WHERE relname = 'hisoblagich';
CREATE TABLE
INSERT 0 1
UPDATE 1
UPDATE 1
UPDATE 1
pg_stat_force_next_flush
--------------------------
(1 row)
vacuumdan_oldin
-----------------
3
(1 row)
VACUUM
pg_stat_force_next_flush
--------------------------
(1 row)
vacuumdan_keyin
-----------------
0
(1 row)
Uchta o'lik qator yo'qoldi - ularning joyi endi yangi qatorlar uchun ishlatilishi mumkin.
Autovacuum #
Odatda VACUUM ni qo'lda chaqirish shart emas - buni
autovacuum jarayoni bajaradi.
SHOW autovacuum;
autovacuum
------------
on
(1 row)
U jadvaldagi o'zgarishlar ma'lum chegaradan oshganda ishga
tushadi. Chegara ikki qismdan iborat: qat'iy son
(autovacuum_vacuum_threshold) va jadval hajmiga nisbatan
ulush (autovacuum_vacuum_scale_factor).
Transaction wraparound #
Tranzaksiya raqami cheksiz emas. U aylanib kelganda eski qatorlar "kelajakdan" ko'rinib qolishi mumkin - bu ma'lumotning buzilishi demakdir.
VACUUM buni ham oldini oladi: u eski qatorlarni "abadiy
ko'rinadigan" deb belgilaydi.
Ba'zan yuklamani kamaytirish uchun autovacuum ni o'chirish
vasvasasi tug'iladi. Bu jiddiy xato.
Ikki oqibati bor:
- O'lik qatorlar to'planib, disk to'ladi va so'rovlar sekinlashadi.
- Tranzaksiya raqami aylanish chegarasiga yaqinlashganda PostgreSQL o'zini himoya qilish uchun yozishni butunlay to'xtatadi:
ERROR: database is not accepting commands to avoid
wraparound data loss in database "dokon"
Bu holatdan chiqish uchun bazani to'xtatib, bir
foydalanuvchili rejimda VACUUM qilish kerak - ya'ni uzoq
davom etadigan to'xtash.
To'g'ri yo'l: autovacuum ni yoqiq qoldirish va zarur bo'lsa
uni tezroq ishlashga sozlash.
Zaxira nusxa #
Ikki xil yondashuv bor:
| Usul | Nima oladi | Tiklash nuqtasi |
|---|---|---|
pg_dump | Bitta bazaning mantiqiy nusxasi | Nusxa olingan payt |
pg_dumpall | Butun klaster + rollar | Nusxa olingan payt |
pg_basebackup + WAL | Fayl darajasidagi nusxa | Istalgan payt |
Eng oddiy holat:
pg_dump -Fc -d dokon -f dokon.dump
pg_restore -d dokon_yangi dokon.dump
| Bayroq | Ma'nosi |
|---|---|
-Fc | Siqilgan ikkilik format - pg_restore uchun |
-Fp | Oddiy SQL matni - psql bilan tiklanadi |
-t jadval | Faqat bitta jadval |
-s | Faqat sxema, ma'lumotsiz |
-a | Faqat ma'lumot, sxemasiz |
pg_dump ishlab turgan bazada bemalol chaqiriladi: u MVCC
tufayli izchil nusxa oladi va hech kimni to'smaydi.
Eng ko'p uchraydigan fojia: zaxira har kecha olinib kelingan, lekin kerak bo'lganda tiklanmagan.
Sabablar odatda oddiy: fayl bo'sh edi, joy tugagani uchun
yarim yozilgan edi, yoki rollar pg_dump ga tushmagani
uchun tiklangan bazada hech kim ulana olmadi.
Minimal talab:
- Zaxirani boshqa mashinaga tiklab ko'ring.
- Qator sonlarini asl baza bilan solishtiring.
- Buni jadval bo'yicha takrorlang, masalan har oyda.
- Necha daqiqada tiklanishini o'lchang va yozib qo'ying.
Uchinchi bandsiz "bizda zaxira bor" degan gap shunchaki umid - kafolat emas.
PARTITION BY RANGEbilan sana bo'yicha jadval tuzing.- Uchta yillik bo'lak yarating va ma'lumot kiriting.
tableoid::regclassbilan qaysi qator qayerdaligini ko'ring.PARTITION BY LISTbilan viloyat bo'yicha bo'lang.DEFAULTbo'lak nega kerakligini tekshiring.DETACH PARTITIONqiling va qator sonini solishtiring.- Bitta qatorni uch marta
UPDATEqiling. VACUUMning nima qilishini o'z so'zingiz bilan yozing.VACUUM FULLnega xavfli ekanini tushuntiring.pg_dumpbilan nusxa oling va uni boshqa bazaga tiklang.
Xulosa #
- Bo'laklash bitta mantiqiy jadvalni bir nechta fizik jadvalga bo'ladi.
RANGEsana uchun,LISTro'yxat uchun,HASHteng taqsimlash uchun.- Baza faqat kerakli bo'lakni o'qiydi - buni bo'lak kesish deyiladi.
LISTdaDEFAULTbo'lak bo'lmasa, noma'lum qiymat xato beradi.- Eski ma'lumotni
DELETEemas,DETACH PARTITIONbilan chiqaring. UPDATEvaDELETEo'lik qator qoldiradi - ular joy egallaydi.VACUUMbu joyni qayta ishlatishga ochadi va hech kimni to'smaydi.VACUUM FULLjadvalni to'liq qulflaydi - ishlab turgan tizimda ishlatilmaydi.autovacuumni hech qachon o'chirmang - wraparound bazani to'xtatib qo'yadi.- Tiklab ko'rilmagan zaxira - zaxira emas; uni muntazam sinab turing.
Keyingi bo'limda o'rgangan hamma narsani bitta amaliy loyihada birlashtiramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.