پوسٽ گريس: بلوٽ، pg_repack ۽ ملتوي رڪاوٽون

پوسٽ گريس: بلوٽ، pg_repack ۽ ملتوي رڪاوٽون

ٽيبل ۽ انڊيڪس تي بلوٽ جو اثر وڏي پيماني تي معلوم ٿئي ٿو ۽ نه رڳو پوسٽ گريس ۾ موجود آهي. ان کي باڪس کان ٻاهر ڊيل ڪرڻ جا طريقا آهن، جهڙوڪ VACUUM FULL يا CLUSTER، پر اهي آپريشن دوران ٽيبل کي بند ڪري ڇڏيندا آهن ۽ ان ڪري هميشه استعمال نٿا ڪري سگهجن.

آرٽيڪل ۾ ٿورو نظريو هوندو ته بلوٽ ڪيئن ٿئي ٿو، توهان ان سان ڪيئن وڙهندا، ملتوي ٿيل رڪاوٽن بابت ۽ اهي مسئلا جيڪي اهي pg_repack ايڪسٽينشن جي استعمال ۾ آڻيندا آهن.

هي مضمون ان بنياد تي لکيو ويو آهي منهنجي ڳالهه PgConf.Russia 2020 تي.

ڦوٽو ڇو ٿئي ٿو؟

Postgres هڪ ملٽي ورزن ماڊل تي ٻڌل آهي (ايم وي سي سي). ان جو خلاصو اهو آهي ته ٽيبل ۾ هر قطار جا ڪيترائي نسخا ٿي سگهن ٿا، جڏهن ته ٽرانزيڪشن انهن نسخن مان هڪ کان وڌيڪ نه ڏسندا آهن، پر ضروري ناهي ته هڪ ئي. اهو ڪيترن ئي ٽرانزيڪشن کي هڪ ئي وقت ڪم ڪرڻ جي اجازت ڏئي ٿو ۽ عملي طور تي هڪ ٻئي تي ڪو به اثر نه آهي.

ظاهر آهي، انهن سڀني نسخن کي ذخيرو ڪرڻ جي ضرورت آهي. Postgres ميموري صفحي سان صفحي جي حساب سان ڪم ڪري ٿو ۽ ھڪڙو صفحو گھٽ ۾ گھٽ ڊيٽا آھي جيڪو ڊسڪ مان پڙھي سگھجي ٿو يا لکيل آھي. اچو ته هڪ ننڍڙو مثال ڏسون ته اهو ڪيئن ٿئي.

اچو ته اسان وٽ هڪ ٽيبل آهي جنهن ۾ اسان ڪيترائي رڪارڊ شامل ڪيا آهن. نئين ڊيٽا فائل جي پهرين صفحي تي ظاهر ٿيو آهي جتي ٽيبل محفوظ ٿيل آهي. اهي قطارن جا لائيو ورجن آهن جيڪي ڪمٽمينٽ کان پوءِ ٻين ٽرانزيڪشن لاءِ دستياب آهن (سادگي لاءِ، اسان اهو سمجهون ٿا ته اڪيلائي جي سطح پڙهيل آهي).

پوسٽ گريس: بلوٽ، pg_repack ۽ ملتوي رڪاوٽون

اسان ان کان پوءِ داخلائن مان ھڪڙي کي اپڊيٽ ڪيو، اھڙي طرح پراڻي ورزن کي ھاڻي لاڳاپو نه ھجڻ ڪري نشان لڳايو.

پوسٽ گريس: بلوٽ، pg_repack ۽ ملتوي رڪاوٽون

قدم بہ قدم، قطار جي ورزن کي اپڊيٽ ڪرڻ ۽ حذف ڪرڻ، اسان ھڪڙي صفحي سان ختم ڪيو جنھن ۾ تقريبا اڌ ڊيٽا "کچرو" آھي. هي ڊيٽا ڪنهن به ٽرانزيڪشن کي نظر نٿو اچي.

پوسٽ گريس: بلوٽ، pg_repack ۽ ملتوي رڪاوٽون

Postgres هڪ ميکانيزم آهي ويڪيوم، جيڪو ختم ٿيل نسخن کي صاف ڪري ٿو ۽ نئين ڊيٽا لاءِ ڪمرو ٺاهي ٿو. پر جيڪڏهن اهو ڪافي جارحتي طور تي ترتيب نه ڏنو ويو آهي يا ٻين جدولن ۾ ڪم ڪرڻ ۾ مصروف آهي، ته پوء "ڪچرا ڊيٽا" رهي ٿو، ۽ اسان کي نئين ڊيٽا لاء اضافي صفحا استعمال ڪرڻو پوندو.

تنهن ڪري اسان جي مثال ۾، ڪنهن وقت ۾ ٽيبل چار صفحن تي مشتمل هوندو، پر ان جو اڌ حصو زنده ڊيٽا تي مشتمل هوندو. نتيجي طور، جدول تائين پهچندي، اسان ضروري کان وڌيڪ ڊيٽا پڙهي سگهنداسين.

پوسٽ گريس: بلوٽ، pg_repack ۽ ملتوي رڪاوٽون

ايستائين جو VACUUM هاڻي سڀني غير لاڳاپيل قطار نسخن کي ختم ڪري ٿو، صورتحال ڊرامي طور تي بهتر نه ٿيندي. نون قطارن لاءِ اسان وٽ صفحا يا اڃا پوري صفحا ۾ خالي جاءِ هوندي، پر پوءِ به اسان ضرورت کان وڌيڪ ڊيٽا پڙهندا رهياسين.
رستي ۾، جيڪڏهن مڪمل طور تي خالي صفحو (اسان جي مثال ۾ ٻيو) فائل جي آخر ۾ هئا، پوء VACUUM ان کي ٽرم ڪرڻ جي قابل هوندو. پر هاڻي هوءَ وچ ۾ آهي، تنهن ڪري هن سان ڪجهه به نٿو ڪري سگهجي.

پوسٽ گريس: بلوٽ، pg_repack ۽ ملتوي رڪاوٽون

جڏهن اھڙن خالي يا تمام گھڻن صفحن جو تعداد وڏو ٿئي ٿو، جنھن کي بلوٽ سڏيو ويندو آھي، اھو ڪارڪردگي تي اثر انداز ٿيڻ شروع ڪري ٿو.

مٿي بيان ڪيل هر شي ٽيبل ۾ بلوٽ جي واقعن جي ميڪيڪل آهي. انڊيڪس ۾ اهو گهڻو ڪري ساڳيو طريقي سان ٿئي ٿو.

ڇا مون کي گل آهي؟

اهو طئي ڪرڻ جا ڪيترائي طريقا آهن ته توهان کي ڦوٽو آهي. پهرين جو خيال آهي اندروني پوسٽ گريس انگن اکرن کي استعمال ڪرڻ، جنهن ۾ جدولن ۾ قطارن جي تعداد، "لائيو" قطارن جو تعداد، وغيره بابت تقريبن معلومات شامل آهي. توهان انٽرنيٽ تي تيار ڪيل اسڪرپٽ جا ڪيترائي مختلف قسم ڳولي سگهو ٿا. اسان هڪ بنياد طور ورتو اسڪرپٽ PostgreSQL ماهرن کان، جيڪي ٽوسٽ ۽ بلوٽ بيٽري انڊيڪس سان گڏ بلوٽ ٽيبلز جو جائزو وٺي سگھن ٿا. اسان جي تجربي ۾، ان جي غلطي 10-20٪ آهي.

ٻيو طريقو آهي واڌارو استعمال ڪرڻ pgstattuple، جيڪو توهان کي صفحن جي اندر ڏسڻ جي اجازت ڏئي ٿو ۽ ٻنهي جو اندازو ۽ صحيح بلوٽ قيمت حاصل ڪري ٿو. پر ٻئي صورت ۾، توهان کي سڄي ٽيبل کي اسڪين ڪرڻو پوندو.

اسان هڪ ننڍڙي بلوٽ جي قيمت تي غور ڪيو، 20٪ تائين، قابل قبول. هن لاء fillfactor جي هڪ analogue سمجهي سگهجي ٿو ٽيبل и اشارا. 50٪ ۽ مٿي تي، ڪارڪردگي مسئلا شروع ٿي سگھي ٿي.

ڦڦڙن کي منهن ڏيڻ جا طريقا

پوسٽ گريس وٽ دٻي مان بلوٽ سان معاملو ڪرڻ جا ڪيترائي طريقا آهن، پر اهي هميشه هر ڪنهن لاءِ مناسب نه هوندا آهن.

