company_banner

Как мы раскрыли 24-летний баг в ядре Linux

Автор оригинала: APOLLON OIKONOMOPOULOS
  • Перевод

Захватывающий детективный рассказ о поиске и исправлении трудноуловимого и при этом весьма странного бага, который неведомым образом ускользал от обнаружения на протяжении долгих лет взросления системы Linux. В начале описывается предыстория и технологическая база компании, в которой этот баг постигло разоблачение, после чего автор подробно демонстрирует все предпринятые для его раскрытия шаги.

Здесь в Skroutz в составе нашего стандартного набора инструментов мы обеспечиваем каждого разработчика снимками базы данных с возможностью перезаписи, которые он использует в разработке. Обновление при этом происходит ежедневно в виде конвейера, который включает формирование LVM-снимков данных производственной среды, анонимизацию этого датасета путем удаления всей личной информации и его последующую передачу через rsync на серверы базы данных разработки. Серверы, в свою очередь, предоставляют ZFS-снимок каждому разработчику вместе с инструментами, позволяющими выполнять переход на новые снимки или делать откат к старым.

Для предоставления данных MariaDB и MongoDB с общими размерами датасетов в 600ГБ и 200ГБ соответственно, мы используем такой же конвейер, а для Elasticsearch несколько иной его вариант. Несмотря на то, что данные на дисках существенно изменяются для всех источников, rsync все равно экономит немало времени, каждую ночь передавая приблизительно 1/3 всего датасета.

Такая настройка работала отлично более пяти лет и при этом была масштабирована с 15 до 150 разработчиков. Тем не менее, как и большинство крупных систем, требовала она недюжинного обслуживания, в ходе которого возникали весьма интересные моменты.

К таким моментам можно отнести проблему, которая в итоге привела к раскрытию поистине старого бага ядра Linux, связанного, в частности, с реализацией TCP. Дело в том, что передача rsync от исходного сервера по неочевидным причинам начала постоянно зависать, хотя в остальном все работало в штатном режиме. Более того, как в итоге выяснилось, эту проблему невозможно было воссоздать намеренно, хотя некоторые действия (например, добавление ограничения скорости на уровне rsync) снижали частоту ее появления, диапазон которой составлял от 1-2 раз в неделю до 1 раза в три месяца.

Поначалу мы не придали этой проблеме должного внимания из-за наличия более актуальных и важных задач, пометив ее как «состояние гонки в rsync», которым как-нибудь нужно заняться, и использовали уже описанный обходной путь с ограничением скорости передачи.

Но в итоге она начала возникать буквально ежедневно.

rsync как конвейер


Хоть это и не обязательно, но знание внутреннего принципа работы rsync поможет вам понять последующий анализ. На сайте приводится подробное описание внутренней архитектуры этой программы, так что я просто обобщу наиболее важные пункты:

  1. rsync запускается как один процесс на клиенте и один процесс на сервере, взаимодействующие через пару сокетов. При использовании же демона rsync, как в нашем случае, коммуникация выполняется через простое TCP-соединение.
  2. В зависимости от направления синхронизации после завершения начального рукопожатия каждая конечная точка получает роль отправителя либо получателя. В нашем случае клиент выступает как получатель, а сервер как отправитель.
  3. Получатель создает ветку дополнительного процесса, называемого генератор, который использует тот же сокет, что и процесс получателя. Генератор определяет, что ему нужно запросить у отправителя, после чего отправитель посылает эти данные получателю. В итоге после этого шага мы, по сути, получаем конвейер: генератор -> отправитель -> получатель, где стрелки отражают два направления одного TCP-соединения. Несмотря на то, что в процессе присутствует система уведомлений, конвейер оперирует с использованием блокировки и для контроля обратного потока опирается на буферы ОС и окна получения TCP.

«Призрак» в сети?


Когда мы только столкнулись с проблемой, то первым делом предположили наличие в сети ошибок, что было вполне логичным, так как незадолго до этого был произведен апгрейд серверов и свитчей. Исключив всех стандартных подозреваемых (баги прошивки NIC, связанные с разгрузкой TSO/GSO/GRO/VLAN, чрезмерным отбрасыванием пакетов или CRC-ошибками свитчей и т.д.), мы пришли к заключению, что здесь все в порядке, и проблема кроется в чем-то другом.

Присоединение зависших процессов при помощи strace и gdb оказалось не особо информативным: генератор подвисал на send(), а отправитель и получатель на recv(), но при этом никакие данные не перемещались. Тем не менее при рассмотрении ядра обоих систем мы обнаружили кое-что интересное. Клиентский сокет rsync, совместно используемый процессами генератора и получателя, находился в следующем состоянии:

