MAATRIX / Блог / Антипаттерн: swap вместо нормальной памяти

Антипаттерн: swap вместо нормальной памяти

MAATRIX

Сервер начал падать по нехватке памяти, и вместо апгрейда тарифа кто-то один раз выполнил fallocate -l 8G /swapfile — и с тех пор так и живёт: приложению физически не хватает RAM, а вместо покупки памяти к серверу прикручен огромный файл подкачки. Проблема в том, что это решение работает лишь до тех пор, пока swap используется как редкая аварийная подушка, а не как повседневная замена памяти — а именно вторым сценарием он и становится через пару месяцев. Разберём, как именно так делают, что конкретно идёт не так технически, и как отличить нормальный swap от костыля, который тихо убивает производительность сервера.

Как это выглядит на практике

Антипаттерн редко появляется как осознанная архитектурная ошибка — обычно это быстрый ответ на алерт "сервер лёг по OOM" глубокой ночью. Симптом виден сразу: free -h показывает, что оперативная память забита под ноль, dmesg | grep -i "out of memory" находит следы того, как OOM-killer уже прибил процесс базы данных или воркер приложения. Решение принимается за пять минут:

fallocate -l 8G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
# и чтобы пережило перезагрузку
echo '/swapfile none swap sw 0 0' >> /etc/fstab

Алерт исчезает, сервис перестаёт падать, инцидент закрыт. На сервере с 2-4 ГБ реальной RAM теперь официально "16 ГБ памяти" — если считать RAM и swap суммой, как иногда по привычке делают в мониторинге. Дальше происходит ключевой сдвиг: вместо того чтобы воспринимать эти 8 ГБ как разовую страховку на случай пиковой нагрузки, команда воспринимает их как постоянное расширение памяти. Тариф не апгрейдят, потому что "памяти же теперь хватает", утечки в приложении не чинят, потому что процесс больше не падает, а рост потребления памяти со временем компенсируют новым, ещё большим swap-файлом — встречается и 16, и 32 ГБ подкачки на сервере с 4 ГБ RAM.

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

Почему это работает — до поры до времени

Swap действительно решает конкретную и узкую проблему: избежать OOM-killer в момент, когда приложению на несколько секунд понадобилось больше памяти, чем есть физически. Ядро Linux в такой ситуации не обязано сразу убивать процесс — оно может выгрузить на диск страницы памяти, которые давно не использовались (например, часть кэша или редко нужные структуры фонового процесса), освободив место для срочного запроса. Через несколько секунд всплеск проходит, активные страницы снова в RAM, а в swap лежит то, что и так почти не трогают. В этом сценарии swap работает ровно так, как задумано — как амортизатор.

Проблема в том, что этот механизм абсолютно одинаково выглядит "снаружи" в двух принципиально разных ситуациях: когда swap используется раз в сутки на пару секунд для сглаживания пика, и когда система систематически, постоянно выгружает и загружает активные страницы, потому что реальной памяти не хватает не на пик, а на базовый, повседневный объём работы. swapon не делает различия между этими сценариями — команда одна и та же, файл один и тот же. Различие проявляется только в поведении под нагрузкой, и именно оно превращает рабочую страховку в антипаттерн.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Диск против оперативной памяти: разница на порядки

Технический корень проблемы прост и не зависит от того, насколько быстрый диск стоит на сервере. Оперативная память — это электронная схема с временем доступа в единицах и десятках наносекунд. Даже самый быстрый NVMe SSD — это устройство, где физический доступ к данным на порядки медленнее: время отклика меряется уже в микросекундах, а под конкурентной нагрузкой на реальных дисках нередко доходит и до миллисекунд. Точные цифры зависят от конкретной модели диска, файловой системы, интерфейса и нагрузки — здесь не будем приводить надёрганные из вакуума бенчмарки, — но порядок разницы между RAM и диском общеизвестен и не меняется от одного диска к другому: это разрыв в тысячи раз, а не в разы.

Пока swap используется как аварийная подушка, эта разница почти не заметна: обращения к диску редкие, короткие, и общая производительность системы просаживается на долю секунды, которую пользователь чаще всего даже не замечает. Но как только приложению систематически не хватает физической памяти для повседневной работы, ядро вынуждено регулярно выгружать в swap страницы, которые нужны прямо сейчас, а не когда-то потом. Каждое такое обращение — это не обращение к RAM за наносекунды, а обращение к диску за микросекунды-миллисекунды, помноженное на то, что таких обращений становится не единицы в сутки, а сотни и тысячи в минуту.

