
Go 1.27 автоматично очищує непрочитані тіла відповідей HTTP/1 для швидшого повторного використання з'єднання
Go 1.27 покращує повторне використання з'єднання HTTP/1, автоматично очищуючи непрочитані тіла відповідей. Тестування продуктивності демонструє, що Go 1.27 працює майже в п'ять разів швидше за Go 1.26 при виконанні тих самих операцій.
Непрочитані тіла відповідей перешкоджали повторному використанню з'єднання HTTP/1
Пакет net/http підтримує HTTP/1.0, HTTP/1.1 та HTTP/2, які працюють через TCP, а з HTTPS використовує звичайний TLS. Встановлення нового з'єднання обходиться дорого: TCP потребує одного повного RTT для рукостискання, TLS 1.2 — 2 RTT, і лише після 3 RTT можна надіслати перший HTTP-запит. Затримка між регіонами часто сягає 50 мілісекунд або більше, тому встановлення з'єднання може бути значно дорожчим за передачу невеликої відповіді.
Тому HTTP-клієнти та сервери намагаються повторно використовувати наявні з'єднання. У HTTP/1.1 з'єднання за замовчуванням постійні, проте відповіді надсилаються строго послідовно в одному потоці. Це означає, що net/http не може перевірити наступну відповідь, доки поточне тіло не буде повністю прочитане, як визначено заголовками Content-Length або Transfer.encoding.
У блозі Go Ніколас Хуссейн та Дем'єн Ніл вказують на поширену помилку: функція, яка викликає http.Get, за допомогою defer викликає resp.Body.Close і повертає лише код статусу, а тіло взагалі не читає. Починаючи з Go 1.26, якщо Close залишається з непрочитаним тілом, net/http закриває з'єднання та створює нове для наступного запиту, знову витрачаючи час на встановлення. Проблема стосується і програм, які читають тіло, але завершують рано. Наприклад, encoding/json.Decoder зчитує JSON до закриваючої дужки, тому сервер, який завершує відповідь переводом рядка, залишає цей байт непрочитаним, і з'єднання не може бути повторно використане. HTTP/2 не має цієї проблеми, оскільки мультиплексує кадри в одному з'єднанні та не надсилає відповіді строго послідовно.
Go 1.27 автоматично очищує тіла
Починаючи з Go 1.27, net/http.Transport автоматично обробляє цю ситуацію. Він асинхронно очищає непрочитані тіла HTTP/1-відповідей протягом 50 мілісекунд або 256 кілобайт, залежно від того, який ліміт досягається першим. Якщо очищення завершується до наступного запиту, з'єднання повторно використовується; інакше створюється нове з'єднання, а старе потрапляє в пул бездіяльних з'єднань і може бути використане для майбутнього запиту. Реалізація зазвичай очищує тіло, коли відповідь має заголовок Connection: close, оскільки в цьому випадку очищення є зайвим.
Автори зазначають, що ця поведінка може змінитися у майбутніх версіях Go, коли з'являться інші можливі вдосконалення.
Результати бенчмарку та рекомендація
Покращення є значним. Тестова програма, яка викликає функцію перевірки простого статусу 100 разів до index.golang.org, розподілений сайт, що підтримує TLS 1.3, займає близько 710 мілісекунд на Go 1.26 і лише близько 150 мілісекунд на Go 1.27 — майже в п'ять разів швидше.
З новою поведінкою команда Go рекомендує розробникам просто закривати тіла HTTP-відповідей, коли вони більше не потрібні, використовуючи відомий шаблон defer resp.Body.Close(). Сторінки з ручними методами очищення, які використовували багато розробників протягом років, наприклад очищення тіл за допомогою io.Copy, обмеження очищених байтів або таймаут очищення, більше не потрібні. Автори розглядають це як частину філософії простоти Go: нехай код залишається чистим і простим, а складна частина обробляється net/http замість вас.
Джерела: Hacker News
SiTech — веброзробка з підтримкою AI
Створюємо швидкі та сучасні сайти й інтегруємо AI у бізнес-процеси. Маєте проєкт чи запитання? Із задоволенням допоможемо.