$ ss -mito dst :873
State      Recv-Q Send-Q                  Local Address:Port                                 Peer Address:Port
ESTAB      0      392827 ❶             2001:db8:2a::3:38022                             2001:db8:2a::18:rsync                 timer:(persist,1min56sec,0)
	 skmem:(r0,rb4194304,t0,tb530944,f3733,w401771,o0,bl0,d757) ts sack cubic wscale:7,7 rto:204 backoff:15 rtt:2.06/0.541 ato:40 mss:1428 cwnd:10 ssthresh:46 bytes_acked:22924107 bytes_received:100439119971 segs_out:7191833 segs_in:70503044 data_segs_out:16161 data_segs_in:70502223 send 55.5Mbps lastsnd:16281856 lastrcv:14261988 lastack:3164 pacing_rate 133.1Mbps retrans:0/11 rcv_rtt:20 rcv_space:2107888 notsent:392827 minrtt:0.189

На сервере же состояние сокета было таким:

$ ss -mito src :873
State      Recv-Q Send-Q                Local Address:Port                                 Peer Address:Port
ESTAB      0      0                   2001:db8:2a::18:rsync                              2001:db8:2a::3:38022                 timer:(keepalive,3min7sec,0)
 	 skmem:(r0,rb3540548,t0,tb4194304,f0,w0,o0,bl0,d292) ts sack cubic wscale:7,7 rto:204 rtt:1.234/1.809 ato:40 mss:1428 cwnd:1453 ssthresh:1431 bytes_acked:100439119971 bytes_received:22924106 segs_out:70503089 segs_in:7191833 data_segs_out:70502269 data_segs_in:16161 send 13451.4Mbps lastsnd:14277708 lastrcv:16297572 lastack:7012576 pacing_rate 16140.1Mbps retrans:0/794 rcv_rtt:7.5 rcv_space:589824 minrtt:0.026

Интересно то, что на клиенте генератор запрашивает с сервера отправку 3.5Мб данных (1 в первом выводе). Однако в то время как Recv-Q сервера пуст, и он способен принимать данные, ничего не передается. Если бы Recv-Q во втором выводе не равнялся нулю, то мы бы наблюдали зависание rsync на сервере и его невозможность считывать из сети данные. Тем не менее здесь мы видим, что rsync получил все входящие данные, и его вины тут нет.

Так почему же данные на одном конце соединения выстраиваются в очередь, когда второй конец вполне может их принять? Ответ незатейливо кроется в полях timer обоих выводов ss, в частности в timer:(persist,1min56sec,0). Цитируем ss(8):

-o, --options
              Show timer information. For TCP protocol, the output format is:

              timer:(<timer_name>,<expire_time>,<retrans>)

              <timer_name>
                     the name of the timer, there are five kind of timer names:

                     on : means one of these  timers:  TCP  retrans  timer,  TCP
                     early retrans timer and tail loss probe timer

                     keepalive: tcp keep alive timer

                     timewait: timewait stage timer

                     persist: zero window probe timer

                     unknown: none of the above timers

persist означает, что соединение получило уведомление о нулевом окне и ожидает от пира уведомления о ненулевом.

Нулевые окна TCP и зонды нулевых окон


В TCP управление потоком реализуется путем ограничения передаваемых данных с помощью скользящего окна, называемого окном получения. Если коротко, то каждая сторона TCP-соединения объявляет, сколько данных она может буферизовать при передаче, то есть сколько данных другая сторона может отправить, прежде чем перейти к ожиданию подтверждения их получения.

Когда буфер получения (Recv-Q) одной стороны заполняется (в данном случае, потому что процесс rsync выполняет на диске операции ввода/вывода со скоростью меньшей, чем скорость сети), эта сторона отправляет сигнал нулевого окна, который приостанавливает передачу по данному направлению. Когда в конечном итоге буфер освобождается, ядро отправляет незапрашиваемое уведомление с ненулевым окном, и передача данных возобновляется. Для верности на случай потери этого уведомления встречная сторона регулярно опрашивает состояние соединения, используя так называемые зонды нулевого окна (Zero Window Probes), режим persist, который мы здесь и наблюдаем.

Окно «заело» в закрытом состоянии


Настало время копнуть поглубже и использовать tcpdump для оценки происходящего на уровне сети:

 […]
09:34:34.165148 0c:c4:7a:f9:68:e4 > 0c:c4:7a:f9:69:78, ethertype IPv6 (0x86dd), length 86: (flowlabel 0xcbf6f, hlim 64, next-header TCP (6) payload length: 32) 2001:db8:2a::3.38022 > 2001:db8:2a::18.873: Flags [.], cksum 0x711b (incorrect -> 0x4d39), seq 4212361595, ack 1253278418, win 16384, options [nop,nop,TS val 2864739840 ecr 2885730760], length 0
09:34:34.165354 0c:c4:7a:f9:69:78 > 0c:c4:7a:f9:68:e4, ethertype IPv6 (0x86dd), length 86: (flowlabel 0x25712, hlim 64, next-header TCP (6) payload length: 32) 2001:db8:2a::18.873 > 2001:db8:2a::3.38022: Flags [.], cksum 0x1914 (correct), seq 1253278418, ack 4212361596, win 13831, options [nop,nop,TS val 2885760967 ecr 2863021624], length 0
[… repeats every 2 mins]

