הדוח שלכם מסתיים בכשנייה על מארח מסד הנתונים ובארבע דרך ה-VPN, והמספר ש-psql מדפיס מצביע ישירות על מסד הנתונים. המספר הזה הוא שעון עצר בצד הלקוח, לא מדידה של השרת. הוא מכסה את ההוראה בדרך החוצה, את ההמתנה לתשובה ואת השורות בדרך חזרה. הפרדת שלושת אלה היא הדרך היחידה לדעת איזה צד לכוונן.

מה הטיימר של psql באמת מודד

\timing on מפעיל שעון עצר ממש לפני שהלקוח כותב את ההוראה לשקע ועוצר כשנקראה השורה האחרונה של אותה תוצאה. הביצוע של השרת עצמו יושב אי שם בתוך החלון הזה, וכך גם כל אלפית שנייה שהחבילות מבלות במנהרה. שום דבר בפלט לא מפריד ביניהם. EXPLAIN גם מדפיס מספר, אבל אותו מייצר השרת והוא מוציא את ההמתנה של הלקוח. שני מספרים, שתי שאלות שונות.

התחילו הפעלה אחת, השאירו את התזמון דלוק והריצו את ההוראה שמעניינת אתכם מתוכה. שימוש חוזר בהפעלה משאיר את עלות החיבור מחוץ למדידה הראשונה.

psql "host=db.internal port=5432 dbname=appdb user=app" \
  -c "\timing on" \
  -c "select count(*) from orders where created_at > now() - interval '1 day';"

המספר עדיין כולל את העברת התוצאה, וזה מה שרוצים כאן. הוא כבר לא כולל את חילופי החיבור והאימות שהוספה חדשה הייתה מוסיפה.

השוו הוראה אחת בשלושה נתיבים

הריצו את אותה הוראה דרך שקע Unix, דרך כתובת ה-LAN ודרך שם המארח של ה-VPN, והשוו בין השלושה. אם רק האחרון איטי, השאילתה אינה המשתנה.

כל הפעלה מתחברת קודם ורק אחר כך מודדת את ההוראה, כך שעלות החיבור נשארת מחוץ למספר בעוד ההעברה נשארת בפנים. שמרו על סעיף WHERE, על מספר השורות ועל מכונת הלקוח זהים, אחרת אתם משווים עומסי עבודה ולא נתיבים.

psql -h /var/run/postgresql -U app -d appdb -c "\timing on" -c "select count(*) from orders;"
psql -h 10.40.0.12 -U app -d appdb -c "\timing on" -c "select count(*) from orders;"
psql -h db.vpn.internal -U app -d appdb -c "\timing on" -c "select count(*) from orders;"

ב-LAN ההבדל בדרך כלל קטן מספיק כדי להתעלם ממנו; דרך VPN לעיתים קרובות לא. מיון שלוש התוצאות יגיד לכם אחר כך על איזו המתנה לתקוף.

שקע מקומי איטי פירושו עבודת שרת או תכנון. תוצאת LAN איטית פירושה השאילתה או הנפח שהיא מזיזה. רק תוצאת VPN איטית פירושה השהיה, MTU או רוחב פס זמין בנתיב, וכל שינוי אינדקס לא מתקן את זה.

חלצו את זמן הביצוע של השרת עצמו

השרת יכול לדווח כמה זמן הוא השקיע, וזה המספר שיש להעמיד מול זה של הלקוח. EXPLAIN עם ANALYZE מריץ את ההוראה ומדפיס זמן תכנון וביצוע בתוך התוכנית, בעוד BUFFERS מוסיף התנהגות מטמון שלעיתים קרובות מסבירה ריצה חמה לעומת קרה. אל תכוונו אותו להוראות כתיבה אלא אם אתם מתכוונים לכתוב. על עותק לקריאה בלבד זה המקום הבטוח ביותר להתחיל, וכמו כן מדריך psql מתעד את היקף הטיימר בצד הלקוח.

עבור הוראות שהאפליקציה כבר מריצה, pg_stat_statements מצבר את הביצוע בצד השרת בלי להריץ דבר מחדש. השורות הן לפי שאילתה מנורמלת, כך שערך אחד יכול לכסות כמה אתרי קריאה.