Thrashing: когда подкачка перестаёт быть страховкой

Состояние, в которое скатывается сервер при систематическом использовании swap вместо памяти, называется thrashing (в переводе — "молотьба", "перемалывание"). Суть в следующем: система тратит основную часть процессорного времени не на полезную работу приложения, а на постоянное перекладывание страниц памяти между RAM и диском — выгрузила одну страницу, тут же понадобилась другая, которую только что выгрузили, загружает её обратно, вытесняя третью, которая тоже скоро понадобится. Полезной работы делается всё меньше, а системных издержек на управление памятью — всё больше.

Диагностировать thrashing несложно, признаки характерные:

# высокий si/so (swap in / swap out) в столбцах swap — система активно свопит, а не просто держит резерв
vmstat 1 10
# высокий wa (iowait) — процессор простаивает в ожидании диска, а не занят вычислениями
top
# si (страниц свопнуто в секунду) и so (страниц свопнуто из своп в секунду) стабильно ненулевые
sar -B 1 10

Классическая картина: free -h показывает, что своп активно используется (не 50 МБ "для порядка", а гигабайты), top показывает высокий %wa (iowait) при низкой реальной загрузке CPU полезной работой, load average заметно выше числа ядер, а сервис при этом ощутимо "тормозит" — простая операция, которая обычно занимает миллисекунды, вдруг занимает секунды. Пользователи в этот момент обычно формулируют это как "сайт подвис" или "бот не отвечает", хотя формально ни один процесс не упал и в логах нет ошибок — приложение просто перемалывает страницы памяти вместо того, чтобы обслуживать запросы.

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

Страховка и подмена — два разных сценария использования swap

Здесь стоит явно развести два сценария, которые статья про правильный размер swap для VPS разбирает подробнее с таблицей по объёму RAM, а здесь важно зафиксировать принципиальную разницу:

Сценарий "страховка". Swap подключён как резерв на случай редкого и короткого всплеска потребления памяти — секунды, изредка минуты. Всё остальное время своп практически не используется, free -h показывает единицы или десятки мегабайт в столбце Swap used, а не гигабайты. Это здоровое, штатное использование механизма — он существует именно для этого случая, и отключать его "на всякий случай", решив, что своп вреден в принципе, тоже не стоит: без него любой краткий пик грозит немедленным падением процесса через OOM-killer, что подробно разобрано в статье о том, как работает swap и почему его не стоит отключать.

Сценарий "подмена". Swap используется систематически, часами и днями подряд, потому что базовый, ежедневный объём потребления памяти приложением превышает физическую RAM. Здесь free -h показывает постоянно занятые гигабайты в Swap used, si/so в vmstat не опускаются до нуля даже в спокойные часы, а не только на пиках. Это принципиально другой режим работы — не страховка от редкости, а постоянная компенсация нехватки, и производительность в этом режиме несравнимо хуже, потому что диск начинает участвовать не в единичных, а в систематических операциях с активными данными.

Разница между сценариями — не в размере swap-файла, а в том, насколько часто и насколько активно система в него реально пишет и читает. Восьмигигабайтный swap-файл может месяцами простаивать почти нетронутым (сценарий 1) или быть загружен на полную каждый день (сценарий 2) — команда swapon в обоих случаях выглядит одинаково, а последствия для пользователей приложения — нет.

Что делать правильно

Первый шаг — определить, в каком из двух сценариев сервер находится сейчас. Мониторинг swap-активности стоит вести не разово, а постоянно — простая команда для быстрой проверки:

# сколько своп реально используется прямо сейчас
free -h
# активность своп за последние секунды — если si/so стабильно не ноль, это тревожный знак
vmstat 2 10
# исторические данные по памяти, если собирается sysstat
sar -r -B 1 | tail -20

Если swap используется систематически (сценарий "подмена") — единственное устойчивое решение это не наращивать своп ещё сильнее, а закрыть реальную нехватку памяти одним из двух способов:

  • Апгрейд тарифа по RAM. Если приложению по факту нужно, скажем, 8 ГБ, а тариф даёт 4 ГБ — арендовать план с бОльшим объёмом памяти дешевле, чем кажется, если сравнивать не с абстрактным "а вдруг обойдёмся", а с реальной стоимостью деградации производительности, потерянных пользователей и часов на диагностику "тормозов", причина которых на самом деле банальна.
  • Оптимизация потребления памяти приложением. Утечки памяти, неоптимальные кэши без ограничения размера, избыточное число воркеров процесса, не настроенные лимиты для баз данных (например, shared_buffers в PostgreSQL или innodb_buffer_pool_size в MySQL, выставленные больше реального объёма RAM) — частые источники того, что памяти "не хватает" не из-за реальной нагрузки, а из-за неоптимальной настройки. Здесь стоит свериться с ориентирами по объёму памяти под конкретную задачу и с тем, сколько оперативной памяти закладывать с запасом, прежде чем считать текущий объём RAM окончательно недостаточным.