Первый пакет – это клиентский зонд нулевого окна, второй пакет – это ответ сервера. Удивительно, что сервер объявляет ненулевое окно с размером 13831 байт1, которое клиент, по всей видимости, игнорирует.

¹ В действительности умноженное на 128, так как фактор масштабирования окна равен 7.

Мы, наконец, сдвинулись с мертвой точки, и теперь есть над чем работать. В какой-то момент клиент столкнулся с объявлением сервера о нулевом окне, как частью стандартного управления потоком TCP, но затем по непонятной причине окно не открылось повторно. Клиент по-прежнему игнорирует объявляемые сервером обновления окна, поэтому передача стоит.

Обработка TCP-ввода в Linux


На данный момент очевидно, что TCP-соединение находится в странном состоянии на клиенте rsync. Поскольку управление TCP-потоком происходит на уровне ядра, для обнаружения корня проблемы нужно посмотреть, как ядро обрабатывает входящие TCP-подтверждения и постараться выяснить, почему оно игнорирует входящие объявления о состоянии окна.

Обработка TCP-пакетов происходит в net/ipv4/tcp_input.c. Несмотря на то, что в пути прописан компонент ipv4, этот код по большей части общий для IPv4 и IPv6.

После некоторых поисков мы выяснили, что входящие обновления окна обрабатываются в tcp_ack_update_window, а фактическое обновление контролируется следующей функцией:

/* Проверка доступности обновления окна.
 * Функция предполагает, что snd_una<=ack<=snd_next.
 */
static inline bool tcp_may_update_window(const struct tcp_sock *tp,
					const u32 ack, const u32 ack_seq,
					const u32 nwin)
{
	return	after(ack, tp->snd_una) || ❶
		after(ack_seq, tp->snd_wl1) || ❷
		(ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd); ❸
}

Переменные ack, ack_seq, snd_wl1 и snd_una содержат порядковые номера TCP для отслеживания переданных данных. Эти номера являются 32-битными беззнаковыми целыми числами (u32) и увеличиваются на 1 при каждом переданном байте, начиная с произвольного стартового значения (начальный порядковый номер). Вот их сводка:

  • ack_seq – номер входящего сегмента.
  • Ack – номер подтверждения, содержащийся во входящем сегменте. Он подтверждает порядковый номер последнего сегмента, полученного от нас пиром.
  • snd_wl1 – номер входящего сегмента, который последним обновил окно получения пира.
  • snd_una – номер первого не подтвержденного сегмента, то есть отправленного, но еще не подтвержденного пиром.

Будучи целыми числами фиксированного размера, порядковые номера в конечном итоге будут циклически повторяться, поэтому в условиях такого повторения сравнение двух номеров поручается макросу after().

В качестве справки заметим, что имена snd_una и snd_wl1 взяты непосредственно из первой спецификации TCP в RFC 793(англ.) 1981 года!
Выражая довольно запутанную проверку простым языком, мы хотим получать обновление окна от пира, если он:

❶ Подтверждает получение отправленных нами данных.

❷ Отправляет новые данные после предыдущего обновления окна.

❸ Не отправляет новые данные после обновления окна, но объявляет окно большего размера.

Обратите внимание, что сравнение ack_seq и snd_wl1 гарантирует, что окно не будет обновлено случайно (повторно передаваемым) сегментом, который уже был просмотрен.

В нашем случае окно должно повторно открываться по меньшей мере согласно условию ❸, но этого не происходит. Для выяснения причины нам нужно обратиться к указанным переменным. К сожалению, они являются частью внутреннего состояния ядра и не раскрываются непосредственно в пространстве пользователя. Здесь потребуется смекалка.

Доступ к внутреннему состоянию ядра


Для получения доступа к состоянию ядра нужно каким-то образом выполнить внутри него код. Одним из способов было пропатчить ядро несколькими вызовами printk() в нужных местах, но это потребовало бы перезагрузки машины и ожидания очередного зависания rsync. Вместо этого мы решили пропатчить ядро в реальном времени при помощи systemtap(англ.), использовав следующий скрипт:

probe kernel.statement("tcp_ack@./net/ipv4/tcp_input.c:3751")
{
  if ($sk->sk_send_head != NULL) {
	  ack_seq = @cast(&$skb->cb[0], "tcp_skb_cb", "kernel<net/tcp.h>")->seq
	  printf("ack: %d, ack_seq: %d, prior_snd_una: %d\n", $ack, ack_seq, $prior_snd_una)
	  seq = @cast(&$sk->sk_send_head->cb[0], "tcp_skb_cb", "kernel<net/tcp.h>")->seq
	  end_seq = @cast(&$sk->sk_send_head->cb[0], "tcp_skb_cb", "kernel<net/tcp.h>")->end_seq
	  printf("sk_send_head seq:%d, end_seq: %d\n", seq, end_seq)

	  snd_wnd = @cast($sk, "tcp_sock", "kernel<linux/tcp.h>")->snd_wnd
	  snd_wl1 = @cast($sk, "tcp_sock", "kernel<linux/tcp.h>")->snd_wl1
	  ts_recent = @cast($sk, "tcp_sock", "kernel<linux/tcp.h>")->rx_opt->ts_recent
	  rcv_tsval = @cast($sk, "tcp_sock", "kernel<linux/tcp.h>")->rx_opt->rcv_tsval
	  printf("snd_wnd: %d, tcp_wnd_end: %d, snd_wl1: %d\n", snd_wnd, $prior_snd_una + snd_wnd, snd_wl1)
	  printf("flag: %x, may update window: %d\n", $flag, $flag & 0x02)
	  printf("rcv_tsval: %d, ts_recent: %d\n", rcv_tsval, ts_recent)
	  print("\n")
     }
}

