Postgres: bloat, pg_repack жана кийинкиге калтырылган чектөөлөр

Postgres: bloat, pg_repack жана кийинкиге калтырылган чектөөлөр

Үстөлдүн таблицаларга жана индекстерге тийгизген таасири кеңири белгилүү жана Постгресте гана эмес. VACUUM FULL же CLUSTER сыяктуу кутудан тышкары аны чечүүнүн жолдору бар, бирок алар иш учурунда үстөлдөрдү бекитип коюшат, ошондуктан дайыма эле колдонууга болбойт.

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

Бул макаланын негизинде жазылган менин сөзүм PgConf.Russia 2020да.

Эмне үчүн шишик пайда болот?

Postgres көп версиялуу моделге негизделген (MVCC). Анын маңызы таблицадагы ар бир сап бир нече версияга ээ болушу мүмкүн, ал эми транзакциялар бул версиялардын бирден ашык эмес, бирок сөзсүз эле бир эле түрүн көрө албайт. Бул бир нече транзакциялардын бир эле учурда иштөөсүнө мүмкүндүк берет жана бири-бирине дээрлик эч кандай таасир этпейт.

Албетте, бардык бул версиялар сакталышы керек. Postgres эстутум менен барактан барак менен иштейт жана барак - бул дисктен окууга же жазууга мүмкүн болгон маалыматтардын минималдуу көлөмү. Мунун кантип болорун түшүнүү үчүн кичинекей бир мисалды карап көрөлү.

Келгиле, бизде бир нече жазууларды кошкон таблица бар дейли. Жадыбал сакталган файлдын биринчи бетинде жаңы маалыматтар пайда болду. Бул саптардын жандуу версиялары, алар милдеттенмеден кийин башка транзакциялар үчүн жеткиликтүү (жөнөкөйлүк үчүн биз изоляция деңгээли Окуу аткарылды деп ойлойбуз).

Postgres: bloat, pg_repack жана кийинкиге калтырылган чектөөлөр

Андан кийин биз жазуулардын бирин жаңыртып, ошону менен эски версияны актуалдуу эмес деп белгиледик.

Postgres: bloat, pg_repack жана кийинкиге калтырылган чектөөлөр

Кадамдык версияларды жаңыртуу жана жок кылуу менен биз маалыматтын болжол менен жарымы "таштанды" болгон баракчага ээ болдук. Бул маалымат эч кандай транзакцияга көрүнбөйт.

Postgres: bloat, pg_repack жана кийинкиге калтырылган чектөөлөр

Postgres механизми бар боштук, бул эскирген версияларды тазалап, жаңы маалыматтарга орун бошотот. Бирок ал жетиштүү түрдө агрессивдүү конфигурацияланбаса же башка таблицаларда иштөө менен алек болсо, анда "таштанды маалыматтар" калат жана биз жаңы маалыматтар үчүн кошумча баракчаларды колдонууга туура келет.

Ошентип, биздин мисалда, кайсы бир убакта таблица төрт барактан турат, бирок анын жарымы гана жандуу маалыматтарды камтыйт. Натыйжада, таблицага киргенде, биз керектүүдөн алда канча көп маалыматтарды окуйбуз.

Postgres: bloat, pg_repack жана кийинкиге калтырылган чектөөлөр

VACUUM азыр бардык тиешеси жок катар версияларын жок кылса да, абал кескин жакшырбайт. Бизде жаңы саптар үчүн барактарда же ал тургай бүтүндөй беттерде бош орун болот, бирок биз дагы эле керектүүдөн көбүрөөк маалыматтарды окуйбуз.
Айтмакчы, эгер файлдын аягында толугу менен бош барак (биздин мисалдагы экинчиси) болсо, анда VACUUM аны кыркып алмак. Бирок азыр ал ортодо болгондуктан, аны менен эч нерсе кыла албайт.

Postgres: bloat, pg_repack жана кийинкиге калтырылган чектөөлөр

Мындай бош же өтө сейрек барактардын саны көбөйгөндө, бул bloat деп аталат, ал аткарууга таасир эте баштайт.

Жогоруда сүрөттөлгөн нерселердин баары таблицаларда толтуруунун пайда болушунун механикасы. Индекстерде бул дээрлик ушундай болот.

Менде шишик барбы?

