Deep Engineering

ЗАМЕР

bench/stretched-cache/runs/relaycheck.txt

Скрипт, которым получены числа в статье, и запись прогона. Файл читается на сборке из репозитория — это тот самый код, который запускали, а не его копия.

Цитируется в статье
/ru/system-design/caching/stretched-cache

Запись прогона

У этого замера записи прогона нет — только скрипт.

Скрипт

159 строк
Redis 7.0.15 · один хост, два процесса
проверяется не Redis, а измерительный стенд: ретранслятор,
который во всех остальных замерах изображает канал между ДЦ

11. ЧТО СТОИТ САМ СТЕНД, ЕСЛИ ЗАДЕРЖКУ ВЫКРУТИТЬ В НОЛЬ
------------------------------------------------------------------
  без канала вовсе: обычная запись, мс        0.20
  без канала вовсе: запись с WAIT 1, мс       0.42

  канал 0 мс, БЕЗ TCP_NODELAY (стенд, на котором снят блок 10):
      обычная запись, мс                      0.24
      запись с WAIT 1, мс                     44.00

  канал 0 мс, С TCP_NODELAY (исправленный стенд):
      обычная запись, мс                      0.18
      запись с WAIT 1, мс                     0.70

  цена прежнего стенда на одной записи, мс    +43.30

  Вот и ответ на вопрос, который блок 10 оставил открытым, — и ответ
  неприятный. Там нашлось слагаемое в 42-44 мс сверх круга, одинаковое
  на всех трёх расстояниях, и было честно сказано, что замер его
  происхождения не устанавливает. Устанавливает этот: слагаемое целиком
  принадлежит ИЗМЕРИТЕЛЬНОМУ ПРИБОРУ.

  Три строки читаются подряд. Без канала вовсе `SET` + `WAIT 1` стоит
  0.42 мс. Через канал с НУЛЕВОЙ задержкой, но без
  TCP_NODELAY — 44.00 мс. Тот же канал, та же нулевая задержка,
  флаг включён — 0.70 мс. Расстояния в опыте нет ни в одной из
  трёх строк.

  Механизм известный. Redis ставит TCP_NODELAY на своих соединениях сам;
  ретранслятор создаёт ДВА НОВЫХ соединения, и на них флага не было.
  Дальше работает пара «алгоритм Нейгла на отправителе — отложенное
  подтверждение на получателе»: мелкая порция придерживается до
  подтверждения предыдущей, а подтверждение придерживается до сорока
  миллисекунд. Отсюда и ровно сорок с небольшим, и их независимость от
  расстояния, и то, что они появлялись только там, где по каналу шло
  несколько мелких сообщений подряд.

  ЧТО ЭТО ЗНАЧИТ ДЛЯ СТАТЬИ. Числа блока 10 (51,99 / 84,05 / 124,01 мс)
  измеряли Redis плюс расстояние плюс залипание в сорок миллисекунд на
  собственном стенде. Вывод «с расстоянием цена растёт на один круг, а
  не на два» устоял — он держится на разностях, а слагаемое было
  постоянным, — но сами числа и «слагаемое неизвестной природы» надо
  заменить. Ниже они пересняты.



12. СКОЛЬКО РАЗ ЗАДЕРЖКА ПРИМЕНЯЕТСЯ К ОДНОЙ ОПЕРАЦИИ
------------------------------------------------------------------
  канал сериализующий, 20 мс в одну сторону, 20 раз SET+WAIT:
      порций мастер → реплика вхолостую       0
      порций реплика → мастер вхолостую       1
      порций мастер → реплика с нагрузкой     40
      порций реплика → мастер с нагрузкой     21
      порций сверх фона, всего                60
      то же на одну операцию                  3.00
      задержка применена, мс на операцию      60.0
      измерено на операцию, мс                61.81

  канал конвейерный, 20 мс в одну сторону, 20 раз SET+WAIT:
      порций мастер → реплика вхолостую       0
      порций реплика → мастер вхолостую       1
      порций мастер → реплика с нагрузкой     39
      порций реплика → мастер с нагрузкой     21
      порций сверх фона, всего                59
      то же на одну операцию                  2.95
      задержка применена, мс на операцию      59.0
      измерено на операцию, мс                41.67


  Вот в чём была претензия разбора, выраженная числом. Задержка ставится
  не на операцию, а на ПОРЦИЮ, и порций на одну операцию три: сама
  запись, запрос подтверждения и подтверждение. Число это не
  гарантировано: TCP границ сообщений не хранит, и соседние сообщения
  могут слиться в одну порцию или разойтись по двум.

  Три порции на операцию — но на критическом пути их две, а не три:
  запись и запрос подтверждения идут в одну сторону и уходят вместе,
  подтверждение возвращается. Отсюда и круг у конвейерного канала.

  Разница между каналами ровно в том, складываются ли задержки. У
  сериализующего поток останавливается на время каждого полёта, поэтому
  запись и запрос подтверждения, ушедшие бы вместе, идут по очереди — и
  операция стоит на один перелёт дороже. Насколько именно, печатает
  следующий блок.