AUTOVACUUM ترتيب ڏيو ته جيئن بلوٽ نه ٿئي. يا وڌيڪ صحيح طور تي، ان کي برقرار رکڻ لاء هڪ سطح تي توهان کي قابل قبول آهي. اهو لڳي ٿو "ڪپتان جي" صلاح، پر حقيقت ۾ اهو هميشه حاصل ڪرڻ آسان ناهي. مثال طور، توهان وٽ ڊيٽا اسڪيما ۾ باقاعده تبديلين سان فعال ترقي آهي، يا ڪنهن قسم جي ڊيٽا لڏپلاڻ ٿي رهي آهي. نتيجي طور، توھان جي لوڊ پروفائل اڪثر تبديل ٿي سگھي ٿي ۽ عام طور تي ٽيبل کان ٽيبل تائين مختلف ٿي سگھي ٿي. هن جو مطلب آهي ته توهان کي مسلسل ٿورو اڳتي ڪم ڪرڻ جي ضرورت آهي ۽ هر ٽيبل جي بدلجندڙ پروفائل ۾ AUTOVACUUM کي ترتيب ڏيڻ جي ضرورت آهي. پر ظاهر آهي ته اهو ڪم آسان ناهي.

ٻيو عام سبب ڇو AUTOVACUUM جدولن سان گڏ نه ٿو رهي سگهي ڇو ته اتي ڊگھي هلندڙ ٽرانزيڪشن آهن جيڪي ان کي انهن ٽرانزيڪشن لاءِ موجود ڊيٽا کي صاف ڪرڻ کان روڪين ٿيون. هتي جي سفارش پڻ واضح آهي - "ڏنگڻ" ٽرانزيڪشن کان نجات حاصل ڪريو ۽ فعال ٽرانزيڪشن جي وقت کي گھٽايو. پر جيڪڏهن توهان جي ايپليڪيشن تي لوڊ OLAP ۽ OLTP جو هڪ هائبرڊ آهي، ته پوءِ توهان هڪ ئي وقت ۾ ڪيترائي بار بار اپ ڊيٽ ۽ مختصر سوال ڪري سگهو ٿا، انهي سان گڏ ڊگهي مدي وارا آپريشن - مثال طور، رپورٽ ٺاهڻ. اهڙي صورتحال ۾، اهو سوچڻ جي قابل آهي ته لوڊ کي مختلف بنيادن تي پکڙيل آهي، جيڪو انهن مان هر هڪ کي وڌيڪ سٺي ترتيب ڏيڻ جي اجازت ڏيندو.

ٻيو مثال - جيتوڻيڪ پروفائل هڪجهڙائي وارو آهي، پر ڊيٽابيس هڪ تمام گهڻي لوڊ هيٺ آهي، تڏهن به سڀ کان وڌيڪ جارحتي AUTOVACUUM شايد منهن نه ڪري سگهي، ۽ ڦوٽو ٿيندو. اسڪيلنگ (عمودي يا افقي) واحد حل آهي.

اهڙي صورتحال ۾ ڇا ڪجي جتي توهان AUTOVACUUM قائم ڪيو آهي، پر بلوٽ وڌندو رهي ٿو.

ٽيم ويڪيوم مڪمل جدولن ۽ انڊيڪس جي مواد کي ٻيهر ٺاهي ٿو ۽ انهن ۾ صرف لاڳاپيل ڊيٽا ڇڏي ٿو. بلوٽ کي ختم ڪرڻ لاء، اهو مڪمل طور تي ڪم ڪري ٿو، پر ان جي عمل جي دوران ٽيبل تي هڪ خاص تالا (AccessExclusiveLock) تي قبضو ڪيو ويو آهي، جيڪو هن ميز تي سوالن تي عمل ڪرڻ جي اجازت نه ڏيندو، جيتوڻيڪ چونڊيندو. جيڪڏهن توهان برداشت ڪري سگهو ٿا توهان جي خدمت يا ان جو حصو ڪجهه وقت لاءِ (ڏهن منٽن کان ڪيترن ڪلاڪن تائين منحصر ڊيٽابيس جي سائيز ۽ توهان جي هارڊويئر جي لحاظ سان) ته پوءِ هي اختيار بهترين آهي. بدقسمتي سان، اسان وٽ مقرر ڪيل سار سنڀال دوران VACUUM FULL هلائڻ جو وقت نه آهي، تنهنڪري اهو طريقو اسان لاء مناسب ناهي.

