فهارس تالفة في PG12، كيف أصلحها؟

أنا أتخيل فقط، لكن إليك نظريتي غير المؤكدة:

شغلت عملية إعادة الفهرسة مرتين في وقت واحد (أي ccnew ثم ccnew1)
وفي كلتا الحالتين ظهرت أخطاء.

ربما عندما تفشل العملية، لا تقوم بإعادة التراجع، بل تترك المخلفات ثم تتوقف.

يبدو هذا الأمر محتملاً للغاية جدًا جدًا.

أود فقط إضافة رأيي هنا،

لا يمكن إسقاط مؤشرات Toast بهذه الطريقة،
كان علي أن أفعل:

su postgres
psql
\connect discourse
drop index pg_toast.pg_toast_309322_index_ccnew;
drop index pg_toast.pg_toast_309322_index_ccnew1;

ما ورد أعلاه ينطبق فقط على pg toast لأن مستخدم discourse ليس لديه صلاحية الوصول إلى هذا المؤشر.
PG::InsufficientPrivilege: ERROR: permission denied for schema pg_toast

إسقاط مؤشرات التوست أمر مخيف للغاية. أظن أنه لا خيار آخر لديك هنا. نعم، يجب عليك القيام بذلك من psql.

وها يأتي إعادة الفهرسة السعيدة:

discourse=# REINDEX SCHEMA CONCURRENTLY public;
REINDEX

:partying_face:

ممتاز! عثرت على جميع الفهارس التي تحتوي على “ccnew” في اسمها عبر هذا الاستعلام.

psql
\connect discourse
select tablename,indexname,indexdef from pg_indexes where indexname LIKE '%ccnew%';

incoming_referer.csv (7.2 KB)

اتضح أن لدي 30 فهرسًا من هذا القبيل، وجميعها في جدول incoming_referers. لذا تحققت من أن جميع فهارس ccnew كانت بالفعل نسخًا مكررة عبر عمود indexdef في هذا الاستعلام.

select indexname,indexdef from pg_indexes where tablename = 'incoming_referers';

ccnew.csv (6.6 KB)

ثم قمت بحذفها جميعًا بنجاح.

DROP INDEX incoming_referers_pkey_ccnew;
DROP INDEX incoming_referers_pkey_ccnew_ccnew;
DROP INDEX incoming_referers_pkey_ccnew1;
...وهكذا لجميع الـ 30

عندها أعيدت فهرسة المخطط بالكامل مرة أخرى، لكن لم يتم إعادة بناء فهارس ccnew المتطابقة لجدول incoming_referers مرة أخرى، كما تم العثور على ثلاثة فهارس pg_toast. قمت بحذفها ثم أعيدت فهرسة المخطط بالكامل مرة أخرى، وحصلت على أخطاء أكثر، وعثرت على المزيد من فهارس ccnew في مخطط discourse، ثم أعيدت الفهرسة للمرة الثالثة…

لا يمكنني إكمال إعادة فهرسة كاملة دون أخطاء؛ حيث تستمر في إنشاء فهارس ccnew جديدة ثم تفشل في إعادة بنائها في كل مرة. بعد 4 عمليات إعادة بناء كاملة، قمت بحذف فهارس ccnew ثم استسلمت. أعتقد أنه يمكنني محاولة إعادة البناء بدون استخدام خيار concurrently، لكن ذلك سيسبب توقفًا في الخدمة لفترة طويلة.

على أي حال، أعتقد أن معظم المستخدمين الذين قاموا بالترقية من PG10 إلى PG12 وحاولوا إعادة الفهرسة لاحقًا يمتلكون هذه الفهارس الإضافية ccnew، ويجب حذفها جميعًا. فستستهلك مساحة فقط وتزيد من عمليات كتابة القرص I/O دون أي فائدة.

نفذت ما يعادله في Data-Explorer. كان أسهل قليلاً في الإدارة.

ما فعلته هو أنني أخذت جميع فهارس ccnew/ccnew1/ccnew2…ccnewn، وحذفتها تمامًا ثم أعيدت الفهرسة. وهذا ما أصلح المشكلة بالنسبة لي.

ccnew هي ميزة في postgres لوضع علامات على الفهارس، وأعتقد أن هناك بعض عدم الكفاءة في العملية تتسبب في بقاء هذه الفهارس إذا فشلت عملية الفهرسة لسبب ما.

لا يمكنني إلا أن أقترح عليك العمل أولاً على السبب الجذري للمشكلة. كان لدي وسوم مكررة اضطررت إلى حذفها قبل المتابعة في حذف الفهارس. فلو بقيت حتى علامة مكررة واحدة، فلن يتم الفهرسة بشكل صحيح وسيتم إنشاء فهرس ccnew'n' آخر وفشله.

أنت محق، لا يزال لدي تكرارات — لا يمكن إعادة بناء فهارس غير ccnew بشكل غير متزامن. أحتاج إلى إزالة هذه الصفوف المكررة.

discourse=# reindex index index_incoming_referers_on_path_and_incoming_domain_id;
ERROR:  could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id"
DETAIL:  Key (path, incoming_domain_id)=(/search/, 3433) is duplicated.
discourse=# reindex index index_incoming_referers_on_path_and_incoming_domain_id;
ERROR:  could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id"
DETAIL:  Key (path, incoming_domain_id)=(/search/, 1861) is duplicated.

المثير للغرابة حقًا هو أنني أرى صفًا واحدًا فقط مع كل من قيم incoming_domain_id تلك في incoming_referer. إذن لماذا هي مكررة؟

discourse=# select * from incoming_referers where path='/search/' AND incoming_domain_id IN (1861,3433);
  id   |   path   | incoming_domain_id 
-------+----------+--------------------
 42845 | /search/ |               1861
 40763 | /search/ |               3433
(2 rows)

@sam أو @riking، هل يجب أن أحذف هذين الصفين على النحو التالي:

DELETE FROM incoming_referers WHERE path='/search/' AND incoming_domain_id IN (1861,3433);

… أعتقد أنني أتعلم postgres في النهاية، هه.

لا، سيؤدي ذلك إلى حذف كليهما — بل تريد استخدام WHERE للعثور على معرفي id، ثم اختيار معرف id واحد فقط لحذفه.

استعلامك يستخدم الفهرس التالف، وهذا هو السبب في ظهور صف واحد فقط لكل منهما. جرّب ما يلي:

... where path LIKE '%/search/' ...

حسناً، هذا يعيد 43 صفًا.

discourse=# select * from incoming_referers where path LIKE '%/search/' ORDER BY incoming_domain_id;
  id   |    path     | incoming_domain_id 
-------+-------------+--------------------
   878 | /search/    |                 63
 33457 | /do/search/ |                567
  1580 | /search/    |                602
  1888 | /search/    |                663
 42983 | /search/    |               1259
  4896 | /search/    |               1788
 42845 | /search/    |               1861
  5162 | /search/    |               1861
  5176 | /search/    |               1866
 43350 | /search/    |               1905
 17238 | /search/    |               1905
 20689 | /search/    |               1982
  5781 | /hg/search/ |               1987
  8031 | /search/    |               2665
 10325 | /search/    |               3192
 11289 | /search/    |               3414
 40763 | /search/    |               3433
 42849 | /search/    |               3433
 13087 | /search/    |               3895
 13159 | /search/    |               3949
 13802 | /do/search/ |               4051
 14407 | /search/    |               4209
 14507 | /search/    |               4211
 15394 | /search/    |               4230
 15533 | /search/    |               4258
 45274 | /search/    |               5303
 20923 | /search/    |               5400
 21317 | /search/    |               5534
 22928 | /search/    |               5918
 22956 | /search/    |               5926
 37448 | /search/    |               6393
 25094 | /search/    |               6412
 25594 | /search/    |               6547
 39655 | /search/    |               6596
 27371 | /search/    |               6986
 27452 | /a/search/  |               7003
 27623 | /search/    |               7041
 31041 | /search/    |               7767
 36943 | /search/    |               8622
 37381 | /search/    |               8711
 37411 | /search/    |               8716
 40424 | /search/    |               9124
 44451 | /search/    |               9525
(43 rows)

إذن، هل يجب أن أنفذ الأمر التالي؟

DELETE FROM incoming_referers WHERE path LIKE '%/search/' AND id IN (42845,43350,42849);

نعم، يبدو أمر DELETE صحيحًا ويجب أن يسمح لك بأخذ نسخة احتياطية نظيفة.

صدق أو لا تصدق، فشل أكثر. في المجموعة الأولى، اكتشفت مفتاحًا مكررًا آخر في /m/search لم يتم التقاطه بواسطة الويلدكارد %/search/ لسبب ما، فحذفته. قمت بإعادة الفهرسة مرة أخرى (كل مرة تستغرق ساعة تقريبًا!) وظهور المزيد من الأخطاء يشير إلى مفتاح مكرر في users(username_lower).

discourse=# reindex index index_users_on_username_lower;              
ERROR:  could not create unique index "index_users_on_username_lower"
DETAIL:  Key (username_lower)=(john_smith) is duplicated.

لكن الحقيقة هي، كانت هناك صف واحد فقط يحتوي على username_lower=john_smith! حان وقت ارتداء قبعة المحقق.

عند النظر إلى واجهة إدارة المنتدى، كان لدينا مستخدمان منفصلان يُدعى كل منهما “john_smith” و"John_Smith"-- لاحظ اختلاف الأحرف الكبيرة والصغيرة. لذا قمت بحذف المستخدم الذي يحتوي على أحرف صغيرة فقط على مستوى المنتدى لأنه لم يكن نشطًا منذ 4 سنوات، ثم تم إعادة بناء هذا الفهرس بنجاح.

شغلت عملية إعادة بناء كاملة أخرى، حيث استغرقت كل منها ساعة تقريبًا، وأخيرًا أعتقد أننا بخير-- خطأ واحد فقط، لكن لا توجد تكرارات، فقط pg_toast ccnew. قمت بإسقاطه.

discourse=# REINDEX SCHEMA CONCURRENTLY public;               
WARNING:  cannot reindex invalid index "pg_toast.pg_toast_2783329_index_ccnew" concurrently, skipping
REINDEX

لقد كانت رحلة.

هل يمكن/سيتم أتمتة إصلاحات قاعدة البيانات هذه في ترقية مستقبلية لـ Discourse؟

من المرجح أن تتوقف فساد فهرس PostgreSQL في ترقية مستقبلية، إذا تمكن بيتر من الحصول على تكرار متسق.

إذا فشل إعادة الفهرسة المتزامن (REINDEX) لأن الجدول يحتوي على بيانات غير صالحة، فيجب عليك:

  1. إصلاح البيانات غير الصالحة، كما تم ذلك هنا.

  2. سرد الفهارس غير الصالحة باستخدام الأمر SELECT pg_class.relname FROM pg_class, pg_index WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid;

  3. حذف كل فهرس غير صالح مدرج أعلاه باستخدام الأمر DROP INDEX <indexname>;

  4. حاول تنفيذ الأمر REINDEX مرة أخرى.

بعد تشغيل الترقية إلى 2.5.0beta5 واتباع الإرشادات ما بعد التحديث لإعادة فهرسة قاعدة البيانات، حصلت على هذا:

discourse=# REINDEX SCHEMA CONCURRENTLY public;
ERROR:  could not create unique index "index_plugin_store_rows_on_plugin_name_and_key_ccnew"
DETAIL:  Key (plugin_name, key)=(discourse-data-explorer, q:-10) is duplicated.

أفضل عدم تجربة ذلك… إذن، كيف يمكنني حذف التكرار بأمان؟

لا، الترقية لا تتسبب في تلف الفهرس، بل إنها فقط توضح أن الفهرس تالف بالفعل. يمكنك التحقق من ذلك بمحاولة استعادة نسخة احتياطية من بياناتك على خادم آخر (حتى لو كان يعمل بإصدار pg10). أو حاول إعادة بناء الفهرس على التثبيت الحالي لديك. غير واضح ما الذي يسبب تلف الفهارس، لكن هناك أمل في أن يكون إصدار pg12 أقل عرضة لحدوث هذه المشكلة.

هناك بعض الفوائد في الأداء من الترقية، لكن تأجيلها ليس فكرة سيئة.

الآن أصبح الأمر متأخرًا، لذا لا أزال أبحث عن توجيهات بشأن الخطوات التالية.

شيء مثل

select id, plugin_name, key from plugin_store_rows where plugin_name like '%discourse-data-explorer%'

يجب أن يعطيك الصفوف. أعتقد أنه من الآمن بما يكفي حذفها.

أعتقد أن ilike ستكون مطلوبة