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.

🕑 14 daqiqa o‘qish 📄 1 018 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Bo'laklash nima
  2. Bo'laklash turlari
  3. Eski ma'lumotni chiqarish
  4. O'lik qatorlar
  5. VACUUM
  6. Autovacuum
  7. Transaction wraparound
  8. Zaxira nusxa
  9. Xulosa

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.

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

Bitta mantiqiy jadval, uchta fizik jadval sotuv PARTITION BY RANGE (sana) sotuv_2024 2024-01-01 dan 2025-01-01 gacha sotuv_2025 2025-01-01 dan 2026-01-01 gacha sotuv_2026 2026-01-01 dan 2027-01-01 gacha WHERE sana = '2026-01-09' Baza faqat sotuv_2026 ni o'qiydi - qolgan ikkitasiga tegmaydi
Bo'lak kesish (partition pruning) - keraksiz bo'laklar butunlay chetlab o'tiladi

So'rov oddiy jadvaldagidek yoziladi:

SQL
SELECT sana, filial, summa FROM sotuv ORDER BY sana;
Natija
    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:

SQL
SELECT tableoid::regclass AS bolak, count(*) AS qatorlar
FROM sotuv
GROUP BY tableoid
ORDER BY bolak;
Natija
   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 #

TurQachonMisol
RANGEOraliq bo'yichaSana, son oralig'i
LISTAniq qiymatlar ro'yxatiViloyat, mamlakat
HASHTeng taqsimlashmijoz_id bo'yicha
SQL
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;
Natija
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:

SQL
ALTER TABLE sotuv DETACH PARTITION sotuv_2024;

SELECT count(*) AS asosiy_jadvalda FROM sotuv;

SELECT count(*) AS ajratilgan_jadvalda FROM sotuv_2024;
Natija
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.

DELETE va DROP - yer bilan osmoncha farq

Bir yillik ma'lumotni o'chirishning ikki yo'li bor:

UsulNima 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 TABLEFayl 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.

SQL
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';
Natija
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 kerak

Statistika 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:

BuyruqNima qiladiJadvalni qulflaydimi
VACUUMJoyni qayta ishlatishga ochadiYo'q
VACUUM ANALYZEYuqoridagi + statistikaYo'q
VACUUM FULLJadvalni qaytadan yozadi, joy qaytadiHa, to'liq
VACUUM FULL ishlab turgan tizimda ishlatilmaydi

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

SQL
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';
Natija
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.

SQL
SHOW autovacuum;
Natija
 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.

Autovacuum ni o'chirmang

Ba'zan yuklamani kamaytirish uchun autovacuum ni o'chirish vasvasasi tug'iladi. Bu jiddiy xato.

Ikki oqibati bor:

  1. O'lik qatorlar to'planib, disk to'ladi va so'rovlar sekinlashadi.
  2. Tranzaksiya raqami aylanish chegarasiga yaqinlashganda PostgreSQL o'zini himoya qilish uchun yozishni butunlay to'xtatadi:
Natija
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:

UsulNima oladiTiklash nuqtasi
pg_dumpBitta bazaning mantiqiy nusxasiNusxa olingan payt
pg_dumpallButun klaster + rollarNusxa olingan payt
pg_basebackup + WALFayl darajasidagi nusxaIstalgan payt

Eng oddiy holat:

Natija
pg_dump -Fc -d dokon -f dokon.dump

pg_restore -d dokon_yangi dokon.dump
BayroqMa'nosi
-FcSiqilgan ikkilik format - pg_restore uchun
-FpOddiy SQL matni - psql bilan tiklanadi
-t jadvalFaqat bitta jadval
-sFaqat sxema, ma'lumotsiz
-aFaqat ma'lumot, sxemasiz

pg_dump ishlab turgan bazada bemalol chaqiriladi: u MVCC tufayli izchil nusxa oladi va hech kimni to'smaydi.

Tekshirilmagan zaxira - zaxira emas

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:

  1. Zaxirani boshqa mashinaga tiklab ko'ring.
  2. Qator sonlarini asl baza bilan solishtiring.
  3. Buni jadval bo'yicha takrorlang, masalan har oyda.
  4. Necha daqiqada tiklanishini o'lchang va yozib qo'ying.

Uchinchi bandsiz "bizda zaxira bor" degan gap shunchaki umid - kafolat emas.

Amaliy topshiriq
  1. PARTITION BY RANGE bilan sana bo'yicha jadval tuzing.
  2. Uchta yillik bo'lak yarating va ma'lumot kiriting.
  3. tableoid::regclass bilan qaysi qator qayerdaligini ko'ring.
  4. PARTITION BY LIST bilan viloyat bo'yicha bo'lang.
  5. DEFAULT bo'lak nega kerakligini tekshiring.
  6. DETACH PARTITION qiling va qator sonini solishtiring.
  7. Bitta qatorni uch marta UPDATE qiling.
  8. VACUUM ning nima qilishini o'z so'zingiz bilan yozing.
  9. VACUUM FULL nega xavfli ekanini tushuntiring.
  10. pg_dump bilan nusxa oling va uni boshqa bazaga tiklang.

Xulosa #

  • Bo'laklash bitta mantiqiy jadvalni bir nechta fizik jadvalga bo'ladi.
  • RANGE sana uchun, LIST ro'yxat uchun, HASH teng taqsimlash uchun.
  • Baza faqat kerakli bo'lakni o'qiydi - buni bo'lak kesish deyiladi.
  • LIST da DEFAULT bo'lak bo'lmasa, noma'lum qiymat xato beradi.
  • Eski ma'lumotni DELETE emas, DETACH PARTITION bilan chiqaring.
  • UPDATE va DELETE o'lik qator qoldiradi - ular joy egallaydi.
  • VACUUM bu joyni qayta ishlatishga ochadi va hech kimni to'smaydi.
  • VACUUM FULL jadvalni to'liq qulflaydi - ishlab turgan tizimda ishlatilmaydi.
  • autovacuum ni 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.

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.