ٽيم ڪلسٽر جدولن جي مواد کي VACUUM FULL وانگر ساڳي طرح ٻيهر ٺاھيو، پر توھان کي ھڪڙي انڊيڪس جي وضاحت ڪرڻ جي اجازت ڏئي ٿي جنھن مطابق ڊيٽا جسماني طور تي ڊسڪ تي ترتيب ڏني ويندي (پر مستقبل ۾ آرڊر نئين قطار لاء ضمانت نه آھي). ڪجهه حالتن ۾، اهو ڪيترن ئي سوالن لاء هڪ سٺو اصلاح آهي - انڊيڪس طرفان ڪيترن ئي رڪارڊ پڙهڻ سان. حڪم جو نقصان VACUUM FULL جي برابر آهي - اهو آپريشن دوران ٽيبل کي لاڪ ڪري ٿو.

ٽيم REINDEX پوئين ٻن وانگر، پر هڪ مخصوص انڊيڪس يا ٽيبل جي سڀني انڊيڪس کي ٻيهر ٺاهي ٿو. لاڪ ٿورا ڪمزور آهن: ٽيبل تي شيئر لاڪ (تبديل کي روڪي ٿو، پر چونڊ جي اجازت ڏئي ٿو) ۽ AccessExclusiveLock انڊيڪس تي ٻيهر تعمير ٿي رهيو آهي (هن انڊيڪس استعمال ڪندي سوالن کي بلاڪ ڪري ٿو). بهرحال، پوسٽ گريس جي 12 هين ورزن ۾ هڪ پيٽرولر ظاهر ٿيو اتفاق سان, جيڪو توهان کي اجازت ڏئي ٿو انڊيڪس کي ٻيهر تعمير ڪرڻ کان سواءِ سمورو اضافو، ترميم، يا رڪارڊ کي ختم ڪرڻ کي.

Postgres جي اڳئين ورزن ۾، توھان حاصل ڪري سگھو ٿا ھڪڙو نتيجو ساڳيو REINDEX CONCURRENTLY استعمال ڪندي ٺاھيو انڊيڪس گڏوگڏ. اهو توهان کي اجازت ڏئي ٿو هڪ انڊيڪس ٺاهڻ جي بغير سخت لاڪنگ (ShareUpdateExclusiveLock، جيڪو متوازي سوالن سان مداخلت نٿو ڪري)، پوء پراڻي انڊيڪس کي نئين سان تبديل ڪريو ۽ پراڻي انڊيڪس کي حذف ڪريو. هي توهان کي توهان جي ايپليڪيشن سان مداخلت ڪرڻ کان سواء انڊيڪس بلوٽ کي ختم ڪرڻ جي اجازت ڏئي ٿو. اهو غور ڪرڻ ضروري آهي ته انڊيڪس کي ٻيهر تعمير ڪرڻ وقت ڊسڪ سب سسٽم تي اضافي لوڊ ٿيندو.

اهڙيء طرح، جيڪڏهن انڊيڪسس لاء "فلائي تي" بلوٽ کي ختم ڪرڻ جا طريقا آهن، پوء ٽيبل لاء ڪو به ناهي. هي آهي جتي مختلف خارجي توسيعون راند ۾ اچن ٿيون: pg_repack (اڳوڻي pg_reorg) pgcompact, pg compacttable ۽ ٻيا. هن آرٽيڪل ۾، آئون انهن جو مقابلو نه ڪندس ۽ صرف pg_repack بابت ڳالهائيندس، جيڪو، ڪجهه ترميمن کان پوء، اسان پاڻ کي استعمال ڪندا آهيون.

ڪيئن pg_repack ڪم ڪري ٿو