Если же swap используется штатно, как страховка — стоит привести его размер к разумному, а не унаследованному от паники ориентиру. Общее практическое правило для современных серверов: размер swap сопоставим с объёмом RAM или меньше его, а не кратно больше. Своп в разы больше RAM почти никогда не оправдан — если системе реально может понадобиться в несколько раз больше памяти, чем есть физически, проблему решает апгрейд тарифа, а не растущий файл подкачки, который лишь отодвигает тот же вопрос на потом ценой худшей производительности прямо сейчас.

Отдельный практический момент — износ накопителя. Современные SSD, особенно серверного класса, достаточно выносливы, чтобы кратковременная, редкая подкачка не была для них проблемой за разумный срок эксплуатации. Но при систематическом активном использовании swap как замены памяти количество операций записи на диск возрастает на порядки по сравнению со сценарием "страховка" — каждая выгружаемая страница это запись, а при постоянном thrashing таких записей становятся тысячи в минуту, круглосуточно, месяцами. Это не мгновенно убивает диск, но заметно расходует его ресурс записи (TBW) быстрее, чем при нормальной эксплуатации, — ещё один аргумент в пользу того, что swap-как-подмена памяти — это решение с накопительной ценой, а не бесплатный обходной путь.

Финальный штрих — если swap оставлен как страховка, стоит проверить и настроить параметр swappiness, который определяет, насколько охотно ядро выгружает страницы в своп даже при наличии свободной RAM:

# текущее значение (по умолчанию часто 60)
cat /proc/sys/vm/swappiness
# снизить агрессивность подкачки — ядро будет держаться за RAM дольше
sysctl vm.swappiness=10
# закрепить на постоянку
echo 'vm.swappiness=10' >> /etc/sysctl.conf

Значение поменьше (10-20) заставляет ядро использовать swap только при реальной нехватке RAM, а не превентивно выгружать редко используемые страницы при первой возможности — это уменьшает количество ненужных обращений к диску именно в сценарии "страховка", когда swap и так должен быть скорее спящим резервом, чем активным участником повседневной работы.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Как быстро понять, что сервер уже в режиме thrashing, а не просто изредка использует swap?

Посмотрите vmstat 2 10 в течение нескольких минут: если столбцы si/so стабильно не нулевые на протяжении всего наблюдения, а не выскакивают эпизодически, и одновременно wa в top заметно выше единиц процентов — это систематическая подкачка, а не редкая страховка.

Правда ли что своп вообще не нужен, если памяти достаточно?

Нет — даже при достаточном объёме RAM небольшой swap стоит оставить как страховку от редкого, но возможного всплеска потребления, который иначе закончится OOM-killer в самый неподходящий момент. Вопрос не в том, нужен ли своп в принципе, а в том, используется ли он как страховка или как постоянная замена памяти.

Можно ли просто увеличить swap ещё сильнее, если текущего не хватает?

Технически да, но это не решает исходную проблему, а откладывает её ценой ещё худшей производительности: если системе систематически не хватает текущего объёма RAM+swap, значит реальный дефицит физической памяти уже настолько велик, что дальнейшее наращивание файла подкачки только продлит агонию thrashing, а не устранит его.

Как отличить нехватку памяти от других причин "тормозов" сервера — например, от загрузки CPU?

Сначала проверьте top: высокий %wa (iowait) вместе с активным swap указывает именно на нехватку памяти и подкачку, а высокая загрузка %us/%sy при низком %wa и незанятом swap — это, скорее всего, нехватка процессорных ресурсов, а не памяти, и решается это другим способом.

Стоит ли отключать swap полностью, раз он "виноват" в тормозах?

Нет — виноват не сам swap, а то, что его используют как постоянную замену недостающей RAM. Отключение swap полностью убирает страховку от кратких пиков и делает сервер более уязвимым к внезапному OOM-killer; правильный шаг — не отключить своп, а устранить причину систематического дефицита памяти.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →