Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

Şişkinlik cədvəllərinin və indekslərinin (bloat) təsiri geniş şəkildə tanınır və yalnız Postgres-də deyil. VACUUM FULL və ya CLUSTER kimi “qutudan kənar” bununla məşğul olmağın yolları var, lakin onlar əməliyyat zamanı masaları kilidləyir və buna görə də həmişə istifadə edilə bilməz.

Məqalədə şişkinliyin necə baş verdiyi, bununla necə məşğul ola biləcəyiniz, təxirə salınmış məhdudiyyətlər və onların pg_repack uzantısından istifadə edərkən gətirdiyi problemlər haqqında bəzi nəzəriyyələr olacaq.

Bu məqaləyə əsaslanır mənim çıxışım PgConf.Russia 2020-də.

Niyə şişkinlik var

Postgres çox versiyalı modelə əsaslanır (MVCC). Onun mahiyyəti ondan ibarətdir ki, cədvəldəki hər bir sətir bir neçə versiyaya malik ola bilər, əməliyyatlar isə bu versiyalardan birdən çoxunu görmür, lakin mütləq eyni deyil. Bu, bir neçə əməliyyatın eyni vaxtda işləməsinə və bir-birinə çox az təsir göstərməsinə imkan verir.

Aydındır ki, bütün bu versiyaları saxlamaq lazımdır. Postgres səhifə səhifə yaddaşla işləyir və səhifə diskdən oxuna bilən və ya yazıla bilən minimum məlumat miqdarıdır. Bunun necə baş verdiyini anlamaq üçün kiçik bir nümunəyə baxaq.

Tutaq ki, bir neçə qeyd əlavə etdiyimiz bir cədvəlimiz var. Cədvəlin saxlandığı faylın birinci səhifəsində yeni məlumatlar var. Bunlar öhdəsindən sonra digər əməliyyatlar üçün əlçatan olan cərgələrin canlı versiyalarıdır (sadəlik üçün təcrid səviyyəsinin Oxudulmuş olduğunu fərz edəcəyik).

Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

Daha sonra qeydlərdən birini yenilədik və bununla da köhnə versiyanı köhnəlmiş kimi qeyd etdik.

Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

Addım-addım, sıra versiyalarını yeniləyərək və silərək, məlumatların təxminən yarısının "zibil" olduğu bir səhifə əldə etdik. Bu məlumatlar heç bir əməliyyata görünmür.

Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

Postgresin bir mexanizmi var VACUUMköhnəlmiş versiyaları təmizləyən və yeni məlumatlar üçün yer açan . Lakin o, kifayət qədər aqressiv konfiqurasiya edilməyibsə və ya digər cədvəllərdə işlə məşğuldursa, o zaman “zibil məlumatları” qalır və biz yeni məlumatlar üçün əlavə səhifələrdən istifadə etməliyik.

Beləliklə, bizim nümunəmizdə, müəyyən bir vaxtda cədvəl dörd səhifədən ibarət olacaq, lakin orada canlı məlumatların yalnız yarısı olacaq. Nəticədə, cədvələ daxil olarkən, lazım olduğundan daha çox məlumat oxuyacağıq.

Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

VACUUM indi bütün uyğun olmayan sıra versiyalarını silsə belə, vəziyyət kəskin şəkildə yaxşılaşmayacaq. Yeni sətirlər üçün səhifələrdə və ya hətta bütün səhifələrdə boş yerimiz olacaq, lakin biz yenə də ehtiyacımızdan daha çox məlumat oxuyacağıq.
Yeri gəlmişkən, faylın sonunda tamamilə boş bir səhifə (bizim nümunəmizdə ikinci) olsaydı, VACUUM onu kəsə bilərdi. Amma indi ortadadır, ona görə də onunla heç nə etmək olmaz.

Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

Bu cür boş və ya çox seyrək səhifələrin sayı çoxaldıqda, buna bloat deyilir, bu, performansa təsir etməyə başlayır.

Yuxarıda təsvir edilən hər şey cədvəllərdə şişkinliyin meydana gəlməsinin mexanikasıdır. İndekslərdə bu, təxminən eyni şəkildə baş verir.

Məndə şişkinlik var?

Şişkinliyiniz olub olmadığını müəyyən etməyin bir neçə yolu var. Birincisinin ideyası Postgres daxili statistikasından istifadə etməkdir ki, burada cədvəllərdəki sıraların sayı, “canlı” sətirlərin sayı və s. əsas götürdük ssenari PostgreSQL Ekspertlərindən, tost və bloat btree indeksləri ilə birlikdə masanın şişməsini qiymətləndirə bilər. Təcrübəmizdə onun səhvi 10-20% təşkil edir.

Başqa bir yol uzantıdan istifadə etməkdir pgstattuple, bu, səhifələrin içərisinə baxmağa və bloatın həm təxmini, həm də dəqiq dəyərini əldə etməyə imkan verir. Ancaq ikinci halda, bütün cədvəli skan etməli olacaqsınız.

Az miqdarda şişkinlik, 20% -ə qədər məqbuldur. Üçün doldurucunun analoqu hesab edilə bilər masalar и indekslər. 50% və yuxarıda performans problemləri başlaya bilər.

Şişkinliklə mübarizə yolları

Postgres-də şişkinliklə mübarizə aparmağın bir neçə qeyri-adi yolları var, lakin onlar həmişə deyil və hər kəsə uyğun olmaya bilər.

AUTOVACUUM-u elə qurun ki, şişkinlik yaranmasın. Və daha dəqiq desək, sizin üçün məqbul səviyyədə saxlamaq. Bu, “kapitan” məsləhəti kimi görünür, amma əslində buna nail olmaq həmişə asan olmur. Məsələn, məlumat sxemində müntəzəm dəyişikliklə aktiv inkişafınız var və ya bir növ məlumat köçürməsi baş verir. Nəticədə, yük profiliniz tez-tez dəyişə bilər və müxtəlif cədvəllər üçün fərqli ola bilər. Bu o deməkdir ki, siz daim əyridən bir az qabaqda olmalısınız və AUTOVACUUM-u hər masanın dəyişən profilinə uyğunlaşdırmalısınız. Ancaq aydındır ki, bunu etmək asan deyil.

AUTOVACUUM-un cədvəlləri emal edə bilməməsinin başqa bir ümumi səbəbi, bu əməliyyatlar üçün əlçatan olması səbəbindən məlumatların təmizlənməsinə mane olan uzun müddət davam edən əməliyyatların olmasıdır. Buradakı tövsiyə də göz qabağındadır - "asma" əməliyyatlardan xilas olmaq və aktiv əməliyyatların vaxtını minimuma endirmək. Ancaq tətbiqinizdəki yük OLAP və OLTP-nin hibrididirsə, siz eyni vaxtda həm tez-tez yeniləmələrə, həm qısa sorğulara, həm də uzunmüddətli əməliyyatlara sahib ola bilərsiniz - məsələn, hesabat yaratmaq. Belə bir vəziyyətdə, yükü müxtəlif əsaslara yaymaq barədə düşünməlisiniz ki, bu da onların hər birini tənzimləməyə imkan verəcəkdir.

Başqa bir misal - profil homojen olsa belə, lakin verilənlər bazası çox yüksək yük altındadırsa, o zaman hətta ən aqressiv AUTOVACUUM belə öhdəsindən gələ bilməyəcək və şişkinlik baş verəcəkdir. Ölçəkləmə (şaquli və ya üfüqi) yeganə həll yoludur.

AUTOVACUUM-u konfiqurasiya etdiyiniz bir vəziyyətdə necə olmaq olar, ancaq şişkinlik böyüməyə davam edir.

Komanda VAKUM DOLU cədvəllərin və indekslərin məzmununu yenidən qurur və onlarda yalnız ən son məlumatları qoyur. Şişkinliyi aradan qaldırmaq üçün o, mükəmməl işləyir, lakin onun icrası zamanı masada eksklüziv kilid (AccessExclusiveLock) tutulur ki, bu da bu cədvələ sorğulara icazə verməyəcək, hətta seçir. Xidmətinizi və ya onun bir hissəsini bir müddət (verilənlər bazası və aparatınızın ölçüsündən asılı olaraq onlarla dəqiqədən bir neçə saata qədər) dayandırmağa imkanınız varsa, bu seçim ən yaxşısıdır. Təəssüf ki, planlı təmir zamanı VACUUM FULL-u işə salmağa vaxtımız yoxdur, ona görə də bu üsul bizə uyğun deyil.

Komanda KLUSTER cədvəllərin məzmununu VACUUM FULL ilə eyni şəkildə yenidən qurur, eyni zamanda məlumatların diskdə fiziki olaraq sıralanacağı indeksi təyin etməyə imkan verir (lakin gələcəkdə yeni sətirlər üçün sifariş təmin edilmir). Müəyyən hallarda, bu, bir sıra sorğular üçün yaxşı optimallaşdırmadır - indeksdəki bir neçə qeydi oxumaqla. Komandanın dezavantajı VACUUM FULL ilə eynidir - iş zamanı masanı kilidləyir.

Komanda REINDEX əvvəlki ikisinə bənzəyir, lakin müəyyən bir indeksi və ya cədvəldəki bütün indeksləri yenidən qurur. Kilidlər bir qədər zəifdir: cədvəldə ShareLock (dəyişikliklərin qarşısını alır, lakin seçimlərə icazə verir) və yenidən qurulan indeksdə AccessExclusiveLock (bu indeksdən istifadə edərək sorğuları bloklayır). Bununla belə, Postgres 12 seçimi təqdim etdi AYNDA, eyni zamanda qeydlərin əlavə edilməsini, dəyişdirilməsini və ya silinməsini bloklamadan indeksi yenidən qurmağa imkan verir.

Postgres-in əvvəlki versiyalarında siz REINDEX-ə bənzər bir nəticə əldə edə bilərsiniz. AYNDA INDEKS YARATIN. Güclü kilid olmadan indeks yaratmağa imkan verir (paralel sorğulara mane olmayan ShareUpdateExclusiveLock), sonra köhnə indeksi yenisi ilə əvəz edin və köhnə indeksi silin. Bu, tətbiqinizə müdaxilə etmədən indeks şişkinliyini aradan qaldırmağa imkan verir. İndeksləri yenidən qurarkən disk alt sistemində əlavə bir yük olacağını nəzərə almaq vacibdir.

Beləliklə, indekslər üçün isti şişkinliyi aradan qaldırmaq yolları varsa, cədvəllər üçün heç bir yol yoxdur. Xarici genişləndirmələr burada işə düşür: pg_repack (əvvəllər pg_reorg), pgcompact, pgcompacttable və qeyriləri. Bu məqalə çərçivəsində mən onları müqayisə etməyəcəyəm və yalnız bir qədər dəqiqləşdirdikdən sonra evdə istifadə etdiyimiz pg_repack haqqında danışacağam.

pg_repack necə işləyir

Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər
Deyək ki, bizim olduqca adi bir cədvəlimiz var - indekslər, məhdudiyyətlər və təəssüf ki, şişkinliklə. İlk addım olaraq pg_repack işləyərkən bütün dəyişiklikləri izləmək üçün jurnal cədvəli yaradır. Tətik bu dəyişiklikləri hər daxiletmə, yeniləmə və silmə üçün təkrarlayacaq. Sonra verilənlərin daxil edilməsi prosesini ləngitməmək üçün strukturuna görə orijinala bənzəyən, lakin indekslər və məhdudiyyətlər olmadan cədvəl yaradılır.

Sonra, pg_repack məlumatları köhnə cədvəldən yeni cədvələ köçürür, avtomatik olaraq bütün uyğun olmayan sətirləri süzür və sonra yeni cədvəl üçün indekslər yaradır. Bütün bu əməliyyatların icrası zamanı dəyişikliklər jurnal cədvəlində toplanır.