پوسٽ گريس: بلوٽ، pg_repack ۽ ملتوي رڪاوٽون
اچو ته چوندا آهن ته اسان وٽ هڪ مڪمل طور تي عام ٽيبل آهي - انڊيڪس، پابنديون ۽ بدقسمتي سان، بلوٽ سان. pg_repack جو پهريون قدم اهو آهي لاگ ٽيبل ٺاهڻ لاءِ سڀني تبديلين بابت ڊيٽا محفوظ ڪرڻ لاءِ جڏهن اهو هلندڙ آهي. ٽريگر انهن تبديلين کي هر داخل ڪرڻ، تازه ڪاري ۽ حذف ڪرڻ لاء نقل ڪندو. پوءِ ھڪڙي ٽيبل ٺاھيو ويو آھي، اصل ھڪڙي جھڙي ساخت ۾، پر انڊيڪس ۽ پابندين کان سواء، جيئن ڊيٽا داخل ڪرڻ جي عمل کي سست نه ڪيو وڃي.

اڳيون، pg_repack ڊيٽا کي پراڻي جدول مان منتقل ڪري ٿو نئين ٽيبل تي، خودڪار طريقي سان فلٽر ڪري ٿو سڀني غير لاڳاپيل قطارن کي، ۽ پوءِ نئين ٽيبل لاءِ انڊيڪس ٺاهي ٿو. انهن سڀني عملن جي عمل جي دوران، تبديليون لاگ ٽيبل ۾ جمع ٿينديون آهن.

ايندڙ قدم تبديلين کي نئين ٽيبل تي منتقل ڪرڻ آهي. لڏپلاڻ ڪيترن ئي ورهاڱي تي ڪئي وئي آهي، ۽ جڏهن لاگ ٽيبل ۾ 20 کان گهٽ داخلا رهجي ويا آهن، pg_repack هڪ مضبوط تالا حاصل ڪري ٿو، تازه ترين ڊيٽا کي لڏپلاڻ ڪري ٿو، ۽ پوسٽ گريس سسٽم جي ٽيبل ۾ پراڻي ٽيبل کي نئين سان تبديل ڪري ٿو. هي واحد ۽ تمام ننڍو وقت آهي جڏهن توهان ٽيبل سان ڪم ڪرڻ جي قابل نه هوندا. ان کان پوء، پراڻي ٽيبل ۽ لاگز سان ميز کي ختم ڪيو ويو آهي ۽ فائل سسٽم ۾ جاء خالي ڪئي وئي آهي. عمل مڪمل آهي.

نظريي ۾ ته سڀ ڪجهه شاندار نظر اچي ٿو، پر عمل ۾ ڇا ٿو ٿئي؟ اسان آزمايو pg_repack بغير لوڊ ۽ لوڊ جي، ۽ ان جي آپريشن کي وقت کان اڳ بند ٿيڻ جي صورت ۾ چيڪ ڪيو (ٻين لفظن ۾، Ctrl+C استعمال ڪندي). سڀ ٽيسٽ مثبت هئا.

اسان کاڌي جي دڪان تي وياسون - ۽ پوءِ سڀ ڪجهه ائين نه ٿيو جيئن اسان توقع ڪئي هئي.

وڪري تي پهريون pancake

پهرئين ڪلستر تي اسان کي هڪ نقص ملي هڪ منفرد رڪاوٽ جي خلاف ورزي بابت:

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

هن حد تائين هڪ خودڪار ٺاهيل نالو انڊيڪس_16508 هو - اهو ٺهيل هو pg_repack. ان جي جوڙجڪ ۾ شامل ڪيل خاصيتن جي بنياد تي، اسان "اسان" جي پابندي جو تعين ڪيو جيڪو ان سان ملندو. مسئلو اهو نڪتو ته اها مڪمل طور تي عام حد نه آهي، پر هڪ ملتوي ٿيل (ملتوي پابندي)، يعني ان جي تصديق sql ڪمانڊ کان پوءِ ڪئي ويندي آهي، جنهن جي نتيجي ۾ اڻڄاتل نتيجا نڪرندا آهن.

ملتوي رڪاوٽون: انهن جي ضرورت ڇو آهي ۽ اهي ڪيئن ڪم ڪن ٿا

ٿورڙي نظريو ملتوي پابندين بابت.
اچو ته هڪ سادي مثال تي غور ڪريو: اسان وٽ ڪارن جو هڪ ٽيبل ريفرنس ڪتاب آهي جنهن ۾ ٻه خاصيتون آهن - ڊاريڪٽري ۾ ڪار جو نالو ۽ ترتيب.
پوسٽ گريس: بلوٽ، 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
);

جيئن ته اسان جي ابتدائي درخواست جي منطق کي يقيني بڻائي ٿي ته سڀ قدر منفرد آهن عزم جي وقت تي، اهو ڪامياب ٿيندو.