13. СЕРИАЛИЗУЮЩИЙ КАНАЛ ПРОТИВ КОНВЕЙЕРНОГО НА ТРЁХ РАССТОЯНИЯХ
------------------------------------------------------------------
  в одну сторону    круг   сериализующий   конвейерный   разница
           5 мс      10 мс         16.06 мс      11.06 мс    +5.00
          20 мс      40 мс         61.64 мс      41.48 мс   +20.16
          40 мс      80 мс        121.86 мс      81.36 мс   +40.51

  сверх круга (цена минус круг):
      при    5 мс в одну сторону              сериализующий    6.06 · конвейерный    1.06
      при   20 мс в одну сторону              сериализующий   21.64 · конвейерный    1.48
      при   40 мс в одну сторону              сериализующий   41.86 · конвейерный    1.36

  Это и есть цена ошибки, на которую указал разбор, — в миллисекундах, а
  не в рассуждении. Столбец разницы и есть ответ: сериализующий канал
  стоит ровно на ОДИН перелёт в одну сторону дороже на каждом
  расстоянии. Не на случайную величину и не на долю процента — на ту
  самую задержку, которую он применяет к порции, потому что ровно одна
  порция из трёх из-за него идёт по очереди вместо того, чтобы уйти
  вместе с соседней.

  Пока залипание в сорок миллисекунд из блока 11 сидело в обоих
  каналах, этой разницы не было видно вовсе: она тонула в артефакте,
  который был вчетверо больше её самой. Два дефекта стенда маскировали
  друг друга — и это, пожалуй, главный урок всего разбора.

  Числа надо публиковать по конвейерному каналу: у него цена `WAIT` —
  это круг плюс около полутора миллисекунд, то есть ровно то, чем она и
  должна быть по устройству команды.

  Чего этот блок НЕ доказывает: что конвейерный ретранслятор равен
  `netem`. Задержка здесь по-прежнему применяется к порциям потока, а не
  к пакетам; нет ни очередей, ни потерь, ни джиттера, ни ограничения
  полосы. На нагрузке, где порций много, расхождение с настоящей сетью
  вырастет, и проверить его в этой среде нечем.



14. КАЛИБРОВКА: КАКУЮ ЗАДЕРЖКУ КАНАЛ ДАЁТ НА «ЗАПРОС — ОТВЕТ»
------------------------------------------------------------------
  GET напрямую, мс                            0.10

  через канал, GET мимо ретранслятора к тому же мастеру:
  в одну сторону    круг   сериализующий   конвейерный
           5 мс      10 мс         10.81 мс      10.79 мс
          20 мс      40 мс         40.98 мс      41.00 мс
          40 мс      80 мс         81.10 мс      81.11 мс

  На простом «запрос — ответ» оба канала дают одно и то же, и дают ровно
  круг плюс около миллисекунды: одна порция туда, одна обратно,
  складывать нечего. Это граница применимости стенда, названная замером,
  а не оговоркой.

  Читать её так. Там, где за операцию по каналу проходит одна порция в
  каждую сторону, ретранслятор — хорошее приближение расстояния, и числа
  блоков 7 и 1 можно брать как есть. Там, где по каналу идёт несколько
  сообщений подряд — репликация, конвейер команд, `WAIT`, — прибор
  начинает добавлять своё, и добавлял: сорок миллисекунд залипания и
  лишний перелёт на сериализации. Оба слагаемых теперь измерены и
  убраны, но третьего рода погрешность — отсутствие очередей, потерь,
  джиттера и ограничения полосы — остаётся, и снять её в этой среде
  нечем.

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