Növbəti addım dəyişiklikləri yeni cədvələ köçürməkdir. Miqrasiya bir neçə iterasiyada həyata keçirilir və jurnal cədvəlində 20-dən az giriş qaldıqda, pg_repack güclü kilid əldə edir, ən son məlumatları köçürür və Postgres sistem cədvəllərində köhnə cədvəli yenisi ilə əvəz edir. Bu, masa ilə işləyə bilməyəcəyiniz yeganə və çox qısa anıdır. Bundan sonra köhnə cədvəl və jurnalları olan cədvəl silinir və fayl sistemində yer boşaldılır. Proses tamamlandı.

Teorik olaraq hər şey əla görünür, amma praktikada necə? Biz pg_repack-i yüksüz və yük altında sınaqdan keçirdik və vaxtından əvvəl dayanma halında (başqa sözlə, Ctrl+C ilə) işini yoxladıq. Bütün testlər müsbət çıxdı.

Biz istehsala getdik - və sonra hər şey gözlədiyimiz kimi səhv oldu.

İlk pancake satışda

Birinci klasterdə unikal məhdudiyyət pozuntusu ilə bağlı xəta aldıq:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Bu məhdudiyyət pg_repack tərəfindən yaradılmış avtomatik yaradılan index_16508 adına malik idi. Onun tərkibinə daxil olan atributlara görə ona uyğun gələn “bizim” məhdudiyyətimizi müəyyən etdik. Problem o oldu ki, bu, adi bir məhdudiyyət deyil, gecikmiş bir məhdudiyyətdir (təxirə salınmış məhdudiyyət), yəni. onun yoxlanılması sql əmrindən gec həyata keçirilir ki, bu da gözlənilməz nəticələrə gətirib çıxarır.

Təxirə salınmış məhdudiyyətlər: nə üçün lazımdır və necə işləyirlər

Təxirə salınmış məhdudiyyətlər haqqında bir az nəzəriyyə.
Sadə bir misala nəzər salaq: bizdə iki atributlu avtomobil kataloqu cədvəli var - kataloqda avtomobilin adı və sırası.
Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

create table cars
(
  name text constraint pk_cars primary key,
  ord integer not null constraint uk_cars unique
);



Tutaq ki, birinci və ikinci maşını yerlərdə dəyişmək lazım idi. Qarşılıqlı həll birinci dəyəri ikinciyə, ikincini isə birinciyə yeniləməkdir:

begin;
  update cars set ord = 2 where name = 'audi';
  update cars set ord = 1 where name = 'bmw';
commit;

Lakin bu kodu işlətdiyimiz zaman məhdudiyyət pozuntusu alacağımız gözlənilir, çünki cədvəldəki dəyərlərin sırası unikaldır:

[23305] ERROR: duplicate key value violates unique constraint “uk_cars”
Detail: Key (ord)=(2) already exists.

Bunu fərqli şəkildə necə etmək olar? Birinci seçim: dəyərin cədvəldə mövcud olmadığına zəmanət verilən sifarişlə əlavə əvəzini əlavə edin, məsələn, “-1”. Proqramlaşdırmada buna "iki dəyişənin dəyərlərini üçüncü ilə mübadilə etmək" deyilir. Bu metodun yeganə dezavantajı əlavə yeniləmədir.

İkinci seçim: Tam ədədlər əvəzinə eksponent dəyəri üçün üzən nöqtə məlumat növündən istifadə etmək üçün cədvəli yenidən dizayn edin. Sonra, dəyəri 1-dən, məsələn, 2.5-ə qədər yeniləyərkən, ilk giriş avtomatik olaraq ikinci və üçüncü arasında "dayanacaq". Bu həll işləyir, lakin iki məhdudiyyət var. Birincisi, dəyər interfeysin bir yerində istifadə olunarsa, bu sizin üçün işləməyəcək. İkincisi, məlumat növünün dəqiqliyindən asılı olaraq, bütün qeydlərin dəyərlərini yenidən hesablamazdan əvvəl məhdud sayda mümkün əlavələrə sahib olacaqsınız.

