Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

Толтырудың кестелер мен индекстерге әсері кеңінен белгілі және тек Postgres-те ғана емес. VACUUM FULL немесе CLUSTER сияқты қораптан тыс онымен күресудің жолдары бар, бірақ олар жұмыс кезінде үстелдерді құлыптайды, сондықтан әрқашан пайдалану мүмкін емес.

Мақалада кебулер қалай пайда болатыны, онымен қалай күресуге болатыны, кейінге қалдырылған шектеулер және олар pg_repack кеңейтімін пайдалану кезінде әкелетін мәселелер туралы аздаған теория бар.

Бұл мақала негізінде жазылған менің сөзім PgConf.Russia 2020 көрмесінде.

Неліктен ісіну пайда болады?

Postgres көп нұсқалы модельге негізделген (MVCC). Оның мәні мынада: кестедегі әрбір жолдың бірнеше нұсқасы болуы мүмкін, ал транзакциялар осы нұсқалардың біреуін ғана көрмейді, бірақ міндетті түрде бірдей емес. Бұл бірнеше транзакциялардың бір уақытта жұмыс істеуіне мүмкіндік береді және іс жүзінде бір-біріне әсер етпейді.

Бұл нұсқалардың барлығын сақтау керек екені анық. Postgres жадпен бет бойынша жұмыс істейді және бет дискіден оқуға немесе жазуға болатын деректердің ең аз мөлшері болып табылады. Мұның қалай болатынын түсіну үшін шағын мысалды қарастырайық.

Бізде бірнеше жазбаларды қосқан кесте бар делік. Кесте сақталатын файлдың бірінші бетінде жаңа деректер пайда болды. Бұл міндеттемеден кейін басқа транзакциялар үшін қолжетімді жолдардың тірі нұсқалары (қарапайымдылық үшін біз оқшаулау деңгейі оқылды деп есептейміз).

Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

Содан кейін біз жазбалардың бірін жаңарттық, осылайша ескі нұсқаны енді маңызды емес деп белгіледік.

Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

Қадамдық нұсқаларды жаңарту және жою арқылы біз деректердің шамамен жартысы «қоқыс» болатын бетті аяқтадық. Бұл деректер ешбір транзакцияға көрінбейді.

Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

Postgres механизмі бар VACUUM, ол ескірген нұсқаларды тазартады және жаңа деректерге орын береді. Бірақ егер ол жеткілікті түрде агрессивті түрде конфигурацияланбаса немесе басқа кестелерде жұмыстан бос емес болса, онда «қоқыс деректері» қалады және біз жаңа деректер үшін қосымша беттерді пайдалануымыз керек.

Сонымен, біздің мысалда кесте белгілі бір уақытта төрт беттен тұрады, бірақ оның жартысы ғана тірі деректерді қамтиды. Нәтижесінде кестеге қол жеткізген кезде біз қажет болғаннан әлдеқайда көп деректерді оқимыз.

Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

VACUUM қазір барлық қатысы жоқ жол нұсқаларын жойса да, жағдай күрт жақсармайды. Бізде жаңа жолдар үшін беттерде немесе тіпті бүкіл беттерде бос орын болады, бірақ біз әлі де қажет болғаннан көп деректерді оқимыз.
Айтпақшы, егер файлдың соңында толығымен бос бет (біздің мысалдағы екіншісі) болса, VACUUM оны кесуге болады. Бірақ қазір ол ортада, сондықтан онымен ештеңе істеуге болмайды.

Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

Мұндай бос немесе өте сирек беттердің саны көбейген кезде, бұл bloat деп аталады, ол өнімділікке әсер ете бастайды.

Жоғарыда сипатталғандардың барлығы кестелердегі кебулердің пайда болу механикасы болып табылады. Индекстерде бұл дәл осылай болады.

Менде ісіну бар ма?

Сізде ісіну бар-жоғын анықтаудың бірнеше жолы бар. Біріншісінің идеясы кестелердегі жолдар саны, «тірі» жолдар саны және т.б. туралы шамамен ақпаратты қамтитын ішкі Postgres статистикасын пайдалану болып табылады. Интернетте сіз дайын сценарийлердің көптеген нұсқаларын таба аласыз. негізге алдық сценарий PostgreSQL сарапшыларынан, ол тосттар мен bloat btree индекстерімен бірге bloat кестелерін бағалай алады. Біздің тәжірибемізде оның қателігі 10-20% құрайды.

