יכולות להיות אי-התאמות באינדקסים של מסדי נתונים מסיבות שונות, כולל פגמים בתוכנה, בעיות בחומרה או שינויים בסיסיים בהתנהגות, כמו שינויים בסדר המיון.
קהילת PostgreSQL יצרה כלים לזיהוי ולפתרון של בעיות כאלה. זה כולל כלים כמו amcheck, שמומלץ על ידי קהילת PostgreSQL לזיהוי בעיות עקביות, כולל בעיות שהיו בגרסאות קודמות של PostgreSQL 14.
המדריך הזה מיועד למשתמשי Cloud SQL שנתקלים בבעיות האלה של עקביות. המדריך הזה מספק מידע שעוזר למשתמשי PostgreSQL לזהות אינדקסים לא עקביים ולתקן אותם.
כדי לפתור את חוסר העקביות באינדקס, צריך לבצע את השלבים הבאים:
-
לפני שמתחילים ליצור מחדש את האינדקס, צריך לגבות את מסד הנתונים, להגדיר את ההרשאות הנכונות, לוודא מהי גרסת הלקוח של
psqlולהוריד את התוסףamcheck. -
הצהרה אחת מזהה הפרות של מפתח ייחודי ומפתח ראשי, והצהרה אחרת מזהה מגוון של אי-התאמות אחרות.
איך מתקנים את חוסר העקביות באינדקס
ביצוע אינדוקס מחדש של אינדקס מתקן את כל חוסר העקביות שלו. יכול להיות שתצטרכו לשנות את הגדרות הזיכרון של המופע כדי לשפר את הביצועים.
מעקב אחרי פעולות של יצירת אינדקס מחדש.
מומלץ לעקוב אחרי התקדמות הפעולה של יצירת האינדקס מחדש כדי לוודא שהיא מתקדמת ולא נחסמת.
-
אחרי שתבצעו אינדוקס מחדש בהצלחה, מומלץ לוודא שהאינדקס לא מכיל אי-התאמות.
לפני שמתחילים
גיבוי מסד הנתונים
כדי לוודא שלא יאבדו נתונים במהלך יצירת האינדקס מחדש, מומלץ לגבות את מסד הנתונים. מידע נוסף מופיע במאמר יצירת גיבוי לפי דרישה.
הגדרת ההרשאה cloudsqlsuperuser
כדי להשלים את השלבים בדף הזה, צריך הרשאות cloudsqlsuperuser. מידע נוסף זמין במאמר בנושא session_replication_role.
מוודאים שגרסת הלקוח psql היא 9.6 ומעלה
כדי להשלים את השלבים בדף הזה, צריך לוודא שגרסת הלקוח psql היא 9.6 ומעלה. מריצים את הפקודה psql --version כדי לבדוק את גרסת הלקוח הנוכחית של psql.
התקנת התוסף amcheck
כדי לבדוק אם יש אי-התאמות באינדקס, צריך להפעיל את התוסף amcheck.
PostgreSQL 9.6
כדי להתקין את amcheck עבור PostgreSQL 9.6, מריצים את ההצהרה הבאה:
CREATE EXTENSION amcheck_next;
אם מופיעה השגיאה `Could not open extension control file...`, צריך לוודא שאתם מריצים את גרסת התחזוקה הנכונה של היעד (POSTGRES_9_6_24.R20220710.01_12).
PostgreSQL 10 ואילך
כדי להתקין את amcheck ל-PostgreSQL 10 ומעלה, מריצים את ההצהרה הבאה:
CREATE EXTENSION amcheck;
בדיקה של אינדקסים לא עקביים
בקטעים הבאים מוסבר איך לבדוק אם יש אינדקסים לא עקביים. לשם כך, בודקים אם יש אי-עקביות באינדקסים וגם אם יש הפרות של מפתחות ייחודיים ומפתחות ראשיים.
בדיקת אי-התאמות
בכל מסד נתונים, מריצים את ההצהרה הבאה כדי לבדוק אם יש אי-התאמות:
קוד לדוגמה
DO $$ DECLARE r RECORD; version varchar(100); BEGIN RAISE NOTICE 'Started relhasindex validation on database: %', current_database(); FOR r IN SELECT indexrelid::regclass relname FROM pg_index WHERE indrelid NOT IN (SELECT oid FROM pg_class WHERE relhasindex) LOOP RAISE LOG 'Failed to check index %: %', r.relname, 'relhasindex is false, want true'; RAISE WARNING 'Failed to check index %: %', r.relname, 'relhasindex is false, want true'; END LOOP; RAISE NOTICE 'Finished relhasindex validation on database: %', current_database(); RAISE NOTICE 'Started b-tree amcheck on database: %', current_database(); SHOW server_version into version; SELECT split_part(version, '.', 1) into version; FOR r IN SELECT c.oid, c.oid::regclass relname, i.indisunique FROM pg_index i JOIN pg_opclass op ON i.indclass[0] = op.oid JOIN pg_am am ON op.opcmethod = am.oid JOIN pg_class c ON i.indexrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE am.amname = 'btree' AND c.relpersistence != 't' AND c.relkind = 'i' AND i.indisready AND i.indisvalid LOOP BEGIN RAISE NOTICE 'Checking index %:', r.relname; IF version = '10' THEN PERFORM bt_index_check(index => r.oid); ELSE PERFORM bt_index_check(index => r.oid, heapallindexed => r.indisunique); END IF; EXCEPTION WHEN undefined_function THEN RAISE EXCEPTION 'Failed to find the amcheck extension'; WHEN OTHERS THEN RAISE LOG 'Failed to check index %: %', r.relname, sqlerrm; RAISE WARNING 'Failed to check index %: %', r.relname, sqlerrm; END; END LOOP; RAISE NOTICE 'Finished b-tree amcheck on database: %', current_database(); END $$;
הפלט שמתקבל אמור להיראות כך:
תשובה
WARNING: Failed to check index t_i_key: relhasindex is false, want true NOTICE: Checking index t_pkey: NOTICE: Checking index t_i_key: WARNING: Failed to check index t_i_key: item order invariant violated for index "t_i_key" NOTICE: Checking index t_j_key: WARNING: Failed to check index t_j_key: item order invariant violated for index "t_j_key" NOTICE: Checking index ij: WARNING: Failed to check index ij: item order invariant violated for index "ij"
מידע נוסף על צפייה ביומנים של PostgreSQL זמין במאמר צפייה ביומנים של מופע.
זיהוי ותיקון של הפרות של מפתחות ייחודיים ומפתחות ראשיים
בקטע הזה מוסבר איך לבדוק אם יש באינדקס הפרות של מפתחות ייחודיים ומפתחות ראשיים, ואיך לתקן אותן אם הן קיימות.
זיהוי הפרות של מפתח ייחודי
צריך לתקן הפרות של מפתח ייחודי לפני שמבצעים אינדוקס מחדש של אינדקס. כדי לבדוק אם יש הפרות של מפתחות ייחודיים, מריצים את הפקודה הבאה בכל מסד נתונים:
קוד לדוגמה
WITH q AS ( /* this gets info for all UNIQUE indexes */ SELECT indexrelid::regclass as idxname, indrelid::regclass as tblname, indcollation, pg_get_indexdef(indexrelid), format('(%s)',(select string_agg(quote_ident(attname), ', ') from pg_attribute a join unnest(indkey) ia(nr) on ia.nr = a.attnum where attrelid = indrelid)) as idxfields, COALESCE(substring(pg_get_indexdef(indexrelid) FROM '[)] (WHERE .*)$'), '') as whereclause FROM pg_index WHERE indisunique /* next line excludes indexes not affected by collation changes */ AND trim(replace(indcollation::text, '0', '')) != '' ) SELECT /* the format constructs the query to execute for each index */ format( $sql$ DO $$ BEGIN RAISE NOTICE 'checking index=%3$I on table=%1$I key_columns=%2$I '; END;$$; SELECT this, prev, /* we detect both reversed ordering or just not unique */ (CASE WHEN this = prev THEN 'DUPLICATE' ELSE 'BACKWARDS' END) as violation_type FROM (SELECT %2$s AS this, lag(%2$s) OVER (ORDER BY %2$s) AS prev FROM %1$s %4$s ) s WHERE this <= prev and this IS NOT NULL and prev IS NOT NULL; /* change to just '<' if looking for reverse order in index */ $sql$, tblname, idxfields, idxname, whereclause ) FROM q -- LIMIT 20 /* may use limit for testing */ -- the next line tells psql to executes this query and then execute each returned line separately \gexec
הפלט של הסקריפט אמור להיראות כך:
תשובה
NOTICE: checking index=users_email_key on table=users key_columns="(email)" NOTICE: checking index=games_title_key on table=games key_columns="(title)" this | prev | violation_type --------------------+--------------------+---------------- Game #16 $soccer 2 | Game #16 $soccer 2 | DUPLICATE Game #18 $soccer 2 | Game #18 $soccer 2 | DUPLICATE Game #2 $soccer 2 | Game #2 $soccer 2 | DUPLICATE Game #5 $soccer 2 | Game #5 $soccer 2 | DUPLICATE
בפלט הזה, בכותרת הטבלה NOTICE מוצגים האינדקס, העמודה והטבלה של הערכים שמוצגים מתחתיה. אם הפלט מכיל שורות שמוצגות בהן התוצאות DUPLICATE או BACKWARDS, סימן שיש נתונים פגומים באינדקס וצריך לתקן אותם. שורות עם BACKWARDS מציינות ערכים כפולים אפשריים שאולי מוסתרים. אם אחד מהערכים האלה מופיע בטבלה, אפשר לעיין במאמר בנושא תיקון הפרות של מפתחות כפולים.
פתרון בעיות שקשורות למפתחות כפולים
אם זיהיתם אינדקס ייחודי כפול או אם פעולת יצירה מחדש של אינדקס נכשלת בגלל שגיאה של הפרת מפתח כפול, צריך לבצע את השלבים הבאים כדי למצוא ולהסיר את המפתחות הכפולים.
מחולצים את
key_columnsמכותרת הטבלהNOTICE, כמו שמוצג בפלט לדוגמה הקודם. בדוגמה הבאה, עמודת המפתח היאemail.קוד לדוגמה
NOTICE: checking index=users_email_key on table=users key_columns="(email)"
משתמשים בערכים האלה בפרמטר KEY_COLUMNS בשאילתה שבשלב 3.
מחפשים את הסכימה של הטבלה. משתמשים ב-
psqlכדי להתחבר למסד הנתונים ומריצים את הפקודה הבאה:קוד לדוגמה
הערך בעמודה\dt TABLE_NAME
schemaהוא הערך שבו משתמשים עבור SCHEMA_NAME בשאילתה בשלב 3.לדוגמה, בשאילתה הבאה:
\dt games
הפלט אמור להיראות כך:
List of relations Schema | Name | Type | Owner --------+-------+-------+---------- public | games | table | postgres (1 row)
מריצים את ההצהרות הבאות כדי לכפות סריקה מלאה של הטבלה ולקבל מפתחות כפולים.
קוד לדוגמה
SET enable_indexscan = off; SET enable_bitmapscan = off; SET enable_indexonlyscan = off; SELECT KEY_COLUMNS, count(*) FROM SCHEMA_NAME.TABLE_NAME GROUP BY KEY_COLUMNS HAVING count(*) > 1;
בהצהרה שלמעלה, KEY_COLUMNS מייצגות עמודה אחת או יותר שנכללות באינדקס הייחודי או במפתח הראשי בטבלה שאתם בודקים. הם זוהו כשבדקתם אם יש הפרות של מפתחות ייחודיים. ההצהרה מחזירה את המפתחות הכפולים ואת מספר הכפילויות של כל אחד מהם.
לדוגמה, בשאילתה הבאה:
SELECT name,count(*) FROM public.TEST_NAMES GROUP BY name HAVING count(*) > 1;
הפלט אמור להיראות כך:
name | count --------------------+------- Johnny | 2 Peter | 2 (2 rows)
במקרה כזה, ממשיכים לשלב הבא כדי להסיר את המפתחות הכפולים.
אם אחת מהעמודות ב-KEY_COLUMNS היא NULL, אפשר להתעלם ממנה כי אילוצים ייחודיים לא חלים על עמודות NULL.
אם לא נמצאו מפתחות כפולים, אפשר לעבור אל תיקון אינדקסים לא עקביים.
אופציונלי, אבל מומלץ: יוצרים גיבוי לרשומות שמכילות מפתחות כפולים. מריצים את ההצהרה הבאה כדי ליצור רשומות גיבוי:
קוד לדוגמה
CREATE TABLE SCHEMA_NAME.TABLE_NAME_bak AS SELECT * FROM SCHEMA_NAME.TABLE_NAME WHERE (KEY_COLUMNS) IN ((KEY_VALUES));
בהצהרה הזו, KEY_VALUES היא רשימת ערכים שהועתקו מהתוצאה של השלב הקודם. לדוגמה:
קוד לדוגמה
CREATE TABLE public.TEST_NAMES_bak AS SELECT * FROM public.TEST_NAMES WHERE (name) IN (('Johnny'),('Peter'))
אם יש מספר גדול של שורות, קל יותר להחליף את הפרמטר ((KEY_VALUES)) בהצהרת
INבהצהרתSELECTמשלב 2 בלי הפרמטרcount. לדוגמה:קוד לדוגמה
CREATE TABLE SCHEMA_NAME.TABLE_NAME_bak AS SELECT * FROM SCHEMA_NAME.TABLE_NAME WHERE (KEY_COLUMNS) IN ( SELECT (KEY_COLUMNS) FROM SCHEMA_NAME.TABLE_NAME GROUP BY (KEY_COLUMNS) HAVING count(*) > 1);
מוסיפים למשתמש תפקיד שכפול כדי להשבית את הטריגרים:
קוד לדוגמה
ALTER USER CURRENT_USER with REPLICATION; SET session_replication_role = replica;
מריצים את ההצהרה הבאה כדי למחוק את המפתחות הכפולים:
קוד לדוגמה
BEGIN; DELETE FROM SCHEMA_NAME.TABLE_NAME a USING ( SELECT min(ctid) AS ctid, KEY_COLUMNS FROM SCHEMA_NAME.TABLE_NAME GROUP BY KEY_COLUMNS HAVING count(*) > 1 ) b WHERE a.KEY_COLUMNS = b.KEY_COLUMNS AND a.ctid <> b.ctid;
לדוגמה, עבור KEY_COLUMNS עם כמה עמודות:
קוד לדוגמה
כאשר day ו-rnum הם KEY_COLUMNS.DELETE FROM public.test_random a USING ( SELECT min(ctid) AS ctid, day, rnum FROM public.test_random GROUP BY day, rnum HAVING count(*) > 1 ) b WHERE a.day=b.day and a.rnum = b.rnum AND a.ctid <> b.ctid;
הפעלת ההצהרה הזו תשמור שורה אחת ותמחק את השאר בכל קבוצה של שורות כפולות. אם רוצים לשלוט בגרסה של השורה שתימחק, מריצים את המסנן הבא בהצהרת המחיקה:
קוד לדוגמה
DELETE FROM SCHEMA_NAME.TABLE_NAME WHERE ( KEY_COLUMNS, ctid) = (KEY_VALUES, CTID_VALUE);
כדי לוודא שהפקודה
DELETEהחזירה את המספר הצפוי של שורות בלי שגיאות:מריצים את ההצהרה הבאה כדי לזהות את השורות שבהן הטבלאות השתנו:
קוד לדוגמה
SELECT schemaname, relname, n_tup_del, n_tup_upd FROM pg_stat_xact_all_tables WHERE n_tup_del+n_tup_upd > 0;
אם כל השורות נכונות, מאשרים את העסקה
DELETE:קוד לדוגמה
END;
אם יש שגיאות, מבטלים את השינויים כדי לתקן אותן:
קוד לדוגמה
ROLLBACK;
אחרי מחיקת העותקים הכפולים של המפתחות, אפשר ליצור מחדש את האינדקס.
תיקון של אינדקסים לא עקביים
בקטעים הבאים מוסבר איך לתקן את חוסר העקביות באינדקס שנמצא במופע.
בהתאם להגדרות של מסד הנתונים, יכול להיות שתצטרכו לבצע את הפעולות הבאות לכל אינדקס שזוהה בשלבים הקודמים:
אם פעולת יצירת האינדקס מחדש נכשלת בגלל הפרות של מפתח זר, צריך למצוא את ההפרות האלה ולתקן אותן.
מריצים שוב את פעולת יצירת האינדקס מחדש.
הכנה ליצירה מחדש של האינדקס
איך מוצאים את גודל האינדקס
יצירת אינדקס למסדי נתונים גדולים דורשת יותר זמן מאשר יצירת אינדקס למסדי נתונים קטנים. כדי לשפר את המהירות של פעולות אינדוקס ואינדוקס מחדש של מסדי נתונים גדולים יותר, אפשר להקצות יותר זיכרון וכוח עיבוד לפעולות האלה. זהו שלב חשוב בתכנון פעולת יצירת האינדקס מחדש. אחרי שמגלים את גודל האינדקס, אפשר להגדיר את גודל הזיכרון שמשמש לפעולת יצירת האינדקס מחדש ולהגדיר את מספר העובדים המקבילים.
מריצים את ההצהרה הבאה כדי למצוא את גודל האינדקס בקילובייט של האינדקס שרוצים לתקן:
קוד לדוגמה
SELECT i.relname AS index_name, pg_size_pretty(pg_relation_size(x.indexrelid)) AS index_size FROM pg_index x JOIN pg_class i ON i.oid = x.indexrelid WHERE i.relname = 'INDEX_NAME';
הפלט של ההצהרה הזו אמור להיראות כך:
תשובה
index_name | index_size ------------+------------ my_index | 16 kB (1 row)
הגדרת גודל הזיכרון לשימוש באינדוקס מחדש
בהתאם לגודל האינדקס שנקבע בקטע הקודם, חשוב להגדיר את הערך המתאים ל-maintenance_work_mem. הפרמטר הזה מציין את כמות הזיכרון שבה יש להשתמש לפעולת יצירת האינדקס מחדש. לדוגמה, אם גודל האינדקס גדול מ-15GB, מומלץ לשנות את זיכרון התחזוקה. מידע נוסף זמין במאמר בנושא הגדרת דגל מסד נתונים.
יצירת אינדקס למסדי נתונים גדולים דורשת יותר זמן מאשר יצירת אינדקס למסדי נתונים קטנים. כדי לשפר את המהירות של פעולות אינדוקס ואינדוקס מחדש, מומלץ להגדיר את maintenance_work_mem ל-2% לפחות מזיכרון המופע עבור מופעים עם זיכרון של 4GB ומעלה במהלך פעולת האינדוקס מחדש הזו.
הגדרת מספר העובדים המקבילים
כדי להגדיל את מספר העובדים המקבילים לאינדוקס מחדש, אפשר להגדיר את הפרמטר max_parallel_maintenance_workers במסדי נתונים באמצעות PostgreSQL 11 ואילך. ערך ברירת המחדל של הפרמטר הזה הוא 2, אבל אפשר להגדיר ערך גבוה יותר כדי להגדיל את מספר העובדים לאינדוקס מחדש. במקרים עם 8 ליבות vCPU או יותר, מומלץ להגדיר את ערך הדגל max_parallel_maintenance_workers ל-4.
מידע נוסף זמין במאמר בנושא הגדרת דגל מסד נתונים.
יצירה מחדש של האינדקס
אפשר ליצור מחדש אינדקס בלי לחסום את עומס העבודה של הייצור באמצעות כלי השירות pg_repack. הכלי הזה מאפשר לבצע אינדוקס מחדש באופן אוטומטי ופשוט, בלי השבתה, במיוחד בגרסאות 11 ומטה של PostgreSQL, שבהן אין את הפעולה REINDEX CONCURRENTLY. לצורך התהליך הזה, צריך להשתמש בגרסה 1.4.7.pg_repack
כדי ליצור מחדש את האינדקס באמצעות pg_repack:
מורידים, מהדרים ומתקינים את כלי השירות
pg_repackמהדף pg_repack.Debian GNU/Linux 11
לנוחותכם, מומלץ למשתמשי Debian Linux להוריד ולהתקין את הקובץ הבינארי הזה שניתן להפעלה ומוכן מראש לפלטפורמת Linux x86_64.
גיבוב SHA256 של הקובץ הבינארי:
ecfee54364a625d9365d86cb27940b458bfdb0d6ff63bb88063039256fbde96fכדי לוודא שגרסת Linux היא Debian GNU/Linux 11, מריצים את הפקודה
hostnamectl.הידור עצמי
מורידים, מהדרים ומתקינים את כלי השירות
pg_repackמהדףpg_repack.יוצרים את התוסף
pg_repack:קוד לדוגמה
CREATE EXTENSION pg_repack;
מריצים את הפקודה הבאה כדי ליצור מחדש את האינדקס באופן מקביל:
קוד לדוגמה
pg_repack -h HOSTIP -p 5432 -U USERNAME -d "DATABASE_NAME" -i "INDEX_NAME" --no-superuser-check --no-kill-backend --wait-timeout=3600
הפלט של הפקודה אמור להיראות כך:
תשובה
INFO: repacking index "public.t_i_key"
אם אירעו שגיאות במהלך ההרצה של
pg_repack, אפשר לתקן את השגיאה ולנסות שוב. אחרי שמתקנים את כל האינדקסים של המפתחות הייחודיים ואת האינדקסים של המפתחות הראשיים, צריך לבדוק אם יש הפרות של מפתחות זרים ולתקן את כל ההפרות שנמצאו.
איתור ותיקון של הפרות של מפתח זר
מידע על איתור ותיקון של הפרות של מפתחות זרים זמין במאמר איתור ותיקון של הפרות של מפתחות זרים.
מעקב אחרי פעולות של יצירת אינדקס מחדש
לפעמים, יכול להיות שסשנים אחרים יחסמו את פעולת האינדוקס מחדש. מומלץ לבדוק את זה כל 4 שעות. אם פעולת יצירת האינדקס מחדש נחסמת, אפשר לבטל את הסשן שחוסם אותה כדי שהיא תוכל להסתיים.
כדי לזהות סשנים חסומים וסשנים בהמתנה ואז לבטל אותם בפעולת INDEX:
כדי לזהות סשנים חוסמים, מריצים את השאילתה הבאה:
קוד לדוגמה
SELECT pid, usename, pg_blocking_pids(pid) AS blocked_by, query AS blocked_query FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0;
כדי לבטל סשן, מריצים את השאילתה הבאה באמצעות ה-PID של הסשן החוסם מהשאילתה הקודמת:
קוד לדוגמה
SELECT pg_cancel_backend(PID);
אימות העקביות של האינדקסים
צריך להמשיך לבדוק אם יש אי-התאמות באינדקסים עבור כל אינדקס לא עקבי. אחרי שמתקנים את כל האינדקסים הלא עקביים ואת הפרות המפתח במופע, אפשר לבדוק שאין בעיות באמצעות השלבים שבקטעים הקודמים: