پوسٹگریس: بلوٹ، pg_repack اور موخر رکاوٹیں۔

پوسٹگریس: بلوٹ، pg_repack اور موخر رکاوٹیں۔

میزوں اور اشاریہ جات پر بلوٹ کا اثر وسیع پیمانے پر جانا جاتا ہے اور نہ صرف پوسٹگریس میں موجود ہے۔ اس سے باہر سے نمٹنے کے طریقے ہیں، جیسے ویکیوم فل ​​یا کلسٹر، لیکن وہ آپریشن کے دوران ٹیبل کو لاک کر دیتے ہیں اور اس لیے ہمیشہ استعمال نہیں کیا جا سکتا۔

مضمون میں تھوڑا سا نظریہ ہوگا کہ بلوٹ کیسے ہوتا ہے، آپ اس سے کیسے لڑ سکتے ہیں، موخر کی گئی رکاوٹوں اور pg_repack ایکسٹینشن کے استعمال میں جو مسائل لاتے ہیں ان کے بارے میں۔

اس مضمون کی بنیاد پر لکھا گیا ہے۔ میری تقریر PgConf.Russia 2020 میں۔

بلوٹ کیوں ہوتا ہے؟

پوسٹگریس ایک ملٹی ورژن ماڈل پر مبنی ہے (ایم وی سی سی)۔ اس کا خلاصہ یہ ہے کہ ٹیبل میں ہر قطار میں کئی ورژن ہوسکتے ہیں، جبکہ لین دین میں ان میں سے ایک سے زیادہ ورژن نظر نہیں آتے، لیکن ضروری نہیں کہ ایک ہی ہو۔ یہ کئی لین دین کو بیک وقت کام کرنے کی اجازت دیتا ہے اور عملی طور پر ایک دوسرے پر کوئی اثر نہیں پڑتا ہے۔

ظاہر ہے، ان تمام ورژنز کو ذخیرہ کرنے کی ضرورت ہے۔ پوسٹگریس میموری پیج کے ساتھ صفحہ بہ صفحہ کام کرتا ہے اور صفحہ ڈیٹا کی کم از کم مقدار ہے جسے ڈسک سے پڑھا یا لکھا جا سکتا ہے۔ آئیے یہ سمجھنے کے لیے ایک چھوٹی سی مثال دیکھتے ہیں کہ یہ کیسے ہوتا ہے۔

ہم کہتے ہیں کہ ہمارے پاس ایک ٹیبل ہے جس میں ہم نے کئی ریکارڈز شامل کیے ہیں۔ نیا ڈیٹا فائل کے پہلے صفحے پر ظاہر ہوا ہے جہاں ٹیبل محفوظ ہے۔ یہ قطاروں کے لائیو ورژن ہیں جو کمٹمنٹ کے بعد دوسرے لین دین کے لیے دستیاب ہوتے ہیں (سادگی کے لیے، ہم فرض کریں گے کہ تنہائی کی سطح Read Committed ہے)۔

پوسٹگریس: بلوٹ، pg_repack اور موخر رکاوٹیں۔

اس کے بعد ہم نے اندراجات میں سے ایک کو اپ ڈیٹ کیا، اس طرح پرانے ورژن کو مزید متعلقہ کے طور پر نشان زد کیا۔

پوسٹگریس: بلوٹ، pg_repack اور موخر رکاوٹیں۔

مرحلہ وار، قطار کے ورژن کو اپ ڈیٹ کرتے ہوئے اور حذف کرتے ہوئے، ہم ایک ایسے صفحے کے ساتھ ختم ہوئے جس میں تقریباً نصف ڈیٹا "کوڑا کرکٹ" ہے۔ یہ ڈیٹا کسی بھی لین دین کے لیے نظر نہیں آتا۔

پوسٹگریس: بلوٹ، pg_repack اور موخر رکاوٹیں۔

پوسٹگریس کا ایک طریقہ کار ہے۔ خلا، جو متروک ورژن کو صاف کرتا ہے اور نئے ڈیٹا کے لیے جگہ بناتا ہے۔ لیکن اگر یہ کافی جارحانہ طور پر ترتیب نہیں دیا گیا ہے یا دوسرے ٹیبلز میں کام کرنے میں مصروف ہے، تو پھر "کوڑے کا ڈیٹا" باقی رہتا ہے، اور ہمیں نئے ڈیٹا کے لیے اضافی صفحات استعمال کرنے پڑتے ہیں۔

تو ہماری مثال میں، کسی وقت ٹیبل چار صفحات پر مشتمل ہوگا، لیکن اس میں سے صرف نصف لائیو ڈیٹا پر مشتمل ہوگا۔ نتیجے کے طور پر، جدول تک رسائی حاصل کرتے وقت، ہم ضرورت سے کہیں زیادہ ڈیٹا پڑھیں گے۔

پوسٹگریس: بلوٹ، pg_repack اور موخر رکاوٹیں۔

یہاں تک کہ اگر VACUUM اب قطار کے تمام غیر متعلقہ ورژنز کو حذف کر دیتا ہے، صورت حال ڈرامائی طور پر بہتر نہیں ہوگی۔ ہمارے پاس نئی قطاروں کے لیے صفحات یا یہاں تک کہ پورے صفحات میں خالی جگہ ہوگی، لیکن ہم پھر بھی ضرورت سے زیادہ ڈیٹا پڑھ رہے ہوں گے۔
ویسے، اگر مکمل طور پر خالی صفحہ (ہماری مثال میں دوسرا) فائل کے آخر میں ہوتا، تو VACUUM اسے تراش سکتا ہے۔ لیکن اب وہ بیچ میں ہے اس لیے اس کے ساتھ کچھ نہیں کیا جا سکتا۔

پوسٹگریس: بلوٹ، pg_repack اور موخر رکاوٹیں۔

جب ایسے خالی یا انتہائی ویرل صفحات کی تعداد زیادہ ہو جاتی ہے، جسے بلوٹ کہتے ہیں، تو یہ کارکردگی کو متاثر کرنا شروع کر دیتا ہے۔

اوپر بیان کردہ سب کچھ میزوں میں بلوٹ کی موجودگی کی میکانکس ہے. اشاریہ جات میں یہ اسی طرح ہوتا ہے۔

کیا مجھے پھول ہے؟

اس بات کا تعین کرنے کے کئی طریقے ہیں کہ آیا آپ کو پھولا ہوا ہے۔ سب سے پہلے کا خیال اندرونی پوسٹگریس کے اعدادوشمار کا استعمال کرنا ہے، جس میں میزوں میں قطاروں کی تعداد، "لائیو" قطاروں کی تعداد وغیرہ کے بارے میں تخمینی معلومات ہوتی ہیں۔ ہم نے ایک بنیاد کے طور پر لیا اسکرپٹ PostgreSQL ماہرین سے، جو ٹوسٹ اور بلوٹ بیٹری انڈیکس کے ساتھ بلوٹ ٹیبلز کا جائزہ لے سکتے ہیں۔ ہمارے تجربے میں، اس کی غلطی 10-20٪ ہے۔

دوسرا طریقہ توسیع کا استعمال کرنا ہے۔ pgstattuple، جو آپ کو صفحات کے اندر دیکھنے کی اجازت دیتا ہے اور ایک تخمینہ اور صحیح بلوٹ قدر دونوں حاصل کرتا ہے۔ لیکن دوسری صورت میں، آپ کو پوری میز کو اسکین کرنا پڑے گا۔

ہم ایک چھوٹی بلوٹ ویلیو، 20% تک، قابل قبول سمجھتے ہیں۔ اسے فل فیکٹر کے ینالاگ کے طور پر سمجھا جا سکتا ہے۔ میزیں и اشاریہ جات. 50% اور اس سے اوپر، کارکردگی کے مسائل شروع ہو سکتے ہیں۔

سوجن سے لڑنے کے طریقے

پوسٹگریس کے پاس باکس سے باہر بلوٹ سے نمٹنے کے کئی طریقے ہیں، لیکن وہ ہمیشہ ہر کسی کے لیے موزوں نہیں ہوتے ہیں۔

آٹو ویکیوم کو ترتیب دیں تاکہ بلوٹ نہ ہو۔. یا زیادہ واضح طور پر، اسے آپ کے لیے قابل قبول سطح پر رکھنے کے لیے۔ یہ "کپتان کے" مشورے کی طرح لگتا ہے، لیکن حقیقت میں یہ حاصل کرنا ہمیشہ آسان نہیں ہوتا ہے۔ مثال کے طور پر، آپ کو ڈیٹا اسکیما میں باقاعدہ تبدیلیوں کے ساتھ فعال ترقی ہے، یا کسی قسم کی ڈیٹا کی منتقلی ہو رہی ہے۔ نتیجے کے طور پر، آپ کا لوڈ پروفائل کثرت سے تبدیل ہو سکتا ہے اور عام طور پر ٹیبل سے ٹیبل مختلف ہو گا۔ اس کا مطلب ہے کہ آپ کو مسلسل تھوڑا سا آگے کام کرنے کی ضرورت ہے اور ہر ٹیبل کے بدلتے ہوئے پروفائل میں آٹو ویکیوم کو ایڈجسٹ کرنا ہوگا۔ لیکن ظاہر ہے ایسا کرنا آسان نہیں ہے۔

AUTOVACUUM ٹیبلز کے ساتھ برقرار نہ رہنے کی ایک اور عام وجہ یہ ہے کہ طویل عرصے سے چلنے والے لین دین ہیں جو اسے ان لین دین کے لیے دستیاب ڈیٹا کو صاف کرنے سے روکتے ہیں۔ یہاں کی سفارش بھی واضح ہے - "لٹکتی ہوئی" لین دین سے چھٹکارا حاصل کریں اور فعال لین دین کے وقت کو کم سے کم کریں۔ لیکن اگر آپ کی درخواست پر بوجھ OLAP اور OLTP کا ہائبرڈ ہے، تو آپ بیک وقت کئی بار اپ ڈیٹس اور مختصر سوالات کے ساتھ ساتھ طویل مدتی آپریشنز بھی کر سکتے ہیں - مثال کے طور پر، رپورٹ بنانا۔ ایسی صورت حال میں، یہ مختلف اڈوں پر بوجھ کو پھیلانے کے بارے میں سوچنے کے قابل ہے، جو ان میں سے ہر ایک کو مزید ٹھیک کرنے کی اجازت دے گا۔

ایک اور مثال - یہاں تک کہ اگر پروفائل یکساں ہے، لیکن ڈیٹا بیس بہت زیادہ بوجھ کے نیچے ہے، تب بھی سب سے زیادہ جارحانہ آٹوویکوم کا مقابلہ نہیں کر سکتا، اور پھولنے لگے گا۔ اسکیلنگ (عمودی یا افقی) واحد حل ہے۔

ایسی صورت حال میں کیا کریں جہاں آپ نے آٹوویکوم قائم کیا ہو، لیکن پھولنا جاری ہے۔

ٹیم ویکیوم فل جدولوں اور اشاریہ جات کے مواد کو دوبارہ بناتا ہے اور ان میں صرف متعلقہ ڈیٹا چھوڑتا ہے۔ بلوٹ کو ختم کرنے کے لیے، یہ بالکل کام کرتا ہے، لیکن اس کے عمل کے دوران ٹیبل پر ایک خصوصی لاک (AccessExclusiveLock) پکڑا جاتا ہے، جو اس ٹیبل پر سوالات کو انجام دینے کی اجازت نہیں دے گا، یہاں تک کہ سلیکٹ بھی کرتا ہے۔ اگر آپ کچھ وقت کے لیے اپنی سروس یا اس کا کچھ حصہ بند کرنے کے متحمل ہوسکتے ہیں (ڈیٹا بیس اور آپ کے ہارڈ ویئر کے سائز کے لحاظ سے دسیوں منٹ سے لے کر کئی گھنٹوں تک)، تو یہ آپشن بہترین ہے۔ بدقسمتی سے، ہمارے پاس طے شدہ دیکھ بھال کے دوران VACUUM FULL چلانے کا وقت نہیں ہے، اس لیے یہ طریقہ ہمارے لیے موزوں نہیں ہے۔

ٹیم جھرمٹ ٹیبل کے مواد کو ویکیوم فل ​​کی طرح دوبارہ بناتا ہے، لیکن آپ کو ایک انڈیکس بتانے کی اجازت دیتا ہے جس کے مطابق ڈسک پر ڈیٹا کو فزیکل طور پر آرڈر کیا جائے گا (لیکن مستقبل میں نئی ​​قطاروں کے لیے آرڈر کی ضمانت نہیں ہے)۔ بعض حالات میں، یہ متعدد سوالات کے لیے ایک اچھی اصلاح ہے - انڈیکس کے لحاظ سے متعدد ریکارڈز کو پڑھنے کے ساتھ۔ کمانڈ کا نقصان ویکیوم فل ​​کی طرح ہی ہے - یہ آپریشن کے دوران ٹیبل کو لاک کر دیتا ہے۔

ٹیم REINDEX پچھلے دو کی طرح، لیکن ایک مخصوص انڈیکس یا ٹیبل کے تمام اشاریہ جات کو دوبارہ بناتا ہے۔ تالے قدرے کمزور ہیں: ٹیبل پر شیئر لاک (ترمیم کو روکتا ہے، لیکن انتخاب کی اجازت دیتا ہے) اور دوبارہ بنائے جانے والے انڈیکس پر AccessExclusiveLock (اس انڈیکس کا استعمال کرتے ہوئے سوالات کو روکتا ہے)۔ تاہم، Postgres کے 12ویں ورژن میں ایک پیرامیٹر نمودار ہوا۔ فوری، جو آپ کو ریکارڈ کے ساتھ ساتھ اضافے، ترمیم، یا حذف کیے بغیر انڈیکس کو دوبارہ بنانے کی اجازت دیتا ہے۔

پوسٹگریس کے پہلے ورژن میں، آپ REINDEX CONCURRENTLY استعمال کرتے ہوئے نتیجہ حاصل کر سکتے ہیں ایک ساتھ انڈیکس بنائیں. یہ آپ کو سخت لاکنگ کے بغیر ایک انڈیکس بنانے کی اجازت دیتا ہے (ShareUpdateExclusiveLock، جو متوازی سوالات میں مداخلت نہیں کرتا)، پھر پرانے انڈیکس کو ایک نئے سے تبدیل کریں اور پرانے انڈیکس کو حذف کریں۔ یہ آپ کو اپنی درخواست میں مداخلت کیے بغیر انڈیکس بلوٹ کو ختم کرنے کی اجازت دیتا ہے۔ اس بات پر غور کرنا ضروری ہے کہ اشاریہ جات کو دوبارہ بناتے وقت ڈسک کے سب سسٹم پر اضافی بوجھ پڑے گا۔

اس طرح، اگر اشاریہ جات کے لیے "مکھی پر" بلوٹ کو ختم کرنے کے طریقے موجود ہیں تو پھر میز کے لیے کوئی نہیں ہے۔ یہ وہ جگہ ہے جہاں مختلف بیرونی توسیعات کھیل میں آتی ہیں: pg_repack (سابقہ ​​pg_reorg)، pgcompact, pgcompacttable اور دوسرے. اس مضمون میں، میں ان کا موازنہ نہیں کروں گا اور صرف pg_repack کے بارے میں بات کروں گا، جسے، کچھ ترمیم کے بعد، ہم خود استعمال کرتے ہیں۔

pg_repack کیسے کام کرتا ہے۔

پوسٹگریس: بلوٹ، 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 کمانڈ کے بعد کی جاتی ہے، جو غیر متوقع نتائج کا باعث بنتی ہے۔

موخر رکاوٹیں: ان کی ضرورت کیوں ہے اور وہ کیسے کام کرتی ہیں۔

موخر پابندیوں کے بارے میں ایک چھوٹا سا نظریہ۔
آئیے ایک سادہ مثال پر غور کریں: ہمارے پاس کاروں کی ایک میز حوالہ کتاب ہے جس میں دو صفات ہیں - ڈائریکٹری میں کار کا نام اور ترتیب۔
پوسٹگریس: بلوٹ، 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 اور موخر رکاوٹیں۔
ماخذ: 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 کے پچھلے مرحلے میں ہے، جو صرف اشاریہ جات بناتا ہے، لیکن رکاوٹیں نہیں: پرانے ٹیبل میں ایک انوکھی رکاوٹ تھی، اور اس کے بجائے نئے نے ایک منفرد انڈیکس بنایا۔

پوسٹگریس: بلوٹ، pg_repack اور موخر رکاوٹیں۔

یہاں یہ نوٹ کرنا ضروری ہے کہ اگر رکاوٹ نارمل ہے اور اسے موخر نہیں کیا گیا ہے، تو اس کی بجائے بنایا گیا منفرد انڈیکس اس رکاوٹ کے برابر ہے، کیونکہ پوسٹگریس میں انوکھی رکاوٹوں کو ایک منفرد انڈیکس بنا کر لاگو کیا جاتا ہے۔ لیکن موخر رکاوٹ کی صورت میں، رویہ یکساں نہیں ہے، کیونکہ انڈیکس کو موخر نہیں کیا جا سکتا اور 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 کی ترمیم کے ساتھ کیوں شامل ہوئے، اور مثال کے طور پر، اس کے analogues کا استعمال کیوں نہیں کیا؟ کسی موقع پر ہم نے اس کے بارے میں بھی سوچا، لیکن اس کے پہلے استعمال کے مثبت تجربے نے، بغیر کسی موخر رکاوٹوں کے میزوں پر، ہمیں مسئلہ کے جوہر کو سمجھنے اور اسے ٹھیک کرنے کی کوشش کرنے کی ترغیب دی۔ اس کے علاوہ دیگر حلوں کے استعمال میں بھی ٹیسٹ کروانے کے لیے وقت درکار ہوتا ہے، اس لیے ہم نے فیصلہ کیا کہ ہم سب سے پہلے اس میں موجود مسئلہ کو حل کرنے کی کوشش کریں گے، اور اگر ہمیں احساس ہوا کہ ہم یہ کام مناسب وقت میں نہیں کر سکتے، تو ہم اینالاگز دیکھنا شروع کر دیں گے۔ .

نتائج

ہم اپنے تجربے کی بنیاد پر کیا تجویز کر سکتے ہیں:

  1. اپنے بلوٹ کی نگرانی کریں۔ مانیٹرنگ ڈیٹا کی بنیاد پر، آپ سمجھ سکتے ہیں کہ آٹو ویکیوم کو کتنی اچھی طرح سے ترتیب دیا گیا ہے۔
  2. بلوٹ کو قابل قبول سطح پر رکھنے کے لیے آٹو ویکیوم کو ایڈجسٹ کریں۔
  3. اگر اپھارہ اب بھی بڑھ رہا ہے اور آپ آؤٹ آف دی باکس ٹولز کا استعمال کرکے اس پر قابو نہیں پا سکتے ہیں تو بیرونی ایکسٹینشن استعمال کرنے سے نہ گھبرائیں۔ اہم بات یہ ہے کہ ہر چیز کو اچھی طرح سے جانچنا ہے۔
  4. اپنی ضروریات کے مطابق بیرونی حلوں میں ترمیم کرنے سے نہ گھبرائیں - بعض اوقات یہ آپ کے اپنے کوڈ کو تبدیل کرنے سے زیادہ موثر اور آسان بھی ہوسکتا ہے۔

ماخذ: www.habr.com

نیا تبصرہ شامل کریں