Басқа әдіс - кеңейтімді пайдалану pgstattuple, бұл сізге беттердің ішіне қарап, болжалды және нақты шамадан тыс мәнді алуға мүмкіндік береді. Бірақ екінші жағдайда бүкіл кестені сканерлеуге тура келеді.

Біз 20% -ға дейінгі аз ғана бөртпе мәнін қолайлы деп санаймыз. Оны толтырғыштың аналогы ретінде қарастыруға болады кестелер и индекстер. 50% және одан жоғары болғанда өнімділік мәселелері басталуы мүмкін.

Ісінумен күресу жолдары

Postgres-тің қораптан тыс ісінумен күресудің бірнеше жолы бар, бірақ олар әрқашан барлығына жарамайды.

Ісіну пайда болмайтындай AUTOVACUUM конфигурациялаңыз. Немесе дәлірек айтқанда, оны өзіңізге қолайлы деңгейде ұстау. Бұл «капитанның» кеңесі сияқты көрінеді, бірақ іс жүзінде бұған жету әрқашан оңай емес. Мысалы, сізде деректер схемасына тұрақты өзгерістер енгізілетін белсенді даму бар немесе деректерді тасымалдаудың қандай да бір түрі орын алуда. Нәтижесінде жүктеме профилі жиі өзгеруі мүмкін және әдетте кестеден кестеге өзгереді. Бұл сізге үнемі аздап алға жұмыс істеу және AUTOVACUUM-ды әр кестенің өзгеретін профиліне сәйкес реттеу керек дегенді білдіреді. Бірақ мұны істеу оңай емес екені анық.

AUTOVACUUM кестелерге ілесе алмауының тағы бір жалпы себебі - бұл транзакциялар үшін қол жетімді деректерді тазалауға кедергі келтіретін ұзақ мерзімді транзакциялардың болуы. Мұндағы ұсыныс та анық – «салмалы» транзакциялардан арылыңыз және белсенді транзакциялардың уақытын азайтыңыз. Бірақ егер қолданбаңыздағы жүктеме OLAP және OLTP гибридті болса, онда сізде бір уақытта көптеген жиі жаңартулар мен қысқа сұраулар, сондай-ақ ұзақ мерзімді операциялар болуы мүмкін - мысалы, есепті құру. Мұндай жағдайда жүктемені әртүрлі негіздер бойынша тарату туралы ойлану керек, бұл олардың әрқайсысын дәлірек реттеуге мүмкіндік береді.

Тағы бір мысал - профиль біртекті болса да, бірақ деректер базасы өте жоғары жүктемеде болса, тіпті ең агрессивті AUTOVACUUM жеңе алмауы мүмкін және ісіну пайда болады. Масштабтау (тік немесе көлденең) жалғыз шешім болып табылады.

AUTOVACUUM орнатқан жағдайда не істеу керек, бірақ ісіну жалғасуда.

команда ВАКУУМ ТОЛЫҚ кестелер мен индекстердің мазмұнын қайта құрады және оларда тек сәйкес деректерді қалдырады. Ісінуді жою үшін ол тамаша жұмыс істейді, бірақ оны орындау кезінде кестедегі эксклюзивті құлып алынады (AccessExclusiveLock), бұл кестеде сұрауларды орындауға мүмкіндік бермейді, тіпті таңдайды. Қызметіңізді немесе оның бір бөлігін біраз уақытқа (деректер қоры мен аппараттық құралдың көлеміне байланысты ондаған минуттан бірнеше сағатқа дейін) тоқтатуға мүмкіндігіңіз болса, онда бұл опция ең жақсысы. Өкінішке орай, жоспарлы техникалық қызмет көрсету кезінде VACUUM FULL іске қосуға уақытымыз жоқ, сондықтан бұл әдіс бізге жарамайды.

команда КЛАСТЕР Кестелердің мазмұнын VACUUM FULL сияқты қайта жасайды, бірақ дискіде деректер физикалық реттелген индексті көрсетуге мүмкіндік береді (бірақ болашақта жаңа жолдар үшін тапсырысқа кепілдік берілмейді). Белгілі бір жағдайларда бұл көптеген сұраулар үшін жақсы оңтайландыру болып табылады - индекс бойынша бірнеше жазбаларды оқумен. Команданың кемшілігі VACUUM FULL командасымен бірдей - жұмыс кезінде үстелді құлыптайды.

команда REINDEX алдыңғы екеуіне ұқсас, бірақ белгілі бір индексті немесе кестенің барлық индекстерін қайта құрады. Құлыптар сәл әлсіз: кестедегі ShareLock (өзгертулерді болдырмайды, бірақ таңдауға мүмкіндік береді) және қайта құрылатын индекстегі AccessExclusiveLock (осы индексті пайдаланатын сұрауларды блоктайды). Дегенмен, Postgres-тің 12-нұсқасында параметр пайда болды БІРГЕ, бұл жазбаларды бір мезгілде қосуға, өзгертуге немесе жоюға тыйым салмай, индексті қайта құруға мүмкіндік береді.

Postgres бағдарламасының бұрынғы нұсқаларында REINDEX CONCURRENTLY көмегімен ұқсас нәтижеге қол жеткізуге болады. БІР КЕЗДЕ ИНДЕКС ЖАСАУ. Ол қатаң құлыптаусыз индексті құруға мүмкіндік береді (параллельді сұрауларға кедергі жасамайтын ShareUpdateExclusiveLock), содан кейін ескі индексті жаңасымен ауыстырып, ескі индексті жоюға мүмкіндік береді. Бұл қолданбаңызға кедергі келтірместен индекстің кебуін жоюға мүмкіндік береді. Индекстерді қайта құру кезінде дискінің ішкі жүйесіне қосымша жүктеме болатынын ескеру маңызды.

Осылайша, егер индекстер үшін «жылу кезінде» ісінуді жою жолдары болса, онда кестелер үшін жоқ. Мұнда әртүрлі сыртқы кеңейтімдер пайда болады: pg_repack (бұрынғы pg_reorg), pgcompact, pgcompacttable және басқалар. Бұл мақалада мен оларды салыстырмаймын және тек pg_repack туралы айтатын боламын, оны кейбір өзгертулерден кейін біз өзіміз қолданамыз.

pg_repack қалай жұмыс істейді

Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер
Бізде мүлдем кәдімгі кесте бар делік - индекстермен, шектеулермен және, өкінішке орай, кебулермен. pg_repack бағдарламасының бірінші қадамы жұмыс істеп тұрған кезде барлық өзгерістер туралы деректерді сақтау үшін журнал кестесін жасау болып табылады. Триггер бұл өзгерістерді әрбір кірістіру, жаңарту және жою үшін қайталайды. Содан кейін мәліметтерді енгізу процесін баяулатпау үшін құрылымы бойынша түпнұсқаға ұқсас, бірақ индекстері мен шектеулері жоқ кесте жасалады.

Содан кейін pg_repack ескі кестеден деректерді жаңа кестеге тасымалдайды, барлық қатысы жоқ жолдарды автоматты түрде сүзеді, содан кейін жаңа кесте үшін индекстерді жасайды. Барлық осы операцияларды орындау барысында өзгерістер журнал кестесінде жинақталады.

Келесі қадам - ​​өзгертулерді жаңа кестеге тасымалдау. Тасымалдау бірнеше итерацияда орындалады және журнал кестесінде 20-дан аз жазба қалғанда, pg_repack күшті құлыпқа ие болады, соңғы деректерді тасымалдайды және Postgres жүйелік кестелеріндегі ескі кестені жаңасымен ауыстырады. Бұл кестемен жұмыс істей алмайтын жалғыз және өте қысқа уақыт. Осыдан кейін ескі кесте және журналдары бар кесте жойылады және файлдық жүйеде бос орын босатады. Процесс аяқталды.

Теорияда бәрі керемет көрінеді, бірақ іс жүзінде не болады? Біз pg_repack жүктемесіз және жүктемесіз сынап көрдік және оның мерзімінен бұрын тоқтаған жағдайда жұмысын тексердік (басқаша айтқанда, Ctrl+C арқылы). Барлық сынақтар оң болды.

Біз азық-түлік дүкеніне бардық, содан кейін бәрі біз ойлағандай болмады.

Сатылымдағы алғашқы құймақ

Бірінші кластерде бірегей шектеудің бұзылуы туралы қате алдық:

$ ./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.

Бұл шектеудің автоматты түрде жасалған index_16508 атауы болды - оны pg_repack жасаған. Оның құрамына кіретін атрибуттарға сүйене отырып, біз оған сәйкес келетін «біздің» шектеуімізді анықтадық. Мәселе мынада болды: бұл мүлдем қарапайым шектеу емес, кейінге қалдырылған (кейінге қалдырылған шектеу), яғни. оны тексеру sql пәрменінен кейінірек орындалады, бұл күтпеген салдарға әкеледі.

Кейінге қалдырылған шектеулер: олар не үшін қажет және олар қалай жұмыс істейді

Кейінге қалдырылған шектеулер туралы кішкене теория.
Қарапайым мысалды қарастырайық: бізде екі атрибуттары бар автомобильдердің кесте-анықтамалық кітабы бар - анықтамалықтағы автомобильдің атауы мен реті.
Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

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



Бірінші және екінші көліктерді ауыстыру керек болды делік. Қарапайым шешім - бірінші мәнді екіншісіне, ал екіншісін біріншіге жаңарту:

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

Бірақ біз бұл кодты іске қосқан кезде, шектеулердің бұзылуын күтеміз, өйткені кестедегі мәндердің реті бірегей:

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

Мұны қалай басқаша жасауға болады? Бірінші нұсқа: кестеде жоқтығына кепілдік берілген тапсырысқа қосымша мән ауыстыруды қосыңыз, мысалы, «-1». Бағдарламалауда бұл «екі айнымалының мәндерін үшінші арқылы алмасу» деп аталады. Бұл әдістің жалғыз кемшілігі - қосымша жаңарту.

Екінші нұсқа: бүтін сандардың орнына тапсырыс мәні үшін өзгермелі нүкте деректер түрін пайдалану үшін кестені қайта жасаңыз. Содан кейін, мәнді 1-ден 2.5-ке дейін жаңартқанда, бірінші жазба автоматты түрде екінші және үшінші арасында «тұрады». Бұл шешім жұмыс істейді, бірақ екі шектеу бар. Біріншіден, мән интерфейстің бір жерінде пайдаланылса, ол сіз үшін жұмыс істемейді. Екіншіден, деректер түрінің дәлдігіне байланысты, барлық жазбалардың мәндерін қайта есептеу алдында сізде ықтимал кірістірулердің шектеулі саны болады.

Үшінші нұсқа: шектеуді тек орындау кезінде тексерілетіндей кейінге қалдырыңыз:

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

Біздің бастапқы сұрауымыздың логикасы барлық мәндердің міндеттеме кезінде бірегей екенін қамтамасыз ететіндіктен, ол сәтті болады.

Жоғарыда талқыланған мысал, әрине, өте синтетикалық, бірақ ол идеяны ашады. Қолданбамызда пайдаланушылар тақтадағы ортақ виджет нысандарымен бір уақытта жұмыс істегенде, қақтығыстарды шешуге жауап беретін логиканы жүзеге асыру үшін кейінге қалдырылған шектеулерді қолданамыз. Мұндай шектеулерді пайдалану бізге қолданба кодын біршама жеңілдетуге мүмкіндік береді.

Жалпы, шектеу түріне байланысты Postgres-те оларды тексеру үшін түйіршіктіліктің үш деңгейі бар: жол, транзакция және өрнек деңгейлері.
Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер
Ақпарат көзі: бегрифтер

CHECK және NOT NULL әрқашан жол деңгейінде тексеріледі, кестеден көрініп тұрғандай басқа шектеулер үшін әртүрлі опциялар бар. Толығырақ оқи аласыз осында.

Қысқаша қорытындылау үшін, бірқатар жағдайларда кейінге қалдырылған шектеулер оқуға болатын кодты және аз пәрмендерді қамтамасыз етеді. Дегенмен, сіз қатені түзету процесін қиындату арқылы төлеуіңіз керек, өйткені қате орын алған сәттен және сіз бұл туралы білген сәттен уақытында бөлінеді. Басқа ықтимал мәселе, егер сұрау кейінге қалдырылған шектеуді қамтитын болса, жоспарлаушы әрқашан оңтайлы жоспарды құра алмауы мүмкін.

pg_repack жетілдіру

Біз кейінге қалдырылған шектеулердің не екенін қарастырдық, бірақ олар біздің мәселемізге қалай қатысты? Бұрын алған қатені еске түсірейік:

$ ./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.

Ол деректер журнал кестесінен жаңа кестеге көшірілгенде орын алады. Бұл біртүрлі көрінеді, өйткені... журнал кестесіндегі деректер бастапқы кестедегі деректермен бірге орындалады. Егер олар бастапқы кестенің шектеулерін қанағаттандырса, олар жаңа кестеде бірдей шектеулерді қалай бұзуы мүмкін?

Белгілі болғандай, мәселенің түбірі pg_repack бағдарламасының алдыңғы қадамында жатыр, ол шектеулерді емес, тек индекстерді жасайды: ескі кестеде бірегей шектеу болды, ал жаңасы оның орнына бірегей индексті жасады.

Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

Бұл жерде ескеретін жайт, егер шектеу қалыпты болса және кейінге қалдырылмаса, оның орнына жасалған бірегей индекс осы шектеуге баламалы болады, өйткені Postgres-тегі бірегей шектеулер бірегей индекс жасау арқылы жүзеге асырылады. Бірақ кейінге қалдырылған шектеу жағдайында мінез-құлық бірдей емес, себебі индексті кейінге қалдыру мүмкін емес және әрқашан sql пәрмені орындалған кезде тексеріледі.

Мәселен, мәселенің мәні тексерудің «кідірісінде» жатыр: бастапқы кестеде ол орындау кезінде орын алады, ал жаңа кестеде sql командасы орындалған кезде. Бұл тексерулердің екі жағдайда да бірдей орындалатынына көз жеткізуіміз керек дегенді білдіреді: не әрқашан кешіктіріледі, не әрқашан бірден.

Сонымен, бізде қандай идеялар болды?

Кейінге қалдырылғанға ұқсас индекс жасаңыз

Бірінші идея - екі тексеруді де дереу режимде орындау. Бұл бірнеше жалған позитивті шектеулерді тудыруы мүмкін, бірақ олардың саны аз болса, бұл пайдаланушылардың жұмысына әсер етпеуі керек, өйткені мұндай қақтығыстар олар үшін қалыпты жағдай болып табылады. Олар, мысалы, екі пайдаланушы бір виджетті бір уақытта өңдеуді бастағанда және екінші пайдаланушының клиентінде виджетті бірінші пайдаланушы өңдеу үшін әлдеқашан блокталғаны туралы ақпаратты алуға уақыты болмаған кезде орын алады. Мұндай жағдайда сервер екінші пайдаланушыдан бас тартады және оның клиенті өзгертулерді кері қайтарып, виджетті блоктайды. Біраз уақыттан кейін бірінші пайдаланушы өңдеуді аяқтаған кезде, екіншісі виджет енді блокталмағаны туралы ақпаратты алады және өз әрекетін қайталай алады.

Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

Тексерулердің әрқашан кейінге қалдырылмайтын режимде болуын қамтамасыз ету үшін біз бастапқы кейінге қалдырылған шектеуге ұқсас жаңа индекс жасадық:

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

Сынақ ортасында біз тек бірнеше күтілетін қателерді алдық. Жетістік! Біз өндірісте pg_repack қайта іске қостық және жұмыстың бір сағатында бірінші кластерде 5 қате алдық. Бұл қолайлы нәтиже. Дегенмен, қазірдің өзінде екінші кластерде қателер саны айтарлықтай өсті және pg_repack тоқтатуға тура келді.

Неліктен бұлай болды? Қатенің орын алу ықтималдығы бір уақытта қанша пайдаланушының бірдей виджеттермен жұмыс істейтініне байланысты. Шамасы, сол кезде бірінші кластерде сақталған деректермен бәсекелестік өзгерістер басқаларға қарағанда әлдеқайда аз болды, яғни. біз жай ғана «бақытты» болдық.