select calls, round(mean_exec_time::numeric, 2) as mean_ms, rows
from pg_stat_statements
order by mean_exec_time desc
limit 5;

שמות העמודות משתנים לפי גרסה: PostgreSQL 13 ואילך חושפים mean_exec_time, בעוד גרסאות ישנות יותר קוראות לזה mean_time, אז בדקו עם \d pg_stat_statements אם השאילתה הזו מחזירה שגיאה. הערכים האלה מוציאים את התכנון ומוציאים את הנסיעה אל הלקוח שלכם. בדיוק ההוצאה הזו היא מה שהופך אותם לשימושיים להשוואה.

ספרו נסיעות הלוך ושוב, לא רק אלפיות שנייה

דוח שנבנה ממאתיים הוראות קטנות תלויות מכפיל כל השהיה שהנתיב מוסיף במאתיים. הוראה אחת שמחזירה מיליון שורות משלמת את ההשהיה פעם אחת ואז משלמת רוחב פס לאורך זמן. שניהם נראים זהים בטיימר של לקוח, והם זקוקים לתיקונים הפוכים.

מדדו את מספר ההוראות במקום להעריך אותו. קראו את הסכום, הריצו את הדוח פעם אחת, ואז קראו שוב והחסירו.

select sum(calls) as calls from pg_stat_statements;
-- run the report here
select sum(calls) as calls from pg_stat_statements;

אם הדלתא גדולה וכל הוראה זולה בשרת, קיבוץ הוא התיקון שמשתלם. שלבו שאילתות תלויות, השתמשו בהוראה אחת מבוססת קבוצות או הניעו את הלולאה מסמן. ב-psql אינטראקטיבי, \set FETCH_COUNT 1000 גורם לתוצאה גדולה להגיע במנות ומשנה את התנהגות ההעברה והזיכרון של הלקוח. הוא לא מפחית עבודת שרת, ולכן לא יציל שאילתה איטית.

בדקו את המנהרה לשליחות חוזרות ולתקיעות MTU

כשהוראות קטנות מהירות ותוצאות גדולות נתקעות באמצע הדרך, השאילתה בדרך כלל תקינה והנתיב לא. מנהרה שה-MTU שלה נמוך מהקישור שמתחתיה, יחד עם ICMP חסום, יוצרת בדיוק את התבנית הזו: החבילות הראשונות מגיעות, הגדולות נעלמות, וההעברה תלויה עד שהיא מנסה שוב. מוני שליחות חוזרות בצד הלקוח מראים זאת.

עקבו אחר השקע ומוני הליבה בזמן העברה איטית במכוון. סכומי שליחות חוזרות הם מצטברים, אז רשמו אותם לפני ואחרי במקום לקרוא דגימה אחת.

ss -tin dst 10.40.0.12
netstat -s | grep -iE "retransmit|segments retransmitted"
ping -M do -s 1400 -c 3 10.40.0.12

שליחות חוזרות שעולות במהלך ההעברה, יחד עם ping שמצליח ב-1400 בתים אך נכשל ב-1472, מצביעות על בעיית MTU ולא על בעיית מסד נתונים. הורדת ה-MTU של ממשק המנהרה או הידוק MSS בשער הוא התיקון המקובל, ואותן שלוש פקודות מאשרות אותו אחר כך. אם השליחות החוזרות נשארות שטוחות בזמן שההעברה עדיין איטית, הביטו ברוחב הפס שמותר למנהרה ולא באובדן.

רצף קצר מכסה את הדוח האיטי הבא: השוו הוראה אחת מקומית, ב-LAN ודרך ה-VPN; קראו את ביצוע השרת מ-EXPLAIN או מ-pg_stat_statements; ספרו קריאות לפני ואחרי ריצה אחת; ועקבו אחר שליחות חוזרות בזמן שהתוצאה הגדולה נעה. כל שלב מוציא צד אחד של הנתיב. אף אחד מהם לא דורש לנחש איזה רכיב לאתחל.

מאמרים קשורים