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 යාන්ත්රණයක් ඇත වකුටු, එය යල්පැන ගිය අනුවාද පිරිසිදු කරන අතර නව දත්ත සඳහා ඉඩ සලසයි. නමුත් එය ප්‍රමාණවත් තරම් ආක්‍රමණශීලී ලෙස වින්‍යාස කර නොමැති නම් හෝ වෙනත් වගු වල කාර්යබහුල නම්, “කසළ දත්ත” ඉතිරිව ඇති අතර, අපට නව දත්ත සඳහා අමතර පිටු භාවිතා කිරීමට සිදුවේ.

එබැවින් අපගේ උදාහරණයේ දී, යම් අවස්ථාවක දී වගුව පිටු හතරකින් සමන්විත වනු ඇත, නමුත් සජීවී දත්ත අඩංගු වන්නේ අඩක් පමණි. එහි ප්රතිඵලයක් වශයෙන්, වගුව වෙත පිවිසීමේදී, අපි අවශ්ය ප්රමාණයට වඩා බොහෝ දත්ත කියවනු ඇත.

Postgres: bloat, pg_repack සහ කල් දැමූ බාධා

VACUUM දැන් අදාල නොවන සියලුම පේළි අනුවාද මකා දැමුවද, තත්වය නාටකාකාර ලෙස දියුණු නොවනු ඇත. අපට නව පේළි සඳහා පිටු වල හෝ සම්පූර්ණ පිටු වල පවා නිදහස් ඉඩක් ඇත, නමුත් අපි තවමත් අවශ්‍ය ප්‍රමාණයට වඩා දත්ත කියවමින් සිටිමු.
මාර්ගය වන විට, සම්පූර්ණ හිස් පිටුවක් (අපගේ උදාහරණයේ දෙවැන්න) ගොනුවේ අවසානයේ තිබුනේ නම්, VACUUM හට එය කපා හැරීමට හැකි වනු ඇත. නමුත් දැන් ඇය මැද සිටින නිසා ඇය සමඟ කිසිවක් කළ නොහැක.

Postgres: bloat, pg_repack සහ කල් දැමූ බාධා

එවැනි හිස් හෝ ඉතා විරල පිටු ගණන විශාල වන විට, එය bloat ලෙස හැඳින්වේ, එය කාර්ය සාධනයට බලපෑම් කිරීමට පටන් ගනී.

ඉහත විස්තර කර ඇති සෑම දෙයක්ම වගු වල ඉදිමීමේ යාන්ත්‍ර විද්‍යාවයි. දර්ශක වලදී මෙය බොහෝ දුරට එකම ආකාරයකින් සිදු වේ.

මට බඩ පිපීම තිබේද?

ඔබට බඩ පිපීම තිබේදැයි තීරණය කිරීමට ක්රම කිහිපයක් තිබේ. පළමු අදහස නම්, වගු වල පේළි ගණන, "සජීවී" පේළි ගණන යනාදිය පිළිබඳ ආසන්න තොරතුරු අඩංගු අභ්‍යන්තර Postgres සංඛ්‍යාලේඛන භාවිතා කිරීමයි. ඔබට අන්තර්ජාලයේ සූදානම් කළ ස්ක්‍රිප්ට් වල බොහෝ වෙනස්කම් සොයාගත හැකිය. අපි පදනමක් ලෙස ගත්තා පිටපත PostgreSQL විශේෂඥයින්ගෙන්, ටෝස්ට් සහ bloat btree දර්ශක සමඟින් bloat tables ඇගයීමට හැකිය. අපගේ අත්දැකීම් අනුව, එහි දෝෂය 10-20% කි.

තවත් ක්රමයක් වන්නේ දිගුව භාවිතා කිරීමයි pgstattuple, එය ඔබට පිටු ඇතුළත බැලීමට සහ ඇස්තමේන්තුගත සහ නිශ්චිත bloat අගයක් ලබා ගැනීමට ඉඩ සලසයි. නමුත් දෙවන අවස්ථාවේදී, ඔබට සම්පූර්ණ වගුව පරිලෝකනය කිරීමට සිදුවනු ඇත.

