WEBA

שדרוג ל-Go 1.27 מקצר פי חמישה קריאות HTTP בלי לגעת בקוד

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

538 מילים30 עובדות אומתו

קריאת HTTP קצרה ב-Go נראית תמיד תמימה. אתם שולחים בקשת GET, בודקים את הסטטוס, וקוראים ל-Close על גוף התגובה. מה שקרה מתחת למכסה המנוע עד עכשיו היה רחוק מלהיות תמים. בפוסט שפורסם אתמול, 14 באוקטובר 2026, חשפו Nicholas Husin ו-Damien Neil בבלוג הרשמי של Go שינוי פנימי קטן שמשנה את התמונה מהיסוד.

התוצאה במספרים חדה. בנצ'מרק של 100 בקשות עוקבות מול index.golang.org בחיבור HTTP/1 ארך כ-710 מילי-שניות בגרסה 1.26. אותה בדיקה בדיוק בגרסה Go 1.27 ארכה כ-150 מילי-שניות בלבד. פי חמישה מהר יותר, על אותו מחשב, בלי לשנות שורת קוד אחת.

סגירה פשוטה כבר לא מנתקת

עד היום, הפונקציה net/http.Get הייתה קוראת את הכותרות ומחזירה מיד את המבנה של net/http.Response. גוף התגובה נשאר על שקע הרשת והמתין למימוש. אם סגרתם את הבקשה מבלי לקרוא את התוכן עד הבייט האחרון, הספרייה הניחה שאי אפשר לסנכרן את החיבור לבקשה הבאה. במקום למחזר את ערוץ התקשורת הקיים, המערכת זרקה אותו לפח ופתחה חיבור חדש לגמרי.

שדרוג ל-Go 1.27 מבצע ריקון אסינכרוני אוטומטי של תגובות HTTP/1 ברקע ומאפשר שימוש חוזר בחיבור.

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

המחיר של בייט אחד בודד

בפרוטוקול HTTP/1, כל הבקשות והתשובות על אותו חיבור זורמות ברצף אחד אחרי השני. כדי לדעת איפה מתחילה התשובה הבאה, המערכת חייבת לוודא שהתשובה הקודמת נקראה במלואה, לפי Content-Length או קידוד Chunked. ברגע שנשאר אפילו תו ירידת שורה בודד שלא נקרא, החיבור נחשב לא מתואם ומתנתק.

זה קורה לעיתים קרובות גם כשבטוחים שקראתם הכל. מפענח כמו encoding/json.Decoder עוצר בדיוק בסוגר המסולסל שסוגר את האובייקט. אם השרת שלח אחריו תו רווח או ירידת שורה, החיבור נזנח. פתיחת חיבור חדש דורשת לחיצת יד של TCP שנמשכת סבב רשת אחד (1 RTT), ולאחריה לחיצת יד של TLS שגוזלת עוד סבב אחד או שניים. בתקשורת בין אזורים גאוגרפיים שונים, השהיית הרשת מגיעה בקלות ל-50 מילי-שניות לכל כיוון. שרפתם 300 מילי-שניות שלמות רק על הקמת החיבור.

המנגנון האוטומטי מאחורי הקלעים

בגרסה 1.27, רכיב net/http.Transport לוקח את העבודה לידיים. כשקוראים לפעולת Close, המערכת מרוקנת את גוף התגובה ברקע בצורה אסינכרונית, מבלי לחסום את התוכנית הראשית. המנגנון מוגבל בדיוק לעד 256 קילובייט ועד 50 מילי-שניות של המתנה. אם הריקון מסתיים לפני הבקשה הבאה, החיבור ממוחזר מיד; אם לא, הוא ממתין במאגר החיבורים הלא-פעילים לשימוש עתידי.

ב-Go 1.27, רכיב net/http.Transport ירוקן אוטומטית ברקע גוף תגובה שלא נקרא ב-HTTP/1 למשך עד 50 מילי-שניות או 256 קילובייט.פוסט רשמי בבלוג של Go

יש מקרים שבהם המנוע מוותר מראש. אם התגובה כוללת כותרת Connection: close, המערכת יודעת שהשרת ממילא מתכוון לנתק את השקע, ונמנעת מריקון מיותר. חשוב לדעת: ב-HTTP/2 התופעה הזו לא קיימת מלכתחילה, מפני שהוא מנהל ריבוי ערוצים במקביל על גבי חיבור בודד, כך שתגובה פתוחה אחת לא חוסמת בקשות אחרות.

אפשר למחוק את קוד המעקף

לאורך השנים, מפתחי Go התרגלו להדביק פונקציות עזר כמו drainAndCloseBody. הן כללו שימוש ב-io.Copy לתוך io.Discard, עטיפה ב-goroutine עם ערוצים וטיימרים כדי למנוע דליפות זיכרון. הקוד הזה היה מכוער, מסורבל ונטה להישבר.

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

השוואת התנהגות net/http לפי גרסת Go ותרחיש
תרחיש שימושהתנהגות עד Go 1.26התנהגות ב-Go 1.27
קריאת קוד סטטוס ללא קריאת הגוף (עד 256KB)החיבור נסגר ונפתח מחדשריקון אסינכרוני ושימוש חוזר בחיבור
פענוח JSON שנעצר לפני תו ירידת שורה בסוףהחיבור נסגר עקב בייט שלא נקראריקון אוטומטי של השארית ושימוש חוזר
גוף תגובה שנותרו בו מעל 256KBהחיבור נסגרהחיבור נסגר לאחר חריגה מהמגבלה
תגובה עם כותרת Connection: closeהחיבור נסגר מיידיתדילוג על ניסיון הריקון וסגירה מיידית
תקשורת בפרוטוקול HTTP/2ריבוי ערוצים ללא תלות בריקון הגוףריבוי ערוצים ללא שינוי בהתנהגות

השדרוג הזה מוריד עוד דאגה אחת מהראש של מי שמנהל שירותי רשת עמוסים.

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

מה לעשות עכשיושדרגו ל-Go 1.27 ומחקו מעקפי io.Copy ידניים שמרוקנים תגובות HTTP/1.

מקורות

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

מה שואלים על זה

האם צריך לשנות שורת קוד כדי שהאופטימיזציה תעבוד?

לא. האופטימיזציה פועלת באופן אוטומטי ושקוף בתוך net/http ברגע שמשדרגים ל-Go 1.27, בתנאי שמשתמשים בקריאת Close הרגילה על גוף התגובה.

האם השינוי משפיע על חיבורי HTTP/2?

לא. בפרוטוקול HTTP/2 החיבורים מנוהלים באמצעות ריבוי ערוצים מקבילי (multiplexing), כך שגוף תגובה שלא נקרא בערוץ אחד מעולם לא מנע שימוש חוזר באותו חיבור TCP.

מה קורה אם נותרו בגוף התגובה יותר מ-256 קילובייט?

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

האם ריקון התגובה האוטומטי מעכב את סגירת הבקשה בתוכנית?

לא. הריקון מתבצע בצורה אסינכרונית ברקע על ידי net/http.Transport, ולכן הקריאה ל-Close חוזרת מיד ואינה חוסמת את המשך ביצוע התוכנית שלכם.