Если вы не читали первые 2 части, то найдите их в истории канала. Стоит начать с них.
Мы закончили на том, что даже при канале в 100Мбит/c, нужно более 600мс (а не 4мс), чтобы получить 50КБ данных при первом подключении, если клиент в Киеве, а сервер в Сан-Франциско.
Хотел перейти к решениям, но есть еще одна проблема про которую все-таки стоит сказать - "Head-of-line Blocking". На самом деле, когда говорят про "Head-of-Line Blocking", то могут подразумевать разное. Есть 2 варианта этой проблемы:
1️⃣ "Head-of-line Blocking" на уровне TCP
Мы рассмотрели ситуацию, когда у нас нет потерь пакетов, но на практике пакеты всегда теряются. Более того, TCP Slow Start увеличивает скорость пока не начнут теряться пакеты, потом значительно уменьшает скорость и начинает поднимать более медленно.
Потери пакетов могут приводить к "Head-of-line Blocking" на TCP уровне.
Попробую описать основную идею.
TCP отвечает за то, чтобы пакеты пришли в приложение в правильном порядке. Если сервер отправил:
[1][2][3][4][5]а получили мы (или в другом порядке)
[2][3][4][5]То эти пакеты находятся в TCP буфере получателя пока сервер отправляет нам повторно пакет
[1]. То есть, задача TCP протокола выстроить пакеты в правильную очередь перед тем, как они попадут в приложение. Это удобно, но далеко не всегда нужно. 2️⃣ "Head-of-line Blocking" на уровне HTTP/1.x
Тут немного другая ситуация.
Допустим, нам нужно сделать 10 HTTP запросов. Браузер отправляет запросы один за одним и получается, чтобы отправить новый, он должен дождаться результата предыдущего.
Схематически это выглядит так:
50ms: Kyiv ------запрос 1----> SFДля упрощения я упустил все моменты связанные с установкой соединения (TCP-handshake, TLS-handshake, TCP Slow Start).
20ms: работа бэкенда (запрос 1)
50ms: Kyiv <-----ответ 1------ SF
50ms: Kyiv ------запрос 2----> SF
20ms: работа бэкенда (запрос 2)
50ms: Kyiv <-----ответ 2------ SF
50ms: Kyiv ------запрос 3----> SF
20ms: работа бэкенда (запрос 3)
50ms: Kyiv <-----ответ 3------ SF
В связи с этим, в HTTP/1.1 появился "HTTP Pipelining". Суть - отправить сразу пачку запросов и ждать ответы.
"HTTP Pipelining" выглядит так:
50ms: Kyiv ------запрос 1----> SFЭто полезная штука (120мс против 360мс), но на практике она отключена в большинстве браузеров ввиду того, что реализации серверов часто содержат баги. Но даже, если бы это работало, все равно мы имеем проблему "Head of line blocking": если обработка первого запроса займет 1 секунду, то ответы не смогут вернуться раньше чем через секунду (поскольку первый запрос блокирует возврат остальных).
Kyiv ------запрос 2----> SF
Kyiv ------запрос 3----> SF
20ms: работа бэкенда (запрос 1)
работа бэкенда (запрос 2)
работа бэкенда (запрос 3)
50ms: Kyiv <-----ответ 1------ SF
Kyiv <-----ответ 2------ SF
Kyiv <-----ответ 3------ SF
Да, браузер может параллельно открывать 4-6 соединений (это с настройками по умолчания), но это лишь частично спасает ситуацию.
Проблемы с DNS. Я не обсуждал запросы к DNS ввиду:
✳️ В 99% случаев для DNS используется UDP (за редкими исключением, когда ответ не влазит в датаграмму, тогда может быть инициировано TCP соединение). То есть нам почти никогда не нужна установка соединения, что сильно уменьшает нашу проблему. Вопросы безопасности пока упустим.
✳️ Скорее всего, мы обращаемся к DNS серверу провайдера и сервер этот находится достаточно близко. Да, это все равно отдельный запрос, который тоже влияет на то, как быстро пользователь увидит страницу, но в детали пока вдаваться не будем.
Теперь точно можно переходить к обсуждению того, как с этим всем жить и что делать дальше.
Продолжение следует...