Компания уплотняет серверы с PostgreSQL и Redis. Средняя занятая память выглядит безопасно, поэтому лимит контейнера уменьшают. Сначала ничего не меняется: Linux освобождает страницы, а задержка базы остаётся прежней. Затем небольшое следующее сокращение внезапно поднимает p99, хотя процесс не завершён по OOM и на графике всё ещё видна память. Для этого используют понятие working set — рабочий набор. Это страницы памяти, к которым нагрузка возвращается в выбранном временном окне и фазе. Граница становится заметна не там, где свободной памяти мало, а там, где вытеснение начинает забирать полезные страницы и сервис платит за их возврат ожиданием и задержкой.
Рабочий набор принадлежит фазе, а не базе
У одной СУБД нет единственного вечного working set. Дневные транзакции обращаются к одному диапазону, вечерний отчёт — к другому, резервное копирование и обслуживание проходят больший объём, а после перезапуска база заново прогревает память. Переключение на резервный узел добавляет ещё одну обязательную фазу с непривычным профилем. Страница считается важной не потому, что относится к определённой таблице или ключу, а потому, что работа снова обращается к ней достаточно скоро.
То, что было холодным ночью, утром может стать горячим. Поэтому рабочий набор нельзя определять раз и навсегда по имени объекта или возрасту данных. Практический смысл границы таков: пока уменьшение доступной памяти вытесняет в основном страницы без скорого повторного использования, качество работы почти не меняется. После определённой точки ядро чаще забирает страницы, которые вскоре снова нужны, и стоимость возврата попадает в критический путь запросов.
Refault показывает возврат вытесненной страницы
Когда ядро освобождает память, оно может вытеснить страницу. Если нагрузка вскоре обращается к ней снова, происходит refault — возврат ранее вытесненной страницы. Для файловой страницы это может означать повторное получение данных из файлового пути; для анонимной памяти путь зависит от её состояния и использования swap. В memory.stat cgroup v2 поля workingset_refault_anon и workingset_refault_file считают возвраты ранее вытесненных анонимных и файловых страниц. Это накопительные счётчики: после нормальной долгой работы их абсолютное значение почти неизбежно выше, чем в начале. Само большое число не доказывает текущую нехватку RAM.
Смысл появляется у приращения за сопоставимый интервал работы. Если за одинаковую фазу при меньшей доступной памяти refault растёт быстрее, ядро чаще возвращает вытесненные страницы. Но и это ещё не диагноз: отчёт мог прочитать новый диапазон, изменился поток запросов или началось восстановление. Поля workingset_activate_anon и workingset_activate_file отвечают на другой вопрос. Они считают возвращённые страницы, которые были сразу восстановлены в активном рабочем наборе. Их нельзя смешивать с refault или называть двумя именами одного события: refault фиксирует возврат, activate — последующее решение считать такую страницу активной.
File и anon не делят мир на PostgreSQL и Redis
Файловые страницы связаны с файлами и page cache — кэшем недавно использованных частей файлов в памяти ОС. PostgreSQL опирается и на собственный shared_buffers, и на кэш операционной системы, поэтому чтение его файлов способно проявляться в file-части статистики cgroup. Анонимные страницы не имеют обычной файловой основы. Значительная часть памяти allocator процесса Redis и рабочих структур других сервисов относится к этому классу. Поэтому рост anon-refault может быть связан с памятью приложения и swap, если подкачка разрешена.
Но file не является синонимом PostgreSQL, а anon — синонимом Redis. У PostgreSQL есть анонимные области и процессы, у Redis — исполняемые файлы, библиотеки и другие file-backed mappings. Контейнер также может включать несколько процессов и файловый кэш их I/O. Разделение направляет расследование, но не назначает виновника. File-refault может отражать возврат полезных страниц, новый диапазон или прогрев; anon-refault требует понимания swap и процесса. Оба получают смысл только рядом с фазой СУБД и результатом приложения.
PSI связывает давление с потерянным временем
Pressure Stall Information, или PSI, показывает долю времени, когда задачи не могли продвигаться из-за нехватки ресурса. Для памяти это ближе к бизнес-проблеме, чем процент занятости: измеряется не объём, а потерянное рабочее время. В PSI строка some означает интервалы, когда из-за ресурса ждала хотя бы часть задач. Full означает время, когда одновременно не продвигались все непростаивающие задачи рассматриваемой группы. Длительный full-сигнал характерен для тяжёлого thrashing, но даже он не называет страницу, SQL или ключ, которые создали давление. PSI доступен для системы и cgroup, поэтому различает ситуацию хоста и ожидание ограниченной группы. Но cgroup — не модель всего сервера: родительские границы, соседи и общий I/O всё равно влияют.
p99 показывает задержку, быстрее которой завершается подавляющее большинство операций. Это пользовательский итог, но не датчик RAM. Он растёт также из-за CPU, блокировок, сети, storage и изменения запросов. PSI тоже не доказывает, что нужно купить DRAM: он показывает ожидание ресурса, а не бизнес-причину. Убедительная цепочка появляется, когда в одной обязательной фазе усиление reclaim сопровождается более быстрым приростом refault, растёт memory PSI, видны ожидание I/O или возврата страниц и одновременно ухудшаются p99 либо throughput. Если меняется только один слой, решение о памяти преждевременно.
Мягкая граница показывает цену уменьшения RAM
В cgroup v2 memory.high — мягкая граница. При её превышении процессы группы ограничиваются и попадают под усиленный reclaim. Ядро пытается вернуть расход ниже границы, поэтому приложение постепенно чувствует давление, но само превышение memory.high не запускает OOM killer. memory.max — жёсткий предел. Если использование достигло его и не может быть уменьшено, возможен OOM внутри cgroup. Это средство окончательного ограничения, а не удобная первая ступень поиска производственной границы. Резкое начало с memory.max смешивает исследование качества с риском завершить СУБД.
Сопоставимое сравнение меняет одну доступную ёмкость при одной и той же обязательной фазе. После прогрева нагрузка должна оставаться достаточно похожей, иначе разница в refault и p99 объясняется не RAM, а другим набором запросов. Здесь нет универсального шага или процента: база с ровным доступом и система с небольшим горячим ядром реагируют по-разному. Интерес представляет не первый refault, а устойчивая точка перелома. До неё освобождается преимущественно малоиспользуемая память; после неё refault, PSI и задержка растут вместе при том же объёме полезной работы. Краткая цена смены фазы допустима, если SLO — договорённое качество сервиса — сохраняется. Постоянное вытеснение и возврат рабочих страниц означает, что граница пройдена.
Бюджет принадлежит худшей обязательной фазе
Бизнесу нужна не минимальная RAM, при которой процесс ещё жив, а ёмкость, выдерживающая обязательную работу с запасом. Границу определяют по худшей повторяемой фазе: обычному пику, отчёту, обслуживанию, прогреву или восстановлению, если эта фаза входит в обещание клиенту. Запас нужен потому, что working set меняется. Новый релиз, рост данных, другой план запроса или переключение реплики способны сдвинуть границу. Покупка ровно по найденной точке превращает нормальную изменчивость в будущий инцидент; покупка по максимальной занятости без связи с полезной работой может закрепить переплату.
Прозрачный страничный уровень класса gigaRAM имеет смысл только тогда, когда менее активная часть действительно отделима от горячего working set. Горячие страницы должны оставаться в DRAM, а возврат через NVMe добавляет задержку. Linux refault и PSI не доказывают напрямую, какие страницы перемещает конкретная реализация, и публичное позиционирование не является обещанием совместимости или результата для любой СУБД. Если после отделения менее активной части p99 и throughput сохраняются, более дешёвая ёмкость может иметь экономический смысл. Если рабочая фаза возвращает широкий набор, накопитель насыщен или даже редкий промах нарушает SLO, правильным решением остаётся больше DRAM либо изменение самой нагрузки.
Главный вывод: working set — это не число в паспорте базы, а поведение страниц во времени. Refault показывает их возврат, PSI — потерянное из-за давления время, p99 — последствия для пользователя. Граница RAM становится убедительной лишь там, где эти слои меняются совместно в одной обязательной фазе.
Источники. Linux 6.12 cgroup v2 определяет memory.stat, refault/activate и границы memory.high/memory.max.
Linux PSI объясняет some, full и потерянное время.
PostgreSQL 17 Resource Consumption подтверждает роль кэша ОС, а Redis INFO — память allocator процесса.
Meta TMO используется только для общей механики страничного переноса, продуктовая страница — для позиционирования.