Üçüncü seçim: məhdudiyyəti təxirə salın ki, o, yalnız öhdəlik zamanı yoxlanılsın:

create table cars
(
  name text constraint pk_cars primary key,
  ord integer not null constraint uk_cars unique deferrable initially deferred
);

İlkin sorğumuzun məntiqi öhdəliyin yerinə yetirildiyi vaxta qədər bütün dəyərlərin unikal olmasını təmin etdiyi üçün öhdəlik uğur qazanacaq.

Yuxarıda müzakirə edilən nümunə, əlbəttə ki, çox sintetikdir, lakin fikri ortaya qoyur. Tətbiqimizdə istifadəçilər eyni zamanda lövhədə paylaşılan vidcet obyektləri ilə qarşılıqlı əlaqədə olduqda münaqişələrin həllinə cavabdeh olan məntiqi həyata keçirmək üçün təxirə salınmış məhdudiyyətlərdən istifadə edirik. Bu cür məhdudiyyətlərin istifadəsi bizə proqram kodunu bir qədər sadələşdirməyə imkan verir.

Ümumiyyətlə, Postgres-də məhdudiyyət növündən asılı olaraq, onların yoxlanılmasının qranularlığının üç səviyyəsi var: sıra, əməliyyat və ifadə.
Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər
Mənbə: begriffs

CHECK və NOT NULL həmişə sıra səviyyəsində yoxlanılır, digər məhdudiyyətlər üçün, cədvəldən göründüyü kimi, müxtəlif variantlar var. Ətraflı oxuya bilərsiniz burada.

Qısaca ümumiləşdirsək, bir sıra hallarda təxirə salınmış məhdudiyyətlər daha oxunaqlı koda və daha az əmrlərə səbəb olur. Bununla belə, səhvin baş verdiyi andan bu barədə öyrəndiyiniz andan vaxtında ayrıldığı üçün, debug prosesini çətinləşdirərək bunun üçün ödəməlisiniz. Digər mümkün problem ondan ibarətdir ki, sorğuda gecikmiş məhdudiyyət iştirak edərsə, planlaşdırıcı həmişə optimal plan qura bilməyə bilər.

Təkmilləşdirmə pg_repack

Biz təxirə salınmış məhdudiyyətlərin nə olduğunu əhatə etdik, lakin onların problemimizlə necə əlaqəsi var? Daha əvvəl əldə etdiyimiz səhvi xatırlayın:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Bu, verilənlər jurnal cədvəlindən yeni cədvələ kopyalandıqda baş verir. Bu qəribə görünür, çünki jurnal cədvəlindəki məlumatlar orijinal cədvəldəki məlumatlar ilə birlikdə həyata keçirilir. Əgər onlar orijinal cədvəlin məhdudiyyətlərinə cavab verirlərsə, yeni cədvəldə eyni məhdudiyyətləri necə poza bilərlər?

Məlum olduğu kimi, problemin kökü pg_repack-in əvvəlki addımında yatır, hansı ki, məhdudiyyətlər deyil, yalnız indekslər yaradır: köhnə cədvəldə unikal məhdudiyyət var idi, yenisi isə əvəzində unikal indeks yaratdı.

Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

Burada qeyd etmək vacibdir ki, əgər məhdudiyyət normaldırsa və təxirə salınmırsa, onun əvəzinə yaradılmış unikal indeks bu məhdudiyyətə bərabərdir, çünki Postgres-də unikal məhdudiyyətlər unikal indeks yaratmaqla həyata keçirilir. Lakin təxirə salınmış məhdudiyyət vəziyyətində davranış eyni deyil, çünki indeks təxirə salına bilməz və həmişə sql əmri yerinə yetirilən zaman yoxlanılır.

Beləliklə, problemin mahiyyəti “təxirə salınmış” yoxlamadadır: orijinal cədvəldə o, icra zamanı, yenisində isə sql əmrinin icrası zamanı baş verir. Beləliklə, yoxlamaların hər iki halda eyni şəkildə həyata keçirildiyinə əmin olmalıyıq: ya həmişə gecikdirilir, ya da həmişə dərhal.