අපි පිළිගත හැකි 20% දක්වා කුඩා bloat අගයක් සලකමු. එය පිරවුම්කාරකයේ ප්‍රතිසමයක් ලෙස සැලකිය හැකිය වගු и දර්ශක. 50% සහ ඊට වැඩි, කාර්ය සාධන ගැටළු ආරම්භ විය හැක.

ඉදිමීමට එරෙහිව සටන් කිරීමේ මාර්ග

Postgres හට පෙට්ටියෙන් පිටත ඉදිමීම සමඟ කටයුතු කිරීමට ක්රම කිහිපයක් ඇත, නමුත් ඒවා සෑම විටම සෑම කෙනෙකුටම සුදුසු නොවේ.

බඩ පිපීම සිදු නොවන පරිදි AUTOVACUUM වින්‍යාස කරන්න. නැතහොත් වඩාත් නිවැරදිව, එය ඔබට පිළිගත හැකි මට්ටමක තබා ගැනීමට. මෙය "කැප්ටන්" උපදෙස් මෙන් පෙනේ, නමුත් යථාර්ථයේ දී මෙය සාක්ෂාත් කර ගැනීම සැමවිටම පහසු නොවේ. උදාහරණයක් ලෙස, ඔබට දත්ත ක්‍රමලේඛයට නිතිපතා වෙනස් කිරීම් සමඟ ක්‍රියාකාරී සංවර්ධනයක් ඇත, නැතහොත් යම් ආකාරයක දත්ත සංක්‍රමණයක් සිදුවෙමින් පවතී. එහි ප්‍රතිඵලයක් වශයෙන්, ඔබේ පැටවුම් පැතිකඩ නිතර වෙනස් විය හැකි අතර සාමාන්‍යයෙන් වගුවෙන් වගුවට වෙනස් වේ. මෙයින් අදහස් කරන්නේ ඔබ නිරන්තරයෙන් මඳක් ඉදිරියෙන් වැඩ කිරීමට සහ එක් එක් වගුවේ වෙනස් වන පැතිකඩට AUTOVACUUM සකස් කළ යුතු බවයි. නමුත් මෙය කිරීම පහසු නොවන බව පැහැදිලිය.

AUTOVACUUM හට වගු සමඟ තබා ගැනීමට නොහැකි වීමට තවත් පොදු හේතුවක් වන්නේ එම ගනුදෙනු සඳහා ලබා ගත හැකි දත්ත පිරිසිදු කිරීමෙන් වළක්වන දිගු කාලීන ගනුදෙනු පවතින බැවිනි. මෙහි නිර්දේශය ද පැහැදිලිය - "එල්ලෙන" ගනුදෙනු ඉවත් කර සක්‍රීය ගනුදෙනු කාලය අවම කරන්න. නමුත් ඔබගේ යෙදුමේ බර OLAP සහ OLTP දෙමුහුන් එකක් නම්, ඔබට එකවර බොහෝ යාවත්කාලීන කිරීම් සහ කෙටි විමසුම් මෙන්ම දිගු කාලීන මෙහෙයුම් තිබිය හැකිය - උදාහරණයක් ලෙස, වාර්තාවක් තැනීම. එවැනි තත්වයක් තුළ, විවිධ පදනම් හරහා බර පැටවීම ගැන සිතීම වටී, එමඟින් එක් එක් ඒවා වඩාත් හොඳින් සකස් කිරීමට ඉඩ සලසයි.

තවත් උදාහරණයක් - පැතිකඩ සමජාතීය වුවද, දත්ත සමුදාය ඉතා ඉහළ බරක් යටතේ පවතී නම්, වඩාත් ආක්‍රමණශීලී AUTOVACUUM පවා එයට මුහුණ දිය නොහැකි අතර ඉදිමීම සිදුවනු ඇත. පරිමාණය (සිරස් හෝ තිරස්) එකම විසඳුමයි.

ඔබ AUTOVACUUM පිහිටුවා ඇති තත්වයක් තුළ කුමක් කළ යුතුද, නමුත් බඩ පිපීම දිගටම වර්ධනය වේ.

