# почему 14 KB?
вот в пример реальные запросы.
вот сайт который влезает в 14кб:

а вот который не влезает:

download - это и есть контент сайта: 1 ms против 63 ms, т.е. разница скорости в 63 раза, при разнице веса всего 7 раз.
ета писанина по верхам о потрохах tcp, dns, tls, slow start, congestion collapse 1986 года, да заодно филососия small web: почему маленький сайт - это уважение к пользователю, а не дрочка на циферки.
статья отчасти ларпинг попс, ибо об этом уже писали… но я чет хочу расписать своими словами, да с той инфой, что удалось откопать мне, да может с какими-то своими мыслями, опытом и потугами.
# чотамкак tcp
прежде чем сервер отдаст хоть байт html, происходит овердохуя всего
## three-way handshake
любое tcp-соединение начинает с трёх рукопожатий:
- клиент шлёт
SYN - сервер отвечает
SYN-ACK - клиент добивает
ACK
три сообщения туда-сюда на поздороваться, и только потом можно слать данные (+1rtt)
есть tcp fast open (TFO) - позволяет впихнуть данные в SYN-пакет, но работает только для повторных соединений и требует поддержки с обеих сторон.
## congestion collapse 1986
в начале 1986 интернет пытался умереть: кол-во узлов перевалило за 5000, bandwidth был неравномерным, tcp без контроля перегрузки просто забивал канал пакетами, отчего маршрутизаторы дропали, хосты перепосылали, дропали ещё больше - получился снежный ком, при котором пропускная способность падала в 1000 раз.
ето и назвали congestion collapse.
В 1988 Van Jacobson опубликовал статью “Congestion Avoidance and Control” где описал четыре алгоритма, которые спасли интернет: slow-start, congestion avoidance, fast retransmit, fast recovery. - эти 4 штуки стали обязательной частью tcp, по сути предотвратили коллапс интернета, пока трафик рос экспоненциально.
## slow start
идея состоит в том, что сервер не знает какая пропускная способность у канала до клиента - если сразу вжарить 100 пакетов - можно забить сеть и получить тот самый congestion collapse, потому начинаем с малого и разгоняемся.
сервер держит переменную cwnd(congestion window) - сколько пакетов можно отправить не дожидаясь ACK.
initcwnd - начальное значение окна:
- изначально: 1 сегмент
- RFC 3390: 4 сегмента
- RFC 6928 (2013): 10 сегментов
// google в 2013 продавил повышение до 10 через эксперименты, показав что хуже не становится, некоторые cdn (cloudflare, fastly) ставят 30 и больше.
дальше экспоненциальный рост:
- сервер отправил 10 пакетов - ждёт ACK
- пришёл ACK - окно удвоилось до 20
- отправил 20 - ждёт
- окно до 40, потом до 80…
: каждый такой цикл - это один rtt.
a если ответ влезает в initcwnd - улетает одним пакетом без ожидания - получается one round trip.
на больших файлах (картинки, видево) это не проблема - tcp успевает разогнаться за несколько rtt, но в большинстве случаев на сайтах файлы меньше 100kib - начинается боль…
например: файл на 100mib первые пару rtt разгоняется, потом льёт на полной скорости - эти первые rtt - капля в море, ведь всего их получается дофига.
а вот файл на 30kb как раз попадает в зону, где slow start только начал разгоняться, но не успел, т.е. если 1rtt выполнялся за 50ms, то два уже будет 100ms и т.п., если таких файлов дцать и каждый в новом соединении - каждый проходит этот путь по новой.
# кто сколько жрёт
стартует целый ритуал, прежде чем сервер дойдёт до отправки html, где каждый этап это rtt:
dns : +1rtt : резолвинг домена обычно кешируется, т.ч. в большинстве случаев 0ms, но cold lookup - честный rtt.
tcp handshake : +1 rtt : SYN, SYN-ACK, ACK без вариантов.
tls : +2 rtt (или +1 с tls 1.3) : с zero-rtt resumption можно и ваще 0, но это для повторных соединений.
Кста, сертификат может сам по себе весить несколько килобайт и не влезать в initcwnd - Simon Hørup Eskildsen разбирал датскую газету information.dk - сертификат 6908 байт, initcwnd был 3 (старый линукс): серт не влез в окно, и tls handshake занял не 2 а 3 rtt, просто потому что сервер ждал ACK посреди отправки сертификата.
Итого: минимум 3-4 rtt до первого байта html, до начала отправки контента, а после неё уже стартует slow start.
# спутник
крутой пример от Nathaniel : с его статьи бол-во источников на эту тему слизали инфу - я в т.ч. :D
oil rig: чуваки забыли кости для d&d и хотят зайти на missingdice.com через спутниковый интернет.
путь пакета:
телефон -> wifi-router -> тарелка -> спутник (35786 км) -> наземная станция -> сервер
1rtt:
1ms (роутер) + 120ms (земля-спутник) + 120ms (спутник-земля) + 60ms (наземный-сервер)
: примерно 300ms в одну сторону, а полный круг 612ms (лишь на 1rtt)
с 4 rtt (dns + tcp + 2x tls) до первого байта 2.5 секунды, с slow start на несколько раундов - все 4-5 секунд. - всё это ради простенького сайта с костями, js которого бы влез в 14кб, если бы разраб задумался над оптимизоном и т.д.
# 14 600
- mtu ethernet: 1500 байт
- заголовок ip: 20 байт
- заголовок tcp: 20 байт
- полезная нагрузка: 1500 - 40 = 1460b на пакет
- initcwnd = 10 пакетов
- 10 x 1460 = 14600 байт ~= 14.25 KB
при vpn подключении и т.д. mtu может быть ниже из-за доп оверхеда - типа 1320-1350b, иль около того (на моей практике обычн такие значения проходят)
это очень частая проблема в сложных закрытых сетях, типа корпоротивных - пакеты могут просто дропаться.
быстро прочекать какого размера пакеты долетают до сервера можно командой:
// на linux
ping -f -s 1472 -c 10 google.com
// на windows
ping -f -l 1472 google.com
# step function
- 14kb - 1 rtt.
- 15kb - 2 rtt.
- 29kb - 2 rtt.
- 30kb - 3 rtt.
добавляется целый rtt каждый раз когда перепрыгиваешь границу окна, т.е. разница между 14kb и 15kb может быть больше, чем между 15kb и 40kb.
ещё прикол в том, что html сжимается gzip-ом, где 27kb raw легко превращаются в 12kb gzip, что всё ещё в initcwnd, т.е. 1rtt (пример: sccl.cc не знаю насколько актуален, щас актуален)
14KB Club считает uncompressed - т.е. по правилам клуба надо влезть в 14kb сырым html (хотя вроде умер этот клуб уже, не обновляется нихрена)
но в реальности сайт на 20-30kb raw с gzip почти не уступает “чистому” 14kb сайту - разница в наносекунды на decompression.
хотя кабута логичнее было бы считать compressed, ну да ладно.
# http/2, http/3?
есть мнение что мультиплексирование убивает правило 14kb.
http/2 позволяет слать несколько запросов в одном tcp-соединении, но tcp slow start никуда не девается - это свойство tcp, а не http - первый запрос всё равно проходит через slow start.
http/3 (quic) работает поверх udp, но спецификация quic рекомендует тот же initcwnd в 10 пакетов.
+ httpS-рукопожатие и http/2-префейс съедают часть начального окна - Barry Pollard разбирал что к моменту отправки html окно уже может быть больше 10 - ACKи слались во время tls-переговоров, т.ч. “ровно 14kb” - это упрощение.
механика та же - понимать её полезнее чем дрочить на циферки.
# small web
в small web-е встречаются как и жирные неоптимизированные сайты, как и везде, так и более гиковские, максимально простые, типа plain .md text - но они все больше не про циферки, а про философию простоты.
## клубы
1mb club, 512kb club, 250kb club, 14kb club, ещё всякие no-js.club, nocss.club.
512kb club на главной пишет прямым текстом:
The internet has become a bloated mess. Huge JavaScript libraries, countless client-side queries and overly complex frontend frameworks are par for the course these days.
и ведь правда, там же отличный пример с NYT - газетный лейаут, текст и картинки - весит 9 мегабайт за 891 запроса, половина из которых js, что полный пиздец, сайт, который по природе не может быть тяжёлым, зачем-то весит как простенькая игра.
## motherfucking website
на фоне всего этого родился motherfuckingwebsite.com - один html: без css и js, 5kb
потом появился bestmotherfucking.website - минимальный css, 78 байт inline.
потом thebestmotherfucking.website - уже с тёмной темой, но 132kb.
есть ещё justfuckingusehtml.com - 29kb just html.
всю эту линейку можно считать манифестом того, что интернет в говне.
## уважение
выше был пример со спутником - 612ms на rtt - человек на нефтяной платформе хочет зайти на сайт и грузится 5 секунд - не по той причине, что контент какой-то тяжелый - просто разрабу было похуй.
маленький сайт - это уважение к пользователю: к его времени, трафику, батарее(если это носимое устройство), к тому что не у всех оптоволокно и 5G.
цитата Ilya Grigorik в HPBN:
No bit is faster than one that is not sent.
суть в том, что нахуй ненад слать пользователю то, что ему реально ненужно. красивый лейаут пользователю в какой-то степени нужен, но 50kb скриптов на анимации кнопок - вряд ли.
# оптимизация моего сайта
у меня есть сайтец sccl.cc, что запилен на zine.
до переезда на Zine был Astro и сайт весил 150+кб, см migration-from-astro-to-zine
че имели по началу:
- html (~15kb)
- space mono + martian mono woff2 (~40kb)
- аватарка png (~2mb)
- фавиконка png (~1mb)
на скрине уже немного заоптемайзенный, было хуже :skull:
4 запроса на страницу с контентом, который по факту весит нихрена, и это с keep-alive и http/2 - setup один раз, но каждое новое соединение, каждая вкладка - всё заново.
мой план: всё в один html, base64 инлайн всего, 0 внешних запросов, оставить js: влезть в initcwnd со сжатием gzip.
## шрифт
шрифты это самое жирное - у меня было 2 шрифта + bold-версии = 40kb.
по-хорошему кастомные шрифты нахуй не нужны, но эстетика победила - решено оставить один, выпилив из файла всё кроме ASCII.
инструмент: pyftsubset из fonttools - выпиливаются non-latin диапазоны, символы которых нет на сайте и т.д.
итог: 4.2kb, в base64 - 5.7kb текста в html.
upd: потом стало 2kb на шрифт, точных цифр не скажу, просто над было вырезать больше символов.
## аватарка
пиксель-арт зарендерен в 400%: png с альфа-каналом - на деле жирно и бесполезно.
шаколизатором удалось сжать в 3 цвета, png - 1.9kb. Конвертация в webp - 702 байта.
base64: 940 байт.
## favicon
те же манипуляции: 199 байт в PNG (для мелких картинок он лучше чем webp).
base64: 268 байт.
## js
Анимированные кнопки, анимированный фон, пасхалка с анимацией. Всё для красоты. Реально полезного - кот наплакал.
Но функционал хотелось сохранить. Всё ушло в инлайн. ~4kb скриптов, без которых сайт работал бы не хуже. Но красиво.
## итог
до: 4 запроса - ~50kb
пpосле: 1 запрос - 12kb gzip / 27kb raw
влезает в initcwnd - один tcp-пакет данных после рукопожатий.