Systemtap работает путем преобразования своих скриптов в Си и построения модуля ядра, который патчит его «нагорячую» и переопределяет конкретные инструкции. Здесь мы переопределили системный вызов tcp_ack(), дописали свой код в его конец и сбросили внутреннее состояние TCP-соединения. Проверка $sk->sk_send_head != NULL предоставляет быстрый способ сопоставлять только соединения с ненулевым Send-Q.

Загрузка полученного модуля в ядро привела к следующему:

ack: 4212361596, ack_seq: 1253278418, prior_snd_una: 4212361596
sk_send_head seq:4212361596, end_seq: 4212425472
snd_wnd: 0, tcp_wnd_end: 4212361596, snd_wl1: 1708927328
flag: 4100, may update window: 0
rcv_tsval: 2950255047, ts_recent: 2950255047

Здесь нас интересует два момента: snd_wl1: 1708927328 и ack_seq: 1253278418. Дело не просто в том, что, как и ожидалось, они отличаются, а в том, что ack_seq меньше, чем snd_wl1, то есть отсчет значений ack_seq в определенный момент возобновился с начала, а значение snd_wl1 какое-то время не обновлялось. Воспользовавшись правилами арифметики последовательных чисел(англ.), можно выяснить, что эта конечная точка с момента последнего обновления snd_wl1 получила не менее 3.8ГБ.

Мы уже видели, что поле snd_wl1 содержит последний порядковый номер, использованный для обновления окна получения пира (а значит и нашего окна отправки) и в конечном итоге должно гарантировать защиту от обновления окон на основе старых сегментов. Все должно быть в порядке, если snd_wl1 какое-то время не обновится, но при этом отставание от ack_seq не должно становиться слишком большим, иначе возникает риск отклонения допустимых обновлений окна, как в нашем случае. Поэтому все выглядит так, будто ядро Linux по каким-то причинам не может обновить snd_wl1, что ведет к невозможности выхода из состояния нулевого окна.

После получения весомых доказательств неполадок в ядре пришло время сообщить об этом наверх.

Обращение к мейнтейнерам


Хорошенько переварив информацию, мы составили обращение к мейнтейнерам ядра Linux , в котором указали все, что удалось на тот момент выяснить, вместе со своими домыслами по этому поводу. Меньше, чем через два часа мы получили ответ вместе с патчем от Нила Кардуэлла.

Оказалось, что баг находился в быстром пути получателя объемных данных, то есть пути кода, который пропускает большую часть строгой дорогостоящей TCP-обработки с целью оптимизации производительности в типичных случаях получения большого объема данных. Это серьезная оптимизация, подчеркнутая еще 28 лет назад2 Ваном Якобсоном в его письме “TCP receive in 30 instructions” (TCP-приемник в 30 инструкциях кода). Очевидно, что реализация Linux, находясь в быстром пути получателя, не обновляла snd_wl1. Когда соединение использует быстрый путь слишком долго, snd_wl1 настолько отстает, что ack_seq опережает его «на круг». Если же это происходит при нулевом окне получения, пропадает возможность повторного открытия этого окна, как и было показано выше. Более того, эта ошибка присутствовала в Linux, начиная с версии 2.1.8, которая датируется аж 1996 годом!

² Данная оптимизация актуальна и по сей день: относительно недавняя попытка упростить код, удалив из него прогнозирование заголовков и связанные с ними быстрые пути, потерпела неудачу по причине снижения производительности.

Получив патч, мы его сразу установили, затем пересобрали и развернули на подверженных проблеме машинах ядро, после чего стали ждать в надежде, что все пойдет как положено. Спустя пару дней, мы уже были уверены в корректности патча и отсутствии побочных эффектов.

После недолгого обсуждения итоговый коммит был размещен в linux-net, откуда перетек в основную линию Linux для 5.10-rc1. В конечном итоге он был внедрен в стабильные серии ядер 4.9 и 4.19, которые мы используем на наших системах Debian с версиями 4.9.241 и 4.19.153, соответственно.

Послесловие


Несмотря на то, что баг был исправлен, нас по-прежнему интересовала пара вопросов, а именно:

  1. Как возможно, что TCP-ошибка, которая ведет к зависанию соединений, оставалась незамеченной на протяжении 24 лет?
  2. Как получилось, что, работая с инфраструктурой из более 600 систем, выполняющих всевозможные виды ПО, мы заметили этот баг только при использовании rsync?

На эти вопросы сложно ответить определенно, но можно порассуждать:

  1. Эта ошибка не будет вызываться большинством протоколов L7. В «синхронных» протоколах запрос-ответ, например в HTTP, обычно каждая сторона будет потреблять все доступные данные перед отправкой. В таком случае, даже если snd_wl1 сделает круг, получатель объемных данных сохранит ненулевое окно и сможет отправить данные, в следствии чего очередное подтверждение обновит это окно и подстроит snd_wl1 через проверку ❶ в tcp_may_update_window. С другой стороны, rsync использует агрессивный конвейер, в котором сервер может отправлять многогигабайтные ответы, не получая в ходе этого процесса входящие данные. И даже использование rsync через SSH (достаточно распространенная комбинация), а не через простой TCP-транспорт, не выявляло эту неполадку, поскольку фрейминг/оповещение в SSH не позволял данным выстраиваться на сервере в очередь подобным образом.
  2. Независимо от протокола приложения, получатель должен сохранять нулевое окно в последнем пути достаточно долго (хотя бы на протяжении 2ГБ) для циклического обращения, но не настолько долго, чтобы ack_seq успел снова обогнать snd_wl1. Такой вариант возможен только если не будут утрачены пакеты, и не возникнут другие условия, способные вызвать провал прогнозирования заголовка быстрого пути. На практике же такое очень маловероятно, поскольку TCP сам определяет пропускную способность сети, фактически вызывая утрату пакетов.
  3. Большинство приложений будут обрабатывать сетевые таймауты и либо давать сбой, либо переподключаться, что будет похоже на «случайный глюк сети», не оставляющий следов для отладки.

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

Если эта тема вам интересна, и вы относитесь к любителям поохотиться на странные баги, а также поразбираться в коде ядра, напишите нам – мы постоянно ищем талантливых SRE и DevOps-инженеров!

RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR

Комментарии 38

    +11
    Обращение в тех поддержку

    мы составили обращение к службе сетевого обслуживания Linux,

    Taking things upstream

    we wrote a good summary of what we knew so far and what we supposed was happening, and reached out to the Linux networking maintainers

    Список рассылки netdev — это не техподдержка.

      0
      Как правильнее в таком случае эту службу назвать?
        0

        Я бы перевёл это так:


        Обращение к разработчикам

        мы подытожили всё, что успели узнать, и предполагаемые причины происходящего, и обратились к сопровождающим сетевого стека Linux
          +7
          «отправили сообщение в список рассылки», например

          «служба» подразумевает, что кто-то там что-то делает по долгу службы. Там таких нету, мейнтейнеры поддерживают подсистему в рабочем состоянии, но это не техподдержка
            –4
            Служба подразумевает работу по обслуживанию и поддержке в том числе. По долгу службы — это очень узкая трактовка более обширного значения термина. «Персонал, обслуживающий сетевые коммуникации», например. Это то же самое, что maintain (поддерживать, обслуживать). Я не хотел писать «тех. поддержка» изначально, просто не знал, как лучше выразить и взял сей термин в качестве общего понятия, ибо речь все о том же техническом обслуживании, причем в данном случае «networking maintainers» выступили в качестве именно технической поддержки, приняв обращение от пользователей системы и исправив недочет.
              +12
              вам никто не мешает рассматривать maintainers как «персонал, обслуживающий...». но это немного не так.
              Кто-то в своё свободное время прочёл ваш отчёт об ошибке, сказал «ох ты ж блин», и исправил. Это не обслуживание, не надо так.
                –3
                Слово мейнтейнер — это не русское слово, и происходит оно от слова maintain — поддерживать, обслуживать, сопровождать и т.п. В данном контексте значение оно имеет именно такое. Пусть это не персонал в полном смысле слова, и теперь я могу понять почему, но это люди, которые именно обслуживают ядро, а не мейнтейнят:)))
                  +3

                  Ребят, ну что вы накинулись на человека, он не знает особенности разработки ядра. Пояснили, и хорошо.

            +3

            Это не служба. Более правильное название — список рассылки. Что они сделали — связались с мейнтейнерами ядра.


            сопровождающему сетевой стек персоналу

            Опять мимо. Мейнтейнеры — не персонал. Мейнтейнеры могут быть персоналом каких-либо огранизаций, но в ядре они просто сопровождающие/мейнтейнеры.

              0
              Я не знал тонкостей работы мейнтейнеров, и список рассылки для меня совершенно не был очевиден. Теперь звучит понятно, а то обратились к списку рассылки — это муть какая-то.
              Обратились к мейнтейнерам, а о списке рассылки речь не идет, даже если и посредством него было сделано обращение.

          0

          А как исправили-то?

          0
          Как возможно, что TCP-ошибка, которая ведет к зависанию соединений, оставалась незамеченной на протяжении 24 лет?

          Я дал ответ на этот вопрос, в своей статье: habr.com/ru/sandbox/149580
          (за которую даже инвайт на хабре не получил)
            +2
            И если, ОС не определяла правильную последовательность пакетов. То до приложения пользовательского уровня. Дойдёт такая строка: «World !Hello »

            Я сколько вижу такие опечатки — никак понять не могу, как можно умудриться их делать. Я даже с клавиатуры на смартфоне не могу случайно предложения рубить точками. И это не раз и не два, постоянно вижу эти точки посреди предложений. Как?

              0
              Скорее всего, (когда писал в текстовом редакторе) сделал форматирование и проставил, где следует точки и запятые. А потом, когда статью публиковал использовал ctrl+c / ctrl+v.
              Форматирование сбилось. И я быстро, внёс исправления, какие увидел.
              Может и заметил опечатки и ужас форматирования, после публикации. Но исправлять ничего не стал. т.к. После очередного исправления текста, (мне как новому участнику) приходится ждать дней этак 5, чтоб статью одобрил модератор.
              Форматирование и опечатки, яб в норму привёл. Да, нужно было статью опубликовать. Ведь смысл статьи, не в её внешнем виде, а в том, какой смысл я старался донести.

              И да) Мне тоже режет глаз «такие опечатки». Но, увы и ах. После каждого внесённого исправления статьи, ждать несколько дней проверки модератором, я не стал.
                +5
                яб в норму привёл. Да, нужно было статью опубликовать. Ведь смысл статьи, не в её внешнем виде, а в том, какой смысл я старался донести.

                Понимаете, когда так криво всё написано — и глаз спотыкается, и читать неудобно, и смысл ускользает. Где-то есть критическое число ошибок, после которого годная статья превращается в статью, которую неприятно читать (по содержанию, по-моему, у вас вполне годная статья, только почти в каждом предложении языковые ошибки). Так что в том, что вам не дали инвайт, можете винить, в частности, и ошибки, и опечатки.

                  +3
                  Вообще, если на то пошло, у Вас с запятыми всё ОЧЕНЬ плохо. Например: «И я быстро, внёс исправления, какие увидел.» Пришлось несколько раз перечитывать, чтобы понять, что первой запятой там вообще не должно быть. Или вот это: «Форматирование и опечатки, яб в норму привёл.» Выглядит как обращение к неким «форматированию и опечаткам», потому что ещё одна лишняя запятая. Не говоря уже о каких-то «ябах», которкоторых привели в норму. Или первое предложение в Вашем сообщении: зачем ставить запятую перед скобками? И вообще, зачем обособлять скобками часть предложения, которая по правилам русского языка обособляется запятыми?

                  Такие тексты и на самом деле очень сложно читать. На простое их прочтение тратится в разы больше времени, чем должно, а иногда даже не хватает сил, чтобы дочитать. Потому что прочтение каждого предложения превращается в угадывание, что же на самом деле хотел сказать автор. И если один комментарии можно просто пропустить, а другой прочитать, если количество ошибок будет меньше критического, то у статьи есть все шансы «пролететь» целиком. Вы хотели что-то сказать, но форма высказывания привела к его, высказывания, пропуску и Вы остались неуслышанным.

                  Да, ждать пять дней. Но, мне кажется, это должно быть стимулом для проверки до публикации. Возможно, с привлечением помощи, раз не получается сделать всё самостоятельно.
                  0

                  Два пробела подряд превращаются в точку на большинстве смартфонов.

                    0
                    В статьях? Вообще легко. Пишешь в ворде, недожал шифт — точка вместо запятой, ворд сам поднял регистр после точки, а автор смотрит на клавиши.
                  +4
                  Перевод крайне корявый. Из-за этого крайне сложно понять текст даже тем, кто по должен бы его понимать в соответствии с опытом работы.

                  У меня стойкое ощущение, что следующие фразы означали совсем иное у автора до перевода:
                  — «сбой прогнозирования заголовка быстрого пути»
                  — «попытка упростить код, удалив из него прогнозирование заголовков и связанные с ним пути файлов»
                    0
                    Давайте от стойких ощущений к более явным фактам. Если знаете, как правильно эти фразы должны быть переведены — поделитесь. Исправлю, не вопрос. Я не разработчик ядра Linux, могу в этой области что-то не знать. Лично мне эти моменты были прекрасно понятны на технологическом уровне, а вот в формулировках мог и ошибиться.
                      +2
                      Данная оптимизация актуальна и по сей день: относительно недавняя попытка упростить код, удалив из него прогнозирование заголовков и связанные с ним пути файлов, потерпела неудачу по причине снижения производительности.

                      Было


                      ² This optimization is still relevant today: a relatively recent attempt to remove the header prediction code and associated fast paths to simplify the code was reverted on performance regression grounds.

                      Извините, но раз взялись, то не говорите, что не сдюжили
                      И ещё — в оригинале не «пути файлов», а вполне “fast path”, что означает механизм упрощенной обработки пакета для более быстрой обработки
                      Прогнозирование заголовков — это тоже пять. Так, извините, не переводят. Хотя бы «предсказание» лучшее слово, а может и вовсе построение фразы надо переделать


                      — «сбой прогнозирования заголовка быстрого пути»

                      Тоже с коллегой соглашусь, что это не сбой предсказания, а скорее «неудачное предсказание» (по аналогии с бранч предикшн в процессорах)

                        +3

                        У меня вопрос к знатокам. Что вообще имеется в виду в оригинале под "header prediction"?
                        У меня ощущение после гугления, что это про упрощённый анализ заголовков пакета для принятия решения по какому пути его пустить: slow vs fast.


                        И prediction здесь состоит в том, чтобы минимальными проверками угадать и значение прочих заголовков пакета.


                        Верно?


                        Если я верно понял суть, то возможно и переводить надо без слова предсказание/прогнозирование вообще. Если это не устоявшийся термин конечно.


                        Получится:
                        "header prediction code" -> "упрощённая классификация пакетов" (на быстрый путь/медленный путь)
                        "failed fast path’s header prediction" -> "ошибочно послали пакет по быстрому пути" (в контексте) или "ошибочная маршрутизация пакета по быстрому пути"...

                        +2

                        Извините за тон.


                        Во-первых я ни разу не переводчик. Возможно я придираюсь местами.


                        Давайте первый переведённый абзац разберём.


                        1. we provide each developer with a writable database snapshot
                          мы обеспечиваем каждого разработчика перезаписываемыми снимками базы данных


                          Если без контекста, то перевод слова writable (перезаписываемый) верный.
                          Но в английском была однозначность: database snapshot with write access.
                          А в русском создается второе значение, где сам снимок БД перезаписывают для каждого разработчика в процессе обеспечения его этими снимками.
                          И мне при первом прочтении увиделось вот это искаженное значение.


                        2. These snapshots are updated daily through a pipeline that involves… step1… step2… step3
                          Обновление при этом происходит ежедневно в виде конвейера, который включает… step1… step2… step3


                          Как-то не по-русски этот "конвейер" тут. Хотя дословно слово верно переведено.
                          Возможно потому что часто в русском оставляют кальку "пайплайн". А может было бы ещё лучше превратить во что-нибудь вроде "последовательность шагов". Или вообще переформулировать без перевода этого слова. "Обновление состоит из этапов/шагов".


                        3. copy-on-write при переводе исчезло. Не уверен как правильно и понятно перевести.


                        4. немного непоследовательно описание данных пайплайна: "данные среды", потом "датасет" передающийся "на серверы базы данных".



                        .....


                        ===================================


                        Давайтея попробую сам эту лямку потянуть.


                        Оригинал:


                        As part of our standard toolkit, we provide each developer at Skroutz with a writable database snapshot against which she can develop. These snapshots are updated daily through a pipeline that involves taking an LVM snapshot of production data, anonymizing the dataset by stripping all personal data, and transferring it via rsync to the development database servers. The development servers in turn use ZFS snapshots to expose a copy-on-write snapshot to each developer, with self-service tools allowing rollback or upgrades to newer snapshots.

                        Ваш перевод:


                        Здесь в Skroutz в составе нашего стандартного набора инструментов мы обеспечиваем каждого разработчика перезаписываемыми снимками базы данных, которые он использует в разработке. Обновление при этом происходит ежедневно в виде конвейера, который включает формирование LVM-снимков данных производственной среды, анонимизацию этого датасета путем удаления всей личной информации и его последующую передачу через rsync на серверы базы данных разработки. Серверы, в свою очередь, предоставляют ZFS-снимок каждому разработчику вместе с инструментами, позволяющими выполнять переход на новые снимки или делать откат к старым.

                        Мои правки по вашему переводу:


                        Здесь в Skroutz частью нашего стандартного набора инструментов является обеспечение каждого разработчика снимками базы данных с возможностью записи, которые он использует в разработке. Обновление при этом происходит ежедневно в виде пайплайна, который включает формирование LVM-снимков производственной базы данных, анонимизацию этих данных путем удаления всей личной информации и их последующую передачу через rsync на серверы базы данных разработки. Серверы, в свою очередь, предоставляют ZFS-снимок с копированием при записи каждому разработчику вместе с инструментами, позволяющими выполнять переход на новые снимки или делать откат к старым.

                        И всё ещё хочется это переписать, сделав предложения более простыми, без сложноподчинённых (а-ля художественный, но для технического текста):


                        У нас в Skroutz есть стандартный набор инструментов для разработчиков. Частью набора является личный снимок производственной базы данных с возможностью локальной записи. Обновления происходят ежедневно путём создания LVM-снимка производственной базы данных, анонимизации этих данных путем удаления личной информации, а также и их последующей передачи через rsync на серверы базы данных разработки. Серверы, в свою очередь, предоставляют каждому разработчику ZFS-снимок с копированием при записи вместе с инструментами, позволяющими выполнять переход на новые снимки или делать откат к старым.
                          +1
                          1. С перезаписываемыми соглашусь, нюанс, можно исправить на возможность перезаписи для точности.

                          2. Пайплайн как раз не по-русски, а значение у пайплайн — трубопровод. Конвейер — это устоявшийся термин в среде разработки и значение у него вполне себе понятное. Хотя мне изначально оно тоже не очень заходило, привык. Это программирование, тут много подобных слов и выражений, которые обывателю могут показаться странными и «не по-русски звучащими». Можете поискать по тому же поиску CI/CD-конвейер.

                          3. Copy-on-write исчезло, потому что болталось лишним. ZFS-снимок сам по себе подразумевает этот термин в своем техническом смысле.

                          4. Делается снимок среды (ее данных) — обрабатывается датасет (набор этих снимков данных) — передается на серверы. Что тут непоследовательного. Если слово датасет не нравится, просто писать данные несколько раз подряд будет совсем неуклюже, а датасет тоже домашний термин в программировании.

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

                          Вот ссылка www.freesoft.org/CIE/RFC/1323/17.htm
                          Здесь более-менее про предикшен расписано. Может это и не такое прям прогнозирование, как в моделях МО, например, но суть та же. Задается вопрос «является ли сегмент (пакет) следующим в последовательности?» Отсюда делается вывод и соответствующая обработка.

                          А вот еще точнее картинка faculty-web.msoe.edu/yoder/cs2911/lab1res/Lab1Example.pdf
                          Тут вообще вопросы отпадают.

                            +1
                            Правка по датасету — не набор снимков данных, а набор данных снимка.
                              0

                              :) Настаивать не буду. Я не технический переводчик ни разу.


                              По прогнозированию у меня отдельный пост есть тут в комментах.


                              Да, я знаю что в английском это устоялось именно так пышно называть ту однострочную упрощенную проверку пакета на то посылать ли его по быстрому пути. И возможно действительно правильно переводчику дословно и переводить.

                                –3
                                Можете поискать по тому же поиску CI/CD-конвейер.

                                в моем окружении никто не говорит конвейер — это выглядит как заумь, которая может быть в формальной документации для госорганов. В народе говорят пайплайн — и все понятно. И если уж докапываться — либо конвейер и "набор данных", либо "пайплайн" и "датасет".


                                1. Copy-on-write исчезло, потому что болталось лишним. ZFS-снимок сам по себе подразумевает этот термин в своем техническом смысле.

                                нет, не подразумевает. Молодец, Вы исказили смысл


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

                                чушь. Я уже приводил пример с branch prediction в процессорах, который ближе к топику, и переводится именно как "предсказание", а не "прогнозирование".

                              0
                              Только production в данном контексте не «производственная», а промышленная. По аналогии с промышленной эксплуатацией, например.

                              С остальным согласен :)
                          +2
                          «сбой прогнозирования заголовка быстрого пути»

                          Если память не изменяет, то скорее всего тут имеется введу изменение заголовка TCP пакета. Т.к. когда начинается обработка TCP пакета, то первым делом в сетевом стеке определяется, как его следует обрабатывать. По «slow_path» или по «fast_path». Почти все пакеты обрабатываются по «быстрому пути». В функции tcp_rcv_established. В этой функции, решение, о том следует ли попасть в «slow_path» определяется на основании указанного времени отправки последнего пакета сервером tp->rx_opt.rcv_tsval. TP- это структура tcp соединения. Вообще, можно формировать любые пакеты tcp с любым временем (таким чтоб пакеты не отбрасывались при проверки), играться с значениями syn ack urg в пакетах. И тогда, можно увидеть далеко не "зависание соединения"
                            0
                            Я выше накопал пару полезных ссылок для более точного понимания header prediction. Если с английским дружите — обратите внимание.
                            +2

                            Вот бы кто-то ещё отладил похожую мистику с подвисанием соединений WireGuard. Это точно не данный баг, к сожалению, потому что там UDP. (Судя по tcpdump, пакет от клиента приходит на сервер, сервер на него отвечает, но до клиента ответ не доходит. При этом похоже, что теряются пакеты от сервера исключительно с порта WireGuard сервиса на порт WireGuard клиента — пакет отправленный ручками с другого порта сервера на тот же порт клиента доходит нормально. Перезапуск клиента через wg syncconf приводит к смене порта клиента и всё снова работает. До подвисания соединения по нему тоже успевают гигабайты набегаться, и случается это не каждую неделю, так что отлаживать сложновато.)

                              0

                              Спасибо за информацию. То-то я думаю, что WG еще не готов к массовому распространению )

                                0

                                Я не думаю, что это проблема WG, примерно как в статье это была не проблема rsync.

                              +1

                              В inetd была бага 10 лет дающая DoS и норм :)

                                +1

                                Припоминаю (видимо) аналогичный случай с нашей "полковой кобылой": ~10 лет назад (до докера) была своя мини-ферма для CI, где сборочные chroot-ы подтягивались/синхронизировались rsync-ом. И вот rsync иногда действительно подвешивался, то только при работе на серверах самой фермы (10G на локальном свиче). На рабочих станциях (с 1G) воспроизвести не смогли, поэтому грешили на сферу и/или свичи...

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое