В LinkedIn несколько раз ловили фризы Linux длиной всего 10–15 секунд: база данных, обслуживающая пользовательскую ленту, становилась недоступной, а затем сама возвращалась в строй. Для команд, которые отвечают за высоконагруженные сервисы, это знакомый кошмар: инцидент уже ударил по доступности, а в логах почти пусто. Именно поэтому история важна не только как удачная отладка, но и как практический кейс о том, что делать, когда обычный мониторинг ничего не объясняет.
О разборе инцидента сообщает InfoQ со ссылкой на инженера LinkedIn Пратикмохана Шривастава. По его словам, проблема была особенно неприятной из-за своей природы: сбои жили считаные секунды, повторялись без явного шаблона и не имели заметного внешнего триггера. Для SRE, backend-разработчиков и техлидов тут плохая новость проста: если фризы Linux короткие и редкие, стандартные метрики часто показывают уже последствия, а не причину.
Первая полезная зацепка появилась не в CPU и не в дисковой подсистеме, а в памяти. Команда заметила, что каждый эпизод совпадал с кратким всплеском выделения памяти. После этого система стабилизировалась, но уже на более высоком базовом уровне потребления. Дальше инженеры начали отбрасывать типовые версии одну за другой: CPU throttling не подтвердился, фрагментация памяти и compaction не выглядели виновниками, file I/O тоже не дал объяснения. То есть картина была неприятно знакомой: сервис замирает, графики слегка дергаются, а привычный набор дашбордов по сути разводит руками.
На этом этапе LinkedIn пошла глубже обычного application monitoring и переключилась на off-CPU profiling. Логика здесь простая: если поток не грузит процессор, это не значит, что с ним все в порядке. Он может ждать блокировку, спать в ядре или стоять в очереди на ресурс. Инженеры собрали, как они сами описывают, «ловушку»: скрипт, который непрерывно следил за состоянием базы и в момент обнаружения фриза автоматически запускал профилирование off-CPU через eBPF-инструментарий BCC. Конкретно использовался offcputime.py, который в течение 15 секунд снимал kernel stack traces для заблокированных или спящих потоков.
Это и стало переломным моментом. Главная проблема коротких сбоев в том, что они кончаются быстрее, чем человек успевает подключиться к процессу и запустить диагностику вручную. В LinkedIn фактически признали очевидное, которое на практике многие игнорируют: если баг живет 10 секунд, то отлаживать его постфактум почти бессмысленно. Инструментирование должно уже ждать рядом и включаться автоматически по условию сбоя. Для команд, у которых есть периодические фризы Linux без внятных следов, это, пожалуй, главный организационный вывод из всей истории.
Корневая причина оказалась на стыке рантайма, модели данных и поведения ядра Linux. Виновником стало очень крупное выделение памяти, примерно на 3,5 ГБ. Оно приводило к захвату kernel-level lock на семафоре mmap_lock, из-за чего фактически блокировались все остальные потоки. Причина в механике самого ядра: любая операция, которая меняет виртуальное адресное пространство процесса, например большой mmap, должна взять этот lock в режиме записи. Пока write lock удерживается, останавливаются и другие потоки, которым нужны операции с памятью, включая madvise для очистки и обработку page fault для I/O. Снаружи это выглядит почти мистически: сервис «завис», CPU не обязательно в потолке, явной ошибки нет, а на деле приложение уперлось в узкое место на уровне ядра.
Дальнейший анализ показал, что то самое крупное выделение памяти инициировал Rust HashMap с именем pkey_vs_docref. Эта структура связывала primary key с внутренними ссылками на документы. Когда размер HashMap переваливал за 58 720 256 записей, срабатывал порог resize, и таблица удваивалась. В обычном приложении это может быть просто дорогой момент. В latency-sensitive системе, где на одной машине живет критичная база для ленты LinkedIn, такой resize превращается в вполне осязаемый инцидент доступности. И здесь особенно полезно помнить неприятную инженерную правду: амортизированная сложность в учебнике и поведение под реальной нагрузкой в проде не всегда дружат.
Исправление получилось довольно приземленным, но именно такие решения чаще всего и работают. В LinkedIn заранее выделили память под HashMap, чтобы исключить resize во время работы. Цена вопроса — около дополнительных 3 ГБ резидентной памяти уже на старте процесса. Команда сочла этот компромисс приемлемым. Для бизнеса и эксплуатации смысл очевиден: немного переплатить памятью дешевле, чем регулярно получать короткие, но болезненные провалы в доступности ключевого сервиса. Для разработчиков это еще одно напоминание, что оптимизация «по среднему случаю» не всегда совпадает с оптимизацией под хвосты latency.
В этой истории есть и более широкий контекст. По мере роста систем на Rust, Java, Go и других языках с богатыми runtime-абстракциями команды все чаще упираются не в прямые баги приложения, а в пограничные эффекты между кодом, аллокатором, контейнерами данных и ядром. На графиках это выглядит как кратковременная деградация, на постмортеме — как длинный путь от симптома к механике блокировки в ОС. Поэтому eBPF и off-CPU profiling перестают быть игрушками для очень узких Linux-энтузиастов и становятся рабочим инструментом продакшн-диагностики. Особенно там, где фризы Linux проявляются редко, но бьют точно в пользовательский опыт.
Для русскоязычной IT-аудитории вывод довольно прямой. Если у вас есть высоконагруженный сервис, большие in-memory структуры и жесткие требования по задержкам, проблема может сидеть не в бизнес-логике и не в «медленной базе» как таковой, а в моменте перераспределения памяти, который неожиданно дотягивается до блокировок ядра. Чем активнее компании переходят на детальные профилировщики, eBPF-наблюдаемость и автоматический сбор артефактов по сигналу сбоя, тем меньше шансов, что очередной «немой» инцидент снова будет списан на случайный всплеск нагрузки. Вопрос теперь не в том, пригодится ли такой подход, а в том, сколько команд готовы закладывать его заранее, а не после первого дорогого простоя.