Идея іске аспады. Сол кезде біз тағы екі шешімді көрдік: кейінге қалдырылған шектеулерден бас тарту үшін қолданба кодын қайта жазыңыз немесе олармен жұмыс істеуге pg_repack «үйретіңіз». Біз екіншісін таңдадық.

Жаңа кестедегі индекстерді бастапқы кестедегі кейінге қалдырылған шектеулермен ауыстырыңыз

Қайта қараудың мақсаты айқын болды - егер бастапқы кестеде кейінге қалдырылған шектеу болса, онда жаңасы үшін индекс емес, осындай шектеуді жасау керек.

Өзгерістерді тексеру үшін біз қарапайым сынақты жаздық:

  • кейінге қалдырылған шектеу және бір жазбасы бар кесте;
  • бар жазбаға қайшы келетін циклге деректерді кірістіру;
  • жаңартуды орындаңыз – деректер енді қайшы келмейді;
  • өзгерістерді енгізу.

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 бастапқы нұсқасы әрқашан бірінші кірістіруде бұзылды, өзгертілген нұсқасы қатесіз жұмыс істеді. Тамаша.

Біз өндіріске барамыз және деректерді журнал кестесінен жаңасына көшірудің сол фазасында қайтадан қате аламыз:

$ ./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.

Классикалық жағдай: барлығы сынақ орталарында жұмыс істейді, бірақ өндірісте емес?!

APPLY_COUNT және екі топтаманың түйісуі

Біз кодты сөзбе-сөз талдай бастадық және маңызды тармақты анықтадық: деректер журналдар кестесінен жаңасына пакеттермен тасымалданады, APPLY_COUNT тұрақтысы партияның өлшемін көрсетті:

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

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

Мәселе мынада, бірнеше операциялар шектеуді бұзуы мүмкін бастапқы транзакция деректері тасымалданған кезде екі топтаманың түйіскен жерінде аяқталуы мүмкін - командалардың жартысы бірінші партияда орындалады, ал қалған жартысы. екіншісінде. Және бұл жерде, сіздің сәттілікке байланысты: егер командалар бірінші партияда ештеңе бұзбаса, онда бәрі жақсы, бірақ олар жасаса, қате пайда болады.

APPLY_COUNT 1000 жазбаға тең, бұл біздің сынақтарымыздың неліктен сәтті өткенін түсіндіреді - олар «топтаманың қосылуы» жағдайын қамтымаған. Біз екі пәрменді қолдандық - кірістіру және жаңарту, сондықтан екі команданың дәл 500 транзакциясы әрқашан пакетке орналастырылды және бізде ешқандай проблемалар болмады. Екінші жаңартуды қосқаннан кейін біздің өңдеуіміз жұмысын тоқтатты:

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;

Сонымен, келесі тапсырма - бір транзакцияда өзгертілген бастапқы кестедегі деректер бір транзакция ішінде де жаңа кестеде аяқталатынына көз жеткізу.

Топтамадан бас тарту

Және тағы да бізде екі шешім болды. Біріншіден: пакеттерге бөлуден толығымен бас тартайық және деректерді бір транзакцияда тасымалдайық. Бұл шешімнің артықшылығы оның қарапайымдылығы болды - қажетті код өзгерістері аз болды (айтпақшы, pg_reorg ескі нұсқаларында дәл осылай жұмыс істеді). Бірақ бір мәселе бар - біз ұзақ мерзімді транзакция жасаймыз және бұл, бұрын айтылғандай, жаңа толқудың пайда болуына қауіп төндіреді.

Екінші шешім күрделірек, бірақ дұрысырақ: журнал кестесінде кестеге деректерді қосқан транзакция идентификаторымен баған жасаңыз. Содан кейін деректерді көшірген кезде, біз оны осы атрибут бойынша топтауға және қатысты өзгерістердің бірге тасымалдануын қамтамасыз ете аламыз. Пакет бірнеше транзакциядан (немесе бір үлкен) қалыптасады және оның өлшемі осы транзакцияларда қанша деректер өзгертілгеніне байланысты өзгереді. Әртүрлі транзакциялардың деректері журнал кестесіне кездейсоқ ретпен енетіндіктен, оны бұрынғыдай ретімен оқу мүмкін болмайтынын ескеру маңызды. tx_id арқылы сүзгілеу арқылы әрбір сұрау үшін seqscan тым қымбат, индекс қажет, бірақ оны жаңартудың үстеме шығындарына байланысты әдісті де баяулатады. Жалпы, әдеттегідей, бір нәрсені құрбан ету керек.