කණ්ඩායම රික්තකය සම්පූර්ණයි වගු සහ දර්ශකවල අන්තර්ගතය නැවත ගොඩනඟන අතර ඒවාට අදාළ දත්ත පමණක් ඉතිරි කරයි. බඩ පිපීම තුරන් කිරීම සඳහා, එය පරිපූර්ණව ක්‍රියා කරයි, නමුත් එය ක්‍රියාත්මක කිරීමේදී මේසය මත ඇති සුවිශේෂී අගුලක් අල්ලා ගනු ලැබේ (AccessExclusiveLock), එමඟින් මෙම වගුවේ විමසුම් ක්‍රියාත්මක කිරීමට ඉඩ නොදේ, තෝරා ගනී. ඔබට යම් කාලයක් සඳහා ඔබේ සේවාව හෝ එහි කොටසක් නැවැත්වීමට හැකි නම් (දත්ත සමුදායේ ප්‍රමාණය සහ ඔබේ දෘඩාංග අනුව මිනිත්තු දහයක සිට පැය කිහිපයක් දක්වා), මෙම විකල්පය හොඳම වේ. අවාසනාවකට, නියමිත නඩත්තු කාලය තුළ VACUUM FULL ධාවනය කිරීමට අපට කාලය නොමැති බැවින් මෙම ක්‍රමය අපට සුදුසු නොවේ.

කණ්ඩායම ක්ලස්ටර් වගු වල අන්තර්ගතය VACUUM FULL ලෙසම නැවත ගොඩනඟයි, නමුත් දත්ත භෞතිකව තැටියේ ඇණවුම් කරන දර්ශකයක් නියම කිරීමට ඔබට ඉඩ සලසයි (නමුත් අනාගතයේදී නව පේළි සඳහා ඇණවුම සහතික නොවේ). ඇතැම් අවස්ථා වලදී, මෙය විමසුම් ගණනාවක් සඳහා හොඳ ප්‍රශස්තකරණයකි - දර්ශකය අනුව වාර්තා කිහිපයක් කියවීම සමඟ. විධානයේ අවාසිය VACUUM FULL ට සමාන වේ - එය ක්‍රියාත්මක වන විට මේසය අගුළු දමයි.

කණ්ඩායම REINDEX පෙර දෙකට සමාන නමුත් නිශ්චිත දර්ශකයක් හෝ වගුවේ සියලුම දර්ශක නැවත ගොඩනඟයි. අගුලු තරමක් දුර්වලයි: මේසය මත ShareLock (වෙනස් කිරීම් වළක්වයි, නමුත් තේරීමට ඉඩ දෙයි) සහ නැවත ගොඩනඟන ලද දර්ශකයේ AccessExclusiveLock (මෙම දර්ශකය භාවිතයෙන් විමසුම් අවහිර කරයි). කෙසේ වෙතත්, Postgres හි 12 වන අනුවාදයේ පරාමිතියක් දර්ශනය විය සමගාමීව, සමගාමී එකතු කිරීම, වෙනස් කිරීම හෝ වාර්තා මකාදැමීම අවහිර නොකර දර්ශකය නැවත ගොඩනැගීමට ඔබට ඉඩ සලසයි.

Postgres හි පෙර අනුවාද වල, ඔබට සමගාමීව භාවිතා කරන REINDEX වැනි ප්‍රතිඵලයක් ලබා ගත හැක දර්ශකය සමගාමීව සාදන්න. එය ඔබට දැඩි අගුලු දැමීමකින් තොරව දර්ශකයක් නිර්මාණය කිරීමට ඉඩ සලසයි (ShareUpdateExclusiveLock, සමාන්තර විමසුම්වලට බාධා නොකරන), පසුව පැරණි දර්ශකය නව එකක් සමඟ ප්රතිස්ථාපනය කර පැරණි දර්ශකය මකා දමන්න. ඔබගේ යෙදුමට බාධා නොකර දර්ශක ඉදිමීම ඉවත් කිරීමට මෙය ඔබට ඉඩ සලසයි. දර්ශක නැවත ගොඩනඟන විට තැටි උප පද්ධතිය මත අමතර බරක් ඇති බව සැලකිල්ලට ගැනීම වැදගත්ය.

මේ අනුව, දර්ශක සඳහා "පියාඹන විට" ඉදිමීම ඉවත් කිරීමට ක්රම තිබේ නම්, වගු සඳහා කිසිවක් නොමැත. විවිධ බාහිර දිගු ක්‍රියාත්මක වන්නේ මෙහිදීය: pg_repack (කලින් pg_reorg), pgcompact, pgcompactable සහ වෙනත් අය. මෙම ලිපියෙන්, මම ඒවා සංසන්දනය නොකරන අතර 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 විසින් නිර්මාණය කරන ලදී. එහි සංයුතියට ඇතුළත් කර ඇති ගුණාංග මත පදනම්ව, එයට අනුරූප වන "අපගේ" සීමාව අපි තීරණය කළෙමු. ගැටලුව වූයේ මෙය සම්පූර්ණයෙන්ම සාමාන්‍ය සීමාවක් නොව කල් දැමූ එකක් වීමයි (කල් දැමූ සීමාව), i.e. එහි සත්‍යාපනය 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 සහ කල් දැමූ බාධා
මූලාශ්රය: 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 නැවැත්වීමට සිදු විය.

එය සිදු වූයේ ඇයි? දෝෂයක් ඇතිවීමේ සම්භාවිතාව රඳා පවතින්නේ එකම විජට් සමඟ එකවර කොපමණ පරිශීලකයින් ක්‍රියා කරන්නේද යන්න මතය. පෙනෙන විදිහට, ඒ මොහොතේ අනෙක් ඒවාට වඩා පළමු පොකුරේ ගබඩා කර ඇති දත්ත සමඟ තරඟකාරී වෙනස්කම් ඉතා අඩු විය, i.e. අපි හුදෙක් "වාසනාවන්ත" විය.

අදහස වැඩ කළේ නැහැ. එම අවස්ථාවේදී, අපි වෙනත් විසඳුම් දෙකක් දුටුවෙමු: කල් දැමූ බාධාවන් ඉවත් කිරීමට අපගේ යෙදුම් කේතය නැවත ලියන්න, නැතහොත් ඒවා සමඟ වැඩ කිරීමට 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 මිල අධිකයි, දර්ශකයක් අවශ්‍ය වේ, නමුත් එය යාවත්කාලීන කිරීමේ පොදු කාර්ය නිසා ක්‍රමය මන්දගාමී කරයි. පොදුවේ, සෑම විටම මෙන්, ඔබ යමක් කැප කළ යුතුය.

එබැවින්, එය සරල වන බැවින්, පළමු විකල්පය සමඟ ආරම්භ කිරීමට අපි තීරණය කළා. පළමුව, දිගු ගනුදෙනුවක් සැබෑ ගැටළුවක් වේද යන්න තේරුම් ගැනීමට අවශ්ය විය. පැරණි වගුවේ සිට නව එකට දත්ත ප්‍රධාන මාරු කිරීම ද එක් දිගු ගනුදෙනුවක සිදුවන බැවින්, ප්‍රශ්නය “අපි මෙම ගනුදෙනුව කොපමණ වැඩි කරනවාද?” බවට පරිවර්තනය විය. පළමු ගනුදෙනුවේ කාලසීමාව ප්රධාන වශයෙන් මේසයේ ප්රමාණය මත රඳා පවතී. නව එකක කාලසීමාව රඳා පවතින්නේ දත්ත මාරු කිරීමේදී වගුවේ කොපමණ වෙනස්කම් එකතු වේද යන්න මතය, i.e. බර පැටවීමේ තීව්රතාව මත. pg_repack ධාවනය සිදු වූයේ අවම සේවා බරක් පවතින කාලයකදී වන අතර, මේසයේ මුල් ප්‍රමාණයට සාපේක්ෂව වෙනස්වීම් පරිමාව අසමාන ලෙස කුඩා විය. නව ගනුදෙනුවක කාලය නොසලකා හැරිය හැකි බව අපි තීරණය කළෙමු (සැසඳීම සඳහා, සාමාන්යයෙන් එය පැය 1 යි විනාඩි 2-3 කි).

අත්හදා බැලීම් ධනාත්මක විය. නිෂ්පාදනයටත් දියත් කරන්න. පැහැදිලිකම සඳහා, ධාවනය කිරීමෙන් පසු එක් දත්ත සමුදායක විශාලත්වය සහිත පින්තූරයක් මෙන්න:

Postgres: bloat, pg_repack සහ කල් දැමූ බාධා

මෙම විසඳුම පිළිබඳව අප සම්පුර්ණයෙන්ම සෑහීමකට පත් වූ බැවින්, අපි දෙවැන්න ක්‍රියාත්මක කිරීමට උත්සාහ නොකළ නමුත් එය දිගු කිරීමේ සංවර්ධකයින් සමඟ සාකච්ඡා කිරීමේ හැකියාව අපි සලකා බලමු. අපගේ වර්තමාන සංශෝධනය, අවාසනාවකට මෙන්, තවමත් ප්‍රකාශනයට සූදානම් නැත, මන්ද අපි ගැටලුව විසඳා ඇත්තේ අද්විතීය කල් දැමූ සීමාවන් සමඟ පමණක් වන අතර සම්පූර්ණ පැච් එකක් සඳහා වෙනත් වර්ග සඳහා සහය ලබා දීම අවශ්‍ය වේ. අනාගතයේදී මෙය කිරීමට හැකි වනු ඇතැයි අපි බලාපොරොත්තු වෙමු.

සමහර විට ඔබට ප්‍රශ්නයක් තිබිය හැකිය, අපි pg_repack වෙනස් කිරීම සමඟ මෙම කතාවට සම්බන්ධ වූයේ ඇයිද, උදාහරණයක් ලෙස එහි ප්‍රතිසම භාවිතා නොකළේ ඇයි? යම් අවස්ථාවක දී අපි ද මේ ගැන සිතුවෙමු, නමුත් එය කලින් භාවිතා කිරීමේ ධනාත්මක අත්දැකීම, කල් දැමූ සීමාවන් නොමැතිව වගු මත, ගැටලුවේ සාරය තේරුම් ගැනීමට සහ එය විසඳීමට උත්සාහ කිරීමට අපව පොලඹවන ලදී. ඊට අමතරව, වෙනත් විසඳුම් භාවිතා කිරීම සඳහා පරීක්ෂණ පැවැත්වීමට කාලය අවශ්‍ය වේ, එබැවින් අපි මුලින්ම එහි ඇති ගැටළුව විසඳීමට උත්සාහ කරන බව අපි තීරණය කළ අතර, අපට මෙය සාධාරණ කාලයක් තුළ කළ නොහැකි බව අපට වැටහුවහොත්, අපි ප්‍රතිසමයන් දෙස බැලීමට පටන් ගනිමු. .

සොයා ගැනීම්

අපගේම අත්දැකීම් මත පදනම්ව අපට නිර්දේශ කළ හැකි දේ:

  1. ඔබේ බඩ පිපීම නිරීක්ෂණය කරන්න. අධීක්ෂණ දත්ත මත පදනම්ව, autovacuum වින්‍යාස කර ඇති ආකාරය ඔබට තේරුම් ගත හැකිය.
  2. ඉදිමීම පිළිගත හැකි මට්ටමක තබා ගැනීමට AUTOVACUUM සකසන්න.
  3. බඩ පිපීම තවමත් වර්ධනය වෙමින් පවතී නම් සහ පෙට්ටියෙන් පිටත මෙවලම් භාවිතයෙන් ඔබට එය ජය ගත නොහැකි නම්, බාහිර දිගු භාවිතා කිරීමට බිය නොවන්න. ප්රධාන දෙය නම් සෑම දෙයක්ම හොඳින් පරීක්ෂා කිරීමයි.
  4. ඔබගේ අවශ්‍යතාවයට ගැලපෙන පරිදි බාහිර විසඳුම් වෙනස් කිරීමට බිය නොවන්න - සමහර විට මෙය ඔබගේම කේතය වෙනස් කිරීමට වඩා ඵලදායී සහ පහසු විය හැක.

මූලාශ්රය: www.habr.com

අදහස් එක් කරන්න