Beləliklə, bizim hansı fikirlərimiz var idi.

Təxirə salınmış kimi indeks yaradın

Birinci fikir hər iki yoxlamanı dərhal rejimdə yerinə yetirməkdir. Bu, məhdudiyyətin bir neçə yanlış pozitivliyinə səbəb ola bilər, lakin onlardan azdırsa, bu, istifadəçilərin işinə təsir etməməlidir, çünki bu cür münaqişələr onlar üçün normal bir vəziyyətdir. Onlar, məsələn, iki istifadəçi eyni vidceti redaktə etməyə başladıqda və ikinci istifadəçinin müştərisinin vidcetin birinci istifadəçi tərəfindən redaktə edilməsi üçün artıq bloklandığı barədə məlumat almağa vaxtı olmadıqda baş verir. Belə bir vəziyyətdə server ikinci istifadəçiyə imtina ilə cavab verir və onun müştərisi dəyişiklikləri geri qaytarır və vidceti kilidləyir. Bir az sonra, birinci istifadəçi redaktəni tamamlayanda, ikincisi vidcetin artıq bloklanmadığı barədə məlumat alacaq və öz hərəkətini təkrarlaya biləcək.

Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

Çeklərin həmişə təxirə salınmamış rejimində olmasını təmin etmək üçün biz orijinal təxirə salınmış məhdudiyyətə bənzər yeni indeks yaratdıq:

CREATE UNIQUE INDEX CONCURRENTLY uk_tablename__immediate ON tablename (id, index);
-- run pg_repack
DROP INDEX CONCURRENTLY uk_tablename__immediate;

Test mühitində yalnız bir neçə gözlənilən səhv aldıq. Uğurlar! Biz pg_repack-i yenidən məhsulda işə saldıq və bir saatlıq işdə ilk klasterdə 5 səhv aldıq. Bu məqbul nəticədir. Bununla belə, artıq ikinci klasterdə səhvlərin sayı əhəmiyyətli dərəcədə artdı və biz pg_repack-i dayandırmalı olduq.

Niyə belə oldu? Xətanın baş vermə ehtimalı eyni vidjetlərlə eyni vaxtda neçə istifadəçinin işləməsindən asılıdır. Göründüyü kimi, o anda birinci klasterdə saxlanılan məlumatlarla digərlərinə nisbətən daha az rəqabət dəyişiklikləri var idi, yəni. biz sadəcə "şanslıyıq".

Fikir işləmədi. Həmin anda biz daha iki həll yolu gördük: təxirə salınmış məhdudiyyətlərdən imtina etmək üçün tətbiq kodumuzu yenidən yazın və ya pg_repack-i onlarla işləməyi “öyrətin”. İkincisini seçdik.

Yeni cədvəldəki indeksləri orijinal cədvəldən təxirə salınmış məhdudiyyətlərlə əvəz edin

Yenidən nəzərdən keçirilməsinin məqsədi bəlli idi - əgər orijinal cədvəldə təxirə salınmış məhdudiyyət varsa, yenisi üçün indeks deyil, belə bir məhdudiyyət yaratmaq lazımdır.

Dəyişikliklərimizi yoxlamaq üçün sadə bir test yazdıq:

  • təxirə salınmış məhdudiyyət və bir qeyd ilə cədvəl;
  • mövcud qeydlə ziddiyyət təşkil edən məlumatları dövrəyə daxil edirik;
  • yeniləmə edin - məlumatlar artıq ziddiyyət təşkil etmir;
  • dəyişikliklər etmək.

create table test_table
(
  id serial,
  val int,
  constraint uk_test_table__val unique (val) deferrable initially deferred 
);

INSERT INTO test_table (val) VALUES (0);
FOR i IN 1..10000 LOOP
  BEGIN
    INSERT INTO test_table VALUES (0) RETURNING id INTO v_id;
    UPDATE test_table set val = i where id = v_id;
    COMMIT;
  END;
END LOOP;

pg_repack-in orijinal versiyası həmişə ilk əlavədə qəzaya uğrayıb, dəyişdirilmiş versiya səhvsiz işləyirdi. Əla.

Biz məhsula gedirik və məlumatları jurnal cədvəlindən yenisinə köçürməyin eyni mərhələsində yenidən xəta alırıq:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Klassik vəziyyət: hər şey sınaq mühitində işləyir, istehsalda yox?!

APPLY_COUNT və iki dəstənin qovşağı

Kodu hərfi sətir-sətir təhlil etməyə başladıq və vacib bir məqamı aşkar etdik: məlumatlar jurnal cədvəlindən yenisinə partiyalar şəklində ötürülür, APPLY_COUNT sabiti partiyanın ölçüsünü göstərir:

for (;;)
{
num = apply_log(connection, table, APPLY_COUNT);

if (num > MIN_TUPLES_BEFORE_SWITCH)
     continue;  /* there might be still some tuples, repeat. */
...
}

Problem ondadır ki, bir neçə əməliyyatın potensial olaraq məhdudiyyəti poza biləcəyi ilkin əməliyyatın məlumatları köçürmə zamanı iki partiyanın qovşağında başa çata bilər - əmrlərin yarısı birinci partiyada, digər yarısı isə yerinə yetiriləcək. ikincidə. Budur, necə də şanslıdır: əgər birinci partiyadakı komandalar heç nə pozmursa, deməli, hər şey qaydasındadır, amma etsələr, xəta baş verir.

APPLY_COUNT 1000 qeydə bərabərdir, bu da testlərimizin nə üçün uğurlu olduğunu izah edir - onlar "toplu keçid" işini əhatə etməyiblər. Biz iki əmrdən istifadə etdik - daxil et və yenilə, buna görə də iki əmrin tam 500 əməliyyatı həmişə bir topluda yerləşdirildi və problem yaşamadıq. İkinci yeniləməni əlavə etdikdən sonra redaktəmiz işləməyi dayandırdı:

FOR i IN 1..10000 LOOP
  BEGIN
    INSERT INTO test_table VALUES (1) RETURNING id INTO v_id;
    UPDATE test_table set val = i where id = v_id;
    UPDATE test_table set val = i where id = v_id; -- one more update
    COMMIT;
  END;
END LOOP;

Beləliklə, növbəti vəzifə bir əməliyyatda dəyişdirilmiş orijinal cədvəldəki məlumatların bir əməliyyat daxilində də yeni cədvələ daxil olmasını təmin etməkdir.

Partiyadan imtina

Və yenə iki həll yolu tapdıq. Birincisi: gəlin partiyalara bölünməkdən tamamilə imtina edək və məlumat ötürülməsini tək bir əməliyyata çevirək. Bu qərarın lehinə onun sadəliyi idi - tələb olunan kod dəyişiklikləri minimaldır (yeri gəlmişkən, o zamanlar köhnə versiyalarda pg_reorg belə işləyirdi). Ancaq bir problem var - biz uzunmüddətli bir əməliyyat yaradırıq və bu, əvvəllər qeyd edildiyi kimi, yeni bir şişkinliyin yaranması üçün təhlükədir.

İkinci həll daha mürəkkəbdir, lakin yəqin ki, daha düzgündür: cədvələ məlumat əlavə edən əməliyyatın identifikatoru ilə jurnal cədvəlində bir sütun yaradın. Sonra məlumatları kopyalayarkən, biz onları bu atribut üzrə qruplaşdıra və əlaqədar dəyişikliklərin birlikdə ötürülməsini təmin edə bilərik. Partiya bir neçə əməliyyatdan (və ya bir böyük) formalaşacaq və onun ölçüsü bu əməliyyatlarda nə qədər məlumatın dəyişdirilməsindən asılı olaraq dəyişəcək. Qeyd etmək vacibdir ki, müxtəlif əməliyyatların məlumatları jurnal cədvəlinə təsadüfi ardıcıllıqla daxil olduğundan, onu əvvəlki kimi ardıcıl oxumaq artıq mümkün olmayacaq. tx_id tərəfindən süzülmüş hər sorğuda seqscan çox bahadır, sizə indeks lazımdır, lakin o, həm də onu yeniləməyə əlavə xərclər səbəbindən metodu ləngidir. Ümumiyyətlə, həmişə olduğu kimi, nəyisə qurban vermək lazımdır.

Beləliklə, biz daha sadə olan birinci variantdan başlamaq qərarına gəldik. Birincisi, uzun bir əməliyyatın real problem olub olmadığını anlamaq lazım idi. Məlumatların köhnə cədvəldən yenisinə əsas ötürülməsi də bir uzun əməliyyatda baş verdiyi üçün sual “bu əməliyyatı nə qədər artıracağıq?” sualına çevrildi. İlk əməliyyatın müddəti əsasən cədvəlin ölçüsündən asılıdır. Yenisinin müddəti məlumatların ötürülməsi zamanı cədvəldə nə qədər dəyişikliyin toplanacağından asılıdır, yəni. yükün intensivliyinə görə. pg_repack əməliyyatı minimal xidmət yükü zamanı baş verdi və dəyişikliklərin miqdarı orijinal cədvəl ölçüsü ilə müqayisə olunmayacaq dərəcədə kiçik idi. Qərara gəldik ki, yeni əməliyyatın vaxtını laqeyd edə bilərik (müqayisə üçün orta hesabla 1 saat 2-3 dəqiqədir).

Təcrübələr müsbət idi. Satışa da çıxarın. Aydınlıq üçün qaçışdan sonra əsaslardan birinin ölçüsündə bir şəkil var:

Postgres: bloat, pg_repack və təxirə salınmış məhdudiyyətlər

Bu həll bizə tamamilə uyğun olduğundan, biz ikincini həyata keçirməyə çalışmadıq, lakin genişlənmənin tərtibatçıları ilə bunu müzakirə etmək imkanını nəzərdən keçiririk. Təəssüf ki, hazırkı revizyonumuz hələ dərc olunmağa hazır deyil, çünki biz problemi yalnız unikal gecikmiş məhdudiyyətlərlə həll etdik və tam hüquqlu yamaq üçün digər növlərə dəstək lazımdır. Ümid edirik ki, gələcəkdə bunu bacaracağıq.

Bəlkə bir sualınız var, niyə biz pg_repack-in incəliyi ilə bu hekayəyə qarışdıq və məsələn, analoqlarından istifadə etmədik? Müəyyən bir məqamda biz də bu barədə düşündük, lakin ondan əvvəllər, gecikmiş məhdudiyyətlər olmadan masalarda istifadənin müsbət təcrübəsi bizi problemin mahiyyətini anlamağa və onu düzəltməyə çalışmağa sövq etdi. Bundan əlavə, digər həllərdən istifadə də testlərin aparılması üçün vaxt tələb edir, ona görə də qərara gəldik ki, əvvəlcə orada olan problemi həll etməyə çalışaq və əgər ağlabatan müddətdə bunu edə bilməyəcəyimizi başa düşsək, analoqları nəzərdən keçirməyə başlayacağıq.

Tapıntılar

Öz təcrübəmizə əsaslanaraq nə tövsiyə edə bilərik:

  1. Şişkinliyinizi izləyin. Monitorinq məlumatlarına əsaslanaraq, siz avtovakuumun nə qədər yaxşı tənzimləndiyini başa düşə biləcəksiniz.
  2. Şişkinliyi məqbul səviyyədə saxlamaq üçün AUTOVACUUM-u təyin edin.
  3. Şişkinlik hələ də böyüyürsə və qutudan çıxarılan alətlərlə mübarizə apara bilmirsinizsə, xarici uzantılardan istifadə etməkdən qorxmayın. Əsas odur ki, hər şeyi yaxşı sınaqdan keçirək.
  4. Ehtiyaclarınıza uyğun olaraq xarici həlləri dəyişdirməkdən qorxmayın - bəzən bu, öz kodunuzu dəyişdirməkdən daha səmərəli və hətta asan ola bilər.

Mənbə: www.habr.com

Добавить комментарий