Переезд: замерить фактическую задержку Beget ↔ Selectel СПб — оценка 10–15 мс не проверялась #3032

Closed
opened 2026-08-21 12:56:06 +00:00 by lekss361 · 2 comments
Owner

Эпик: #2989

Зачем

Цифра 10–15 мс взята по типовому Москва↔СПб, а не измерена. От неё напрямую зависит объём работ по пакетной записи скрапперов (см. соответствующую задачу): при 5 мс построчная запись, возможно, доживёт до лучших времён, при 40 мс — точно нет, и пакетность становится блокером окна, а не улучшением.

Это дешёвый замер, который снимает неопределённость с более дорогой задачи. Делать до того, как проектировать пакетную запись.

Что померить

  • RTT: mping/ping и tcptraceroute на 5432 — не только ICMP, он может ходить другим путём и приоритетом.
  • Задержку реального запроса к Postgres, не только сетевую: psql -c 'SELECT 1' в цикле, p50/p95/p99. Именно это число войдёт в расчёт.
  • Пропускную способность: iperf3 или хотя бы scp большого файла в обе стороны.
  • Разброс во времени — прогнать в пиковый и ночной час, джиттер важнее среднего.

Ограничение

Полноценно померить можно только после аренды сервера. До этого — оценить по любому арендованному в том же пуле Selectel ru-1 (СПб / зона доступности 2), где уже лежит бакет gendsgn-backups: аплоад бэкапа на него с Beget уже даёт наблюдаемые 100+ МБ/с, то есть канал широкий, вопрос только в RTT.

Приёмка

  • RTT p50/p95/p99 измерен и записан в issue
  • Задержка SELECT 1 через реальное соединение измерена
  • Замер повторён в пиковый и ночной час
  • Вывод: доживает ли построчная запись, или пакетность обязательна до окна
Эпик: #2989 ## Зачем Цифра 10–15 мс взята **по типовому Москва↔СПб, а не измерена**. От неё напрямую зависит объём работ по пакетной записи скрапперов (см. соответствующую задачу): при 5 мс построчная запись, возможно, доживёт до лучших времён, при 40 мс — точно нет, и пакетность становится блокером окна, а не улучшением. Это дешёвый замер, который снимает неопределённость с более дорогой задачи. Делать **до** того, как проектировать пакетную запись. ## Что померить - RTT: `mping`/`ping` и `tcptraceroute` на 5432 — не только ICMP, он может ходить другим путём и приоритетом. - **Задержку реального запроса к Postgres**, не только сетевую: `psql -c 'SELECT 1'` в цикле, p50/p95/p99. Именно это число войдёт в расчёт. - Пропускную способность: `iperf3` или хотя бы `scp` большого файла в обе стороны. - Разброс во времени — прогнать в пиковый и ночной час, джиттер важнее среднего. ## Ограничение Полноценно померить можно только **после аренды сервера**. До этого — оценить по любому арендованному в том же пуле Selectel `ru-1` (СПб / зона доступности 2), где уже лежит бакет `gendsgn-backups`: аплоад бэкапа на него с Beget уже даёт наблюдаемые 100+ МБ/с, то есть канал широкий, вопрос только в RTT. ## Приёмка - [ ] RTT p50/p95/p99 измерен и записан в issue - [ ] Задержка `SELECT 1` через реальное соединение измерена - [ ] Замер повторён в пиковый и ночной час - [ ] Вывод: доживает ли построчная запись, или пакетность обязательна до окна
lekss361 added the
chore
scope/devops
tradein
labels 2026-08-21 12:57:10 +00:00
Author
Owner

Замерено. Ночной срез, 24.08.2026 ~01:1x МСК, Beget (46.173.16.127) → Poincare (188.246.224.93).

Сеть

Метрика min p50 p95 p99 max
ICMP RTT (100 пакетов, интервал 0.2 с) 17.20 17.50 17.70 18.50 18.50 мс
TCP connect на 22/tcp (50 замеров) 21.11 23.31 25.38 25.74 25.74 мс

Потерь 0 %, джиттер (stdev) 0.181 мс, mdev по ping 0.137 мс. Канал исключительно ровный.

TCP не депроритизирован относительно ICMP — connect ≈ 1.3× RTT, что и должно быть для трёхстороннего рукопожатия.

Postgres — реальный запрос, а не только сеть

pgbench, кастомный скрипт SELECT 1, скретч-Postgres на Poincare:

Режим latency avg tps
Установленное соединение, 200 транзакций 18.115 мс 55.2
Новое соединение на каждую транзакцию (-C), 50 транзакций 75.517 мс 13.2
Установка соединения (initial connection time) 56.421 мс

18.1 мс ≈ ICMP RTT 17.5 мс + 0.6 мс. То есть один statement = ровно один сетевой round-trip, никаких сюрпризов протокола, никакого скрытого умножения. Модель «latency = RTT» подтверждена, а не предположена.

Отдельно стоит заметить цену установки соединения: 56 мс, то есть больше трёх RTT (стартовое рукопожатие Postgres многошаговое). Пул соединений через линк обязателен — без него каждая новая сессия стоит как четыре запроса.

Пропускная способность

Отдельно не мерил: прямого SSH-канала Beget↔Poincare нет ни в одну сторону (проверено — Permission denied (publickey) в обе), а поднимать его ради этого числа не стал. Оно уже наблюдалось при аплоаде бэкапов на gendsgn-backups — 100+ МБ/с, о чём сказано в самом issue. Узкое место — RTT, не полоса; это замер подтверждает.

Что означает 18 мс арифметически

Построчная запись через линк = 55 строк/с на одно соединение.

Грунтующее число с прода: суточный снапшот listing_source_snapshots пишет 103 435 строк (23.08; 102–103 тыс. стабильно четыре дня подряд, при 107 251 строке в listings). Построчно через линк это 31 минута чистого сетевого ожидания в сутки на одном соединении.

Для сравнения, где 18 мс НЕ страшны: cian_full_load идёт ~400 минут и трогает 10–15 тыс. объявлений, то есть уже сейчас тратит ~1.6 с на строку — он упирается в rate-limit площадки, а не в БД. Добавка 18 мс к такой строке — около 1 %.

Вывод по приёмке: построчная запись доживает у скрапперов (они network-bound на стороне площадки) и НЕ доживает у плотных циклов — снапшоты, бэкфиллы, миграции, любая пакетная обработка десятков тысяч строк подряд.

Но сначала стоит проверить саму посылку

Задача сформулирована как «объём работ по пакетной записи скрапперов». По целевой топологии скрапперы и оба кластера Postgres уезжают на Selectel вместе — значит записи скрапперов остаются локальными (~0.1 мс), и 18 мс их вообще не касаются.

Где 18 мс реально появятся:

  1. В окне cutover, если на каком-то шаге скрапперы уже на Selectel, а база ещё на Beget (или наоборот) — это вопрос порядка в раннбуке #3057, а не переписывания кода.
  2. У того, что остаётся на Beget, но чья БД уезжает. Это ровно #3061: база GlitchTip лежит внутри gendesign-postgres, который переезжает, а сам GlitchTip остаётся. После переезда каждая запись события Sentry пойдёт через линк — 18 мс на statement плюс 56 мс на установку соединения, если пул не настроен.

То есть этот замер не столько обосновывает пакетную запись скрапперов, сколько добавляет цифру к #3061: расщепление «сервис здесь, его база там» стоит 18 мс за statement, и это надо учитывать при выборе варианта A/B/C.

Предлагаю переформулировать зависимую задачу с «пакетная запись скрапперов» на «пакетная запись там, где цикл плотный» — по списку выше.

Приёмка

  • RTT p50/p95/p99 измерен и записан
  • Задержка SELECT 1 через реальное соединение измерена (18.115 мс), плюс цена установки соединения (56.4 мс)
  • Замер в пиковый час — не сделан. Сейчас ночь; повторить днём той же командой. Учитывая джиттер 0.18 мс и 0 % потерь ночью, ожидаю расхождение в пределах единиц процентов, но это ожидание, а не замер.
  • Вывод сформулирован (см. выше, с оговоркой про посылку)

Гигиена замера

Прямого доступа к Postgres между хостами нет и не было: ufw на Poincare 5432 не публикует. На время замера поднимал скретч-контейнер pg-latency (пустая база, POSTGRES_HOST_AUTH_METHOD=trust, то есть никаких паролей нигде) и правило ufw allow from 46.173.16.127 to any port 5432 — доступ ровно с одного IP.

Убрано сразу после замера, проверено: контейнер удалён, правило снято (ufw status | grep 5432 пусто), порт никем не слушается (ss -lntp пусто). На хосте остались только три штатных скретч-контейнера — gd-fdw-test, tradein-postgres, pg-rehearsal.

Замерено. Ночной срез, 24.08.2026 ~01:1x МСК, Beget (46.173.16.127) → Poincare (188.246.224.93). ## Сеть | Метрика | min | p50 | p95 | p99 | max | |---|---|---|---|---|---| | ICMP RTT (100 пакетов, интервал 0.2 с) | 17.20 | **17.50** | 17.70 | 18.50 | 18.50 мс | | TCP connect на 22/tcp (50 замеров) | 21.11 | 23.31 | 25.38 | 25.74 | 25.74 мс | Потерь **0 %**, джиттер (stdev) **0.181 мс**, mdev по ping 0.137 мс. Канал исключительно ровный. TCP не депроритизирован относительно ICMP — connect ≈ 1.3× RTT, что и должно быть для трёхстороннего рукопожатия. ## Postgres — реальный запрос, а не только сеть `pgbench`, кастомный скрипт `SELECT 1`, скретч-Postgres на Poincare: | Режим | latency avg | tps | |---|---|---| | Установленное соединение, 200 транзакций | **18.115 мс** | 55.2 | | Новое соединение на каждую транзакцию (`-C`), 50 транзакций | 75.517 мс | 13.2 | | Установка соединения (initial connection time) | 56.421 мс | — | **18.1 мс ≈ ICMP RTT 17.5 мс + 0.6 мс.** То есть один statement = ровно один сетевой round-trip, никаких сюрпризов протокола, никакого скрытого умножения. Модель «latency = RTT» подтверждена, а не предположена. Отдельно стоит заметить цену установки соединения: **56 мс**, то есть больше трёх RTT (стартовое рукопожатие Postgres многошаговое). Пул соединений через линк обязателен — без него каждая новая сессия стоит как четыре запроса. ## Пропускная способность Отдельно не мерил: прямого SSH-канала Beget↔Poincare нет ни в одну сторону (проверено — `Permission denied (publickey)` в обе), а поднимать его ради этого числа не стал. Оно уже наблюдалось при аплоаде бэкапов на `gendsgn-backups` — 100+ МБ/с, о чём сказано в самом issue. Узкое место — RTT, не полоса; это замер подтверждает. ## Что означает 18 мс арифметически Построчная запись через линк = **55 строк/с на одно соединение**. Грунтующее число с прода: суточный снапшот `listing_source_snapshots` пишет **103 435 строк** (23.08; 102–103 тыс. стабильно четыре дня подряд, при 107 251 строке в `listings`). Построчно через линк это **31 минута чистого сетевого ожидания в сутки** на одном соединении. Для сравнения, где 18 мс НЕ страшны: `cian_full_load` идёт ~400 минут и трогает 10–15 тыс. объявлений, то есть уже сейчас тратит ~1.6 с на строку — он упирается в rate-limit площадки, а не в БД. Добавка 18 мс к такой строке — около 1 %. Вывод по приёмке: **построчная запись доживает у скрапперов (они network-bound на стороне площадки) и НЕ доживает у плотных циклов** — снапшоты, бэкфиллы, миграции, любая пакетная обработка десятков тысяч строк подряд. ## Но сначала стоит проверить саму посылку Задача сформулирована как «объём работ по пакетной записи скрапперов». По целевой топологии **скрапперы и оба кластера Postgres уезжают на Selectel вместе** — значит записи скрапперов остаются локальными (~0.1 мс), и 18 мс их вообще не касаются. Где 18 мс реально появятся: 1. **В окне cutover**, если на каком-то шаге скрапперы уже на Selectel, а база ещё на Beget (или наоборот) — это вопрос порядка в раннбуке #3057, а не переписывания кода. 2. **У того, что остаётся на Beget, но чья БД уезжает.** Это ровно #3061: база GlitchTip лежит внутри `gendesign-postgres`, который переезжает, а сам GlitchTip остаётся. После переезда каждая запись события Sentry пойдёт через линк — 18 мс на statement плюс 56 мс на установку соединения, если пул не настроен. То есть этот замер не столько обосновывает пакетную запись скрапперов, сколько **добавляет цифру к #3061**: расщепление «сервис здесь, его база там» стоит 18 мс за statement, и это надо учитывать при выборе варианта A/B/C. Предлагаю переформулировать зависимую задачу с «пакетная запись скрапперов» на «пакетная запись там, где цикл плотный» — по списку выше. ## Приёмка - [x] RTT p50/p95/p99 измерен и записан - [x] Задержка `SELECT 1` через реальное соединение измерена (18.115 мс), плюс цена установки соединения (56.4 мс) - [ ] **Замер в пиковый час — не сделан.** Сейчас ночь; повторить днём той же командой. Учитывая джиттер 0.18 мс и 0 % потерь ночью, ожидаю расхождение в пределах единиц процентов, но это ожидание, а не замер. - [x] Вывод сформулирован (см. выше, с оговоркой про посылку) ## Гигиена замера Прямого доступа к Postgres между хостами нет и не было: `ufw` на Poincare 5432 не публикует. На время замера поднимал скретч-контейнер `pg-latency` (пустая база, `POSTGRES_HOST_AUTH_METHOD=trust`, то есть никаких паролей нигде) и правило `ufw allow from 46.173.16.127 to any port 5432` — доступ ровно с одного IP. **Убрано сразу после замера, проверено:** контейнер удалён, правило снято (`ufw status | grep 5432` пусто), порт никем не слушается (`ss -lntp` пусто). На хосте остались только три штатных скретч-контейнера — `gd-fdw-test`, `tradein-postgres`, `pg-rehearsal`.
Author
Owner

Пиковый час замерен — расхождения с ночью нет, вывод не меняется

Оставался один пункт приёмки. Прогнал теми же командами 26.08 в 18:55–19:07 МСК, вечерний час.

Сеть

Метрика ночь 24.08 ~01:1x пик 26.08 18:55 Δ
ICMP p50 17.50 17.50 0 %
ICMP p95 17.70 17.70 0 %
ICMP p99 18.50 18.00 −2.7 %
ICMP max 18.50 18.10
stdev / потери 0.181 мс / 0 % 0.204 мс / 0 %
TCP connect :22 p50 23.31 23.49 +0.8 %
TCP connect :22 p95 25.38 25.98 +2.4 %
TCP connect :22 p99 25.74 26.68 +3.7 %

Postgres — реальный запрос

Режим ночь пик Δ
Установленное соединение, 200 транзакций 18.115 мс 18.358 мс +1.3 %
Новое соединение на транзакцию (-C), 50 75.517 мс 74.810 мс −0.9 %
Установка соединения 56.421 мс 56.478 мс +0.1 %

Ночное ожидание «расхождение в пределах единиц процентов» подтвердилось замером: максимум 3.7 %, и то на p99 TCP-хендшейка. Канал ровный круглые сутки, суточного профиля у линка нет.

Соотношение «один statement = один RTT» держится и под нагрузкой: 18.358 мс при RTT 17.50 мс — те же ~0.6 мс сверху, что и ночью. Цена установки соединения воспроизвелась с точностью до 0.06 мс — пул соединений через линк по-прежнему обязателен.

Вывод остаётся прежним: построчная запись доживает там, где цикл упирается в площадку (скрапперы), и не доживает в плотных циклах — снапшоты, бэкфиллы, миграции. Плюс цифра для #3061: расщепление «сервис здесь, база там» стоит 18 мс за statement и 56 мс за соединение.

Побочная находка про периметр — пригодится для #3075

Первый заход делал как ночью: скретч в бридж-сети с публикацией порта и ufw allow from 46.173.16.127. Соединение не установилось. tcpdump на Poincare показал, что SYN с Beget доходит до хоста, а SYN-ACK не уходит — то есть режет не провайдер и не Beget, а сам хост.

Причина в том, что для опубликованного порта бридж-сети трафик идёт через FORWARD, а ufw allow пишет правило в INPUT — оно к такому трафику просто не применяется. Плюс в DOCKER-USER уже стоит явный DROP на 5432 и 6379 со стороны eth0.

Практический смысл: периметр Poincare плотнее, чем кажется по ufw status — правило ufw НЕ открывает порт, опубликованный контейнером. Это хорошая новость для #3075, но и ловушка: тот, кто будет открывать доступ по инструкции «добавь правило ufw», получит молчаливый отказ и потратит время. Замер в итоге сделан на host-сети, где ufw работает как ожидается.

Гигиена

Скретч pg-latency на host-сети, база пустая, POSTGRES_HOST_AUTH_METHOD=trust — паролей нигде. Правило ufw allow from 46.173.16.127 to any port 55432 жило только на время замера. Боевой кластер на 5432 не трогался вовсе.

Убрано и проверено после: контейнера нет, правила ufw нет, 55432 никем не слушается, образ postgres:16 (642 МБ, до замера на хосте отсутствовал) удалён. Диск: 95 из 910 ГБ.

Приёмка

  • RTT p50/p95/p99 измерен и записан
  • Задержка SELECT 1 через реальное соединение измерена
  • Замер повторён в пиковый и ночной час
  • Вывод сформулирован

Закрываю.

## Пиковый час замерен — расхождения с ночью нет, вывод не меняется Оставался один пункт приёмки. Прогнал теми же командами 26.08 в 18:55–19:07 МСК, вечерний час. ### Сеть | Метрика | ночь 24.08 ~01:1x | **пик 26.08 18:55** | Δ | |---|---|---|---| | ICMP p50 | 17.50 | **17.50** | 0 % | | ICMP p95 | 17.70 | **17.70** | 0 % | | ICMP p99 | 18.50 | **18.00** | −2.7 % | | ICMP max | 18.50 | 18.10 | — | | stdev / потери | 0.181 мс / 0 % | **0.204 мс / 0 %** | — | | TCP connect :22 p50 | 23.31 | **23.49** | +0.8 % | | TCP connect :22 p95 | 25.38 | **25.98** | +2.4 % | | TCP connect :22 p99 | 25.74 | **26.68** | +3.7 % | ### Postgres — реальный запрос | Режим | ночь | **пик** | Δ | |---|---|---|---| | Установленное соединение, 200 транзакций | 18.115 мс | **18.358 мс** | +1.3 % | | Новое соединение на транзакцию (`-C`), 50 | 75.517 мс | **74.810 мс** | −0.9 % | | Установка соединения | 56.421 мс | **56.478 мс** | +0.1 % | Ночное ожидание «расхождение в пределах единиц процентов» подтвердилось замером: максимум 3.7 %, и то на p99 TCP-хендшейка. Канал ровный круглые сутки, суточного профиля у линка нет. Соотношение «один statement = один RTT» держится и под нагрузкой: 18.358 мс при RTT 17.50 мс — те же ~0.6 мс сверху, что и ночью. Цена установки соединения воспроизвелась с точностью до 0.06 мс — пул соединений через линк по-прежнему обязателен. **Вывод остаётся прежним:** построчная запись доживает там, где цикл упирается в площадку (скрапперы), и не доживает в плотных циклах — снапшоты, бэкфиллы, миграции. Плюс цифра для #3061: расщепление «сервис здесь, база там» стоит 18 мс за statement и 56 мс за соединение. ### Побочная находка про периметр — пригодится для #3075 Первый заход делал как ночью: скретч в бридж-сети с публикацией порта и `ufw allow from 46.173.16.127`. **Соединение не установилось.** `tcpdump` на Poincare показал, что SYN с Beget доходит до хоста, а SYN-ACK не уходит — то есть режет не провайдер и не Beget, а сам хост. Причина в том, что для опубликованного порта бридж-сети трафик идёт через FORWARD, а `ufw allow` пишет правило в INPUT — оно к такому трафику просто не применяется. Плюс в `DOCKER-USER` уже стоит явный `DROP` на 5432 и 6379 со стороны `eth0`. Практический смысл: **периметр Poincare плотнее, чем кажется по `ufw status`** — правило ufw НЕ открывает порт, опубликованный контейнером. Это хорошая новость для #3075, но и ловушка: тот, кто будет открывать доступ по инструкции «добавь правило ufw», получит молчаливый отказ и потратит время. Замер в итоге сделан на host-сети, где ufw работает как ожидается. ### Гигиена Скретч `pg-latency` на host-сети, база пустая, `POSTGRES_HOST_AUTH_METHOD=trust` — паролей нигде. Правило `ufw allow from 46.173.16.127 to any port 55432` жило только на время замера. Боевой кластер на 5432 не трогался вовсе. Убрано и **проверено после**: контейнера нет, правила ufw нет, 55432 никем не слушается, образ `postgres:16` (642 МБ, до замера на хосте отсутствовал) удалён. Диск: 95 из 910 ГБ. ### Приёмка - [x] RTT p50/p95/p99 измерен и записан - [x] Задержка `SELECT 1` через реальное соединение измерена - [x] **Замер повторён в пиковый и ночной час** - [x] Вывод сформулирован Закрываю.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3032
No description provided.