# дилемма инлайна
инлайн всего - компромисс со своей ценой
## кеширование
шрифт в base64 внутри html - не кешируется - соответсвенно при переходе на другую страницу бровсер качает его по новой, потому что он часть html. в случае с отдельным файлом шрифт бы закешировался после первого раза и условно вторая страница грузилась бы без него.
## когда что выбирать
всё в одном html (base64 шрифты)
- 2 страницы по 14kb каждая
- каждая - 1 rtt
- но каждый раз жрёшь полные 14kb, даже если шрифт повторяется
html + отдельный шрифт
- 2 страницы по 8kb + шрифт 4kb отдельно
- первый заход: 2 запроса
- переходы: только 8kb, шрифт закеширован
в реальном интернете разницу между 14kb и 8kb не почувствовать - оба влезают в один initcwnd, а вот делей между запросами - очень даже чувствуется.
если html реально большой, иль переходов между страницами много - стоит кешировать шрифт.
## overhead
инлайн весит чуть больше внешней зависимости - base64 добавляет ~33% к бинарным данным: если ты и так не превышаешь initcwnd с внешними ресурсами - инлайн нахуй не нужен.
oн имеет смысл когда разница между влезть и не влезть в 1rtt - критична, в случае sccl.cc oно того стоило.
? чо в итоге
14kb - не magic number, а следствие архитектуры tcp, которая проектировалась для надёжности.
понимание транспортного уровня даёт больше чем слепое следование правилу 14kb: гдет нужен инлайн, гдет отдельные файлы с кешированием, гдет 30kb raw с gzip - почти то же что 14kb без сжатия.
но филососия остаётся: чем меньше - тем быстрее, да чем меньше говна тащишь на сайт, тем больше уважения к пользователю.
comments