مٿي بيان ڪيل مثال، يقينا، تمام مصنوعي آهي، پر اهو خيال ظاهر ڪري ٿو. اسان جي ايپليڪيشن ۾، اسان منطق کي لاڳو ڪرڻ لاءِ ملتوي رڪاوٽون استعمال ڪندا آهيون جيڪي تڪرارن کي حل ڪرڻ لاءِ ذميوار هوندا آهن جڏهن صارف هڪ ئي وقت بورڊ تي شيئر ٿيل ويجيٽ شين سان ڪم ڪندا آهن. اهڙين پابندين کي استعمال ڪندي اسان کي ايپليڪيشن ڪوڊ کي ٿورو آسان ڪرڻ جي اجازت ڏئي ٿي.

عام طور تي، رڪاوٽ جي قسم تي منحصر ڪري ٿو، پوسٽ گريس انهن کي جانچڻ لاء گرينولرٽي جا ٽي سطح آهن: قطار، ٽرانزيڪشن، ۽ اظهار جي سطح.
پوسٽ گريس: بلوٽ، 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 جي پوئين مرحلي ۾ آهي، جيڪو صرف انڊيڪس ٺاهي ٿو، پر رڪاوٽون نه: پراڻي ٽيبل ۾ هڪ منفرد رڪاوٽ هئي، ۽ نئين ان جي بدران هڪ منفرد انڊيڪس ٺاهيو.

پوسٽ گريس: بلوٽ، pg_repack ۽ ملتوي رڪاوٽون

هتي اهو نوٽ ڪرڻ ضروري آهي ته جيڪڏهن رڪاوٽ عام آهي ۽ ملتوي نه ڪئي وئي آهي، ته ان جي بدران ٺاهيل منفرد انڊيڪس هن رڪاوٽ جي برابر آهي، ڇاڪاڻ ته Postgres ۾ منفرد پابنديون لاڳو ڪيون ويون آهن هڪ منفرد انڊيڪس ٺاهي. پر ملتوي ٿيل رڪاوٽ جي صورت ۾، اهو رويو ساڳيو نه آهي، ڇاڪاڻ ته انڊيڪس کي ملتوي نه ٿو ڪري سگهجي ۽ هميشه ان وقت چيڪ ڪيو ويندو آهي جڏهن sql حڪم تي عمل ڪيو ويندو آهي.

اهڙيء طرح، مسئلي جو بنياد چيڪ جي "تاخير" ۾ آهي: اصل جدول ۾ اهو ڪمٽ جي وقت تي ٿئي ٿو، ۽ نئين جدول ۾ وقت تي sql حڪم تي عمل ڪيو ويندو آهي. ان جو مطلب اهو آهي ته اسان کي پڪ ڪرڻ جي ضرورت آهي ته چڪاس ٻنهي صورتن ۾ ساڳيو ڪم ڪيو ويو آهي: يا ته هميشه دير سان، يا هميشه فوري طور تي.

پوءِ اسان وٽ ڪهڙا خيال هئا؟

ٺاھ ٺاھيو ھڪڙي انڊيڪس جھڙي ملتوي

پهريون خيال ٻنهي چيڪن کي فوري طور تي موڊ ۾ انجام ڏيڻ آهي. اهو ڪيترن ئي غلط مثبت پابنديون پيدا ڪري سگهي ٿو، پر جيڪڏهن انهن مان ٿورڙا آهن، اهو صارفين جي ڪم کي متاثر نه ڪرڻ گهرجي، ڇو ته اهڙي تڪرار انهن لاء هڪ عام صورتحال آهي. اهي واقعا ٿين ٿا، مثال طور، جڏهن ٻه استعمال ڪندڙ هڪ ئي وقت هڪ ئي ويجيٽ کي ايڊٽ ڪرڻ شروع ڪن ٿا، ۽ ٻئي صارف جي ڪلائنٽ وٽ اها معلومات حاصل ڪرڻ لاءِ وقت نه هوندو آهي ته ويجيٽ پهريان ئي پهرين صارف طرفان ايڊٽ ڪرڻ لاءِ بند ٿيل آهي. اهڙي صورتحال ۾، سرور ٻئي صارف کي رد ڪري ٿو، ۽ ان جو ڪلائنٽ تبديلين کي واپس ڪري ٿو ۽ ويجٽ کي بلاڪ ڪري ٿو. ٿوري دير کان پوءِ، جڏهن پهريون استعمال ڪندڙ ايڊيٽنگ مڪمل ڪندو، ٻئي کي اها معلومات ملندي ته ويجيٽ هاڻي بند نه آهي ۽ پنهنجي عمل کي ورجائي سگهندو.