Сизде шишик бар-жогун аныктоонун бир нече жолу бар. Биринчисинин идеясы - ички Postgres статистикасын колдонуу, анда таблицалардагы саптардын саны, "тирүү" катарлардын саны ж. негиз кылып алганбыз скрипт PostgreSQL Эксперттеринен, алар тост жана bloat btree индекстери менен бирге bloat таблицаларына баа бере алышат. Биздин тажрыйбада анын катасы 10-20% түзөт.

Дагы бир жолу - кеңейтүүнү колдонуу pgstattuple, бул барактардын ичин карап чыгууга жана болжолдуу жана так көлөмдүү маанини алууга мүмкүндүк берет. Бирок, экинчи учурда, сиз бүт таблицаны сканерлөө керек болот.

Биз 20% га чейин бир аз bloat маанисин алгылыктуу деп эсептейбиз. Аны толтуруучу фактордун аналогу катары кароого болот жыйынтык чыгаруу и индекстер. 50% жана андан жогору болсо, аткаруу көйгөйлөрү башталышы мүмкүн.

Шишик менен күрөшүүнүн жолдору

Postgres кутучадан чыккан шишик менен күрөшүү үчүн бир нече жолдору бар, бирок алар ар дайым эле баарына ылайыктуу эмес.

AUTOVACUUMды конфигурациялаңыз, ошентип, шишик пайда болбойт. Тагыраак айтканда, сиз үчүн алгылыктуу деңгээлде кармап туруу үчүн. Бул "капитандын" кеңеши сыяктуу көрүнөт, бирок чындыгында буга жетүү дайыма эле оңой боло бербейт. Мисалы, сизде маалымат схемасына үзгүлтүксүз өзгөртүүлөр менен активдүү иштеп жатасыз же кандайдыр бир маалымат миграциясы жүрүп жатат. Натыйжада, сиздин жүктөө профилиңиз тез-тез өзгөрүшү мүмкүн жана адатта үстөлдөн столго өзгөрүп турат. Бул дайыма бир аз алдыга иштеп, AUTOVACUUM ар бир столдун өзгөрүп турган профилине тууралоо керек дегенди билдирет. Бирок муну жасоо оңой эмес экени анык.

AUTOVACUUM таблицалар менен туруштук бере албастыгынын дагы бир жалпы себеби, бул транзакциялар үчүн жеткиликтүү болгон маалыматтарды тазалоого тоскоол болгон узакка созулган транзакциялар бар. Бул жерде сунуш да ачык - "салма" транзакциялардан арылуу жана активдүү транзакциялардын убактысын азайтуу. Бирок, эгерде сиздин тиркемеңиздеги жүк OLAP жана OLTP гибриди болсо, анда сиз бир эле учурда көп жаңыртууларды жана кыска сурамдарды, ошондой эле узак мөөнөттүү операцияларды аткара аласыз - мисалы, отчет түзүү. Мындай кырдаалда, жүктү ар кандай негиздер боюнча жайылтуу жөнүндө ойлонуу керек, бул алардын ар бирин жакшыраак жөнгө салууга мүмкүндүк берет.

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

AUTOVACUUM орноткон кырдаалда эмне кылуу керек, бирок шишик өсө берет.

команда ВАКУУМ ТОЛУК таблицалардын жана индекстердин мазмунун калыбына келтирет жана аларга тиешелүү маалыматтарды гана калтырат. Бөйрөктү жок кылуу үчүн, ал эң сонун иштейт, бирок аны аткаруу учурунда үстөлдүн эксклюзивдүү кулпусу (AccessExclusiveLock) алынат, ал бул таблицада сурамдарды аткарууга мүмкүндүк бербейт, жада калса тандайт. Эгерде сиз өзүңүздүн кызматыңызды же анын бир бөлүгүн бир нече убакытка (маалымат базасынын жана аппараттык камсыздооңуздун көлөмүнө жараша ондогон мүнөттөн бир нече саатка чейин) токтотууга мүмкүнчүлүгүңүз болсо, анда бул вариант эң жакшы. Тилекке каршы, пландуу тейлөө учурунда ВАКУМ ТОЛУГУ МЕНЕН иштетүүгө убактыбыз жок, ошондуктан бул ыкма бизге туура келбейт.

команда КЛАСТЕР Таблицалардын мазмунун 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 жана кийинкиге калтырылган чектөөлөр
Source: begriffs

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. Сиздин муктаждыктарыңызга ылайык тышкы чечимдерди өзгөртүүдөн коркпоңуз - кээде бул өзүңүздүн кодуңузду өзгөртүүгө караганда натыйжалуураак жана ал тургай жеңилирээк болушу мүмкүн.

Source: www.habr.com

Комментарий кошуу