Сонымен, біз бірінші нұсқадан бастауды шештік, өйткені ол қарапайым. Біріншіден, ұзақ транзакцияның нақты проблема болатынын түсіну қажет болды. Мәліметтердің ескі кестеден жаңасына негізгі тасымалдануы да бір ұзақ транзакцияда болатындықтан, сұрақ «бұл транзакцияны қаншаға көбейтеміз?» Деген сұраққа айналды. Бірінші транзакцияның ұзақтығы негізінен кестенің өлшеміне байланысты. Жаңасының ұзақтығы деректерді беру кезінде кестеде қанша өзгерістер жиналатынына байланысты, яғни. жүктеменің қарқындылығы бойынша. pg_repack іске қосылуы ең аз қызмет жүктемесі уақытында орын алды және өзгерістер көлемі кестенің бастапқы өлшемімен салыстырғанда пропорционалды түрде аз болды. Біз жаңа транзакцияның уақытын елемеуге болатынын шештік (салыстыру үшін бұл орташа есеппен 1 ​​сағат 2-3 минутты құрайды).

Тәжірибелер оң болды. Өндірісті де іске қосыңыз. Түсінікті болу үшін мұнда іске қосылғаннан кейін дерекқорлардың бірінің өлшемі бар сурет берілген:

Postgres: bloat, pg_repack және кейінге қалдырылған шектеулер

Біз бұл шешімге толығымен қанағаттанғандықтан, біз екіншісін жүзеге асыруға тырыспадық, бірақ біз оны кеңейтімді әзірлеушілермен талқылау мүмкіндігін қарастырамыз. Біздің қазіргі редакциямыз, өкінішке орай, әлі жариялануға дайын емес, өйткені біз мәселені бірегей кейінге қалдырылған шектеулермен ғана шештік, ал толыққанды патч үшін басқа түрлерге қолдау көрсету қажет. Болашақта мұны істей аламыз деп үміттенеміз.

Мүмкін сізде сұрақ туындауы мүмкін, неге біз бұл оқиғаға pg_repack модификациясымен араластық және, мысалы, оның аналогтарын пайдаланбадық? Бір кездері біз де бұл туралы ойладық, бірақ оны ертерек, кейінге қалдырылған шектеулерсіз кестелерде пайдаланудың оң тәжірибесі бізді мәселенің мәнін түсінуге және оны түзетуге талпындырды. Сонымен қатар, басқа шешімдерді пайдалану сынақтарды жүргізуге уақытты қажет етеді, сондықтан біз алдымен ондағы мәселені шешуге тырысамыз деп шештік, ал егер біз мұны ақылға қонымды уақыт ішінде жасай алмайтынымызды түсінсек, аналогтарды іздей бастаймыз. .

қорытындылар

Өз тәжірибемізге сүйене отырып, біз не ұсына аламыз:

  1. Ісінуіңізді бақылаңыз. Бақылау деректеріне сүйене отырып, автовакуумның қаншалықты жақсы конфигурацияланғанын түсінуге болады.
  2. Ісінуді қолайлы деңгейде ұстау үшін AUTOVACUUM реттеңіз.
  3. Егер ісіну әлі де өсіп келе жатса және оны қораптан тыс құралдарды пайдаланып жеңе алмасаңыз, сыртқы кеңейтімдерді пайдаланудан қорықпаңыз. Ең бастысы - бәрін жақсы сынау.
  4. Сіздің қажеттіліктеріңізге сәйкес сыртқы шешімдерді өзгертуден қорықпаңыз - кейде бұл өз кодыңызды өзгертуге қарағанда тиімдірек және тіпті оңайырақ болуы мүмкін.

Ақпарат көзі: www.habr.com

пікір қалдыру