پوسٽ گريس: بلوٽ، 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 منٽ).

تجربا مثبت هئا. پيداوار تي پڻ لانچ ڪريو. وضاحت لاءِ، ھتي ھڪڙي تصوير آھي ھڪڙي ڊيٽابيس جي سائيز سان گڏ ھلڻ کانپوءِ:

پوسٽ گريس: بلوٽ، pg_repack ۽ ملتوي رڪاوٽون

جيئن ته اسان هن حل سان مڪمل طور تي مطمئن هئاسين، اسان ٻئي کي لاڳو ڪرڻ جي ڪوشش نه ڪئي، پر اسان ان تي بحث ڪرڻ جي امڪان تي غور ڪري رهيا آهيون ته واڌارو ڊولپرز سان. اسان جي موجوده نظرثاني، بدقسمتي سان، اڃا تائين اشاعت لاء تيار نه آهي، ڇو ته اسان صرف منفرد ملتوي پابنديون سان مسئلو حل ڪيو آهي، ۽ مڪمل پيچ لاء ضروري آهي ته ٻين قسمن جي مدد مهيا ڪرڻ لاء. اسان کي اميد آهي ته مستقبل ۾ اهو ڪم ڪرڻ جي قابل هوندو.

شايد توهان وٽ هڪ سوال آهي، ڇو ته اسان هن ڪهاڻي ۾ pg_repack جي ترميم سان شامل ٿي چڪا آهيون، ۽ مثال طور، ان جي اينالاگ استعمال نه ڪيو؟ ڪجھ وقت تي اسان ان بابت پڻ سوچيو، پر ان جي اڳ ۾ استعمال ڪرڻ جي مثبت تجربو، بغير ڪنهن رڪاوٽ جي ٽيبل تي، اسان کي مسئلي جي جوهر کي سمجهڻ ۽ ان کي حل ڪرڻ جي ڪوشش ڪرڻ جي حوصلا افزائي ڪئي. ان کان علاوه، ٻين حلن کي استعمال ڪرڻ لاء پڻ ٽيسٽ ڪرڻ لاء وقت جي ضرورت آهي، تنهنڪري اسان فيصلو ڪيو ته اسان پهريان ان ۾ مسئلو حل ڪرڻ جي ڪوشش ڪنداسين، ۽ جيڪڏهن اسان محسوس ڪيو ته اسان اهو مناسب وقت ۾ نه ٿا ڪري سگهون ته پوء اسان اينالاگ ڏسڻ شروع ڪنداسين. .

پهچڻ

ڇا اسان سفارش ڪري سگھون ٿا اسان جي پنهنجي تجربي جي بنياد تي:

  1. توهان جي بلوٽ جي نگراني ڪريو. مانيٽرنگ ڊيٽا جي بنياد تي، توهان سمجهي سگهو ٿا ته ڪيئن چڱي طرح autovacuum ترتيب ڏنل آهي.
  2. بلوٽ کي قابل قبول سطح تي رکڻ لاءِ AUTOVACUUM کي ترتيب ڏيو.
  3. جيڪڏهن ڦوٽو اڃا به وڌي رهيو آهي ۽ توهان ان تي قابو نه ٿا ڪري سگهو، ٻاهرئين اوزارن کي استعمال ڪندي، ٻاهرين ايڪسٽينشن کي استعمال ڪرڻ کان نه ڊڄو. بنيادي شيء هر شيء کي چڱي طرح جانچڻ آهي.
  4. توهان جي ضرورتن مطابق خارجي حلن کي تبديل ڪرڻ کان نه ڊڄو - ڪڏهن ڪڏهن اهو توهان جي پنهنجي ڪوڊ کي تبديل ڪرڻ کان وڌيڪ اثرائتو ۽ اڃا به آسان ٿي سگهي ٿو.

جو ذريعو: www.habr.com

تبصرو شامل ڪريو