Рабочий процесс 1С растёт по памяти: как это замерить и что выставить
Иван Недомолков · Жарияланды: · Жаңартылды:
За одиннадцать суток рабочий процесс сервера приложений распух с 7,22 гигабайта до 12,72: ровно по полгигабайта в день, прямой линией, без ступеней и плато. Сбросить рост может только перезапуск сервиса, ведь ни одного лимита памяти в кластере не задано.
Ниже порядок работы с таким случаем, а не рассказ о расследовании. Сначала как убедиться, что растёт именно то, что вы думаете, потому что метрики врут в обе стороны и на этом теряется больше времени, чем на самой настройке. Потом сбор и расчёт наклона. Потом четыре свойства кластера, которые надо заполнить. И отдельно соседняя дыра, которая обошлась в десять гигабайт и лечится одной настройкой без единой строчки кода.
Сразу честно: рост на полгигабайта в сутки - симптом. Что именно держит память, мы не выяснили. Лимиты с вежливым перезапуском утечку не лечат, только не дают ей уронить сервер. Замеренного “после” тоже нет, всё ниже - настройки на момент разбора.
Числа взяты с одной машины на четыре ядра и 32 гигабайта. Там крутятся сразу сервер терминалов, 1С 8.3.27 как сервер приложений, толстые и тонкие клиенты вместе с браузерами пользователей, плюс локальный PostgreSQL 18. Совмещать так спорно, зато вывод из этого полезный: процессор на такой машине физически не упрётся в потолок раньше памяти, потому что каждый сеанс обходится в гигабайты и десятки мегагерц. Метрики снимались раз в пятнадцать секунд на протяжении двух недель.
Шаг 1: убедиться, что метрика не врёт
Три проверки, каждая снимает свой класс ложных выводов. Делать их надо до того, как пойдёте что-то настраивать, иначе настроите не то.
Порядок чтения: среднее, доля времени, максимум
Сводная таблица суточных максимумов рисовала дисковую подсистему загруженной на 97-99 % ежедневно, все дни подряд. Вывод сам просится: диск в потолке, пора расширять. И вывод удобный: одним махом объясняет всё и сводится к бюджету.
А среднее по тому же ряду - 4,24 %. Расхождение в двадцать три раза уже диагноз: ресурс свободен, у него лишь бывает короткий пик, он-то и попадает в максимум.
Третья цифра - доля времени над порогом. Выше 80 % диск за неделю поднимался в 7 пятиминутках из 2016, то есть треть процента времени. Откуда 2016: столько пятиминуток в неделе. Семь всплесков за семь суток, по одному каждую ночь, и это окно резервного копирования, по журналу минут шесть.
Хуже суточного максимума метрики для суждения о перегрузке не найти. Из него видно лишь, что ресурс когда-то был занят, а как долго, он не говорит. Читать надо в таком порядке: среднее (есть ли нагрузка вообще), доля времени выше порога (мешает ли она), максимум (уточнение к первым двум).
Долю времени считать несложно, если ряд лежит в CSV:
# Доля времени, когда счётчик был выше порога. Порог и путь подставьте свои.
$porog = 80
$ryad = Import-Csv 'C:\metrics\disk.csv'
$vsego = $ryad.Count
$vyshe = ($ryad | Where-Object { [double]$_.Value -gt $porog }).Count
'{0} из {1} замеров выше {2}% = {3:N2}% времени' -f $vyshe, $vsego, $porog, (100 * $vyshe / $vsego)
Меньше процента - расширять ресурс незачем: у вас не перегрузка, а регламентная операция, и её надо либо принять, либо развести по времени.
Разрешение ряда: прореженный врёт в обе стороны
Ради скорости отрисовки графиков ряд проредили до шага в пять минут: на интервал оставалось одно значение. И свободное место на диске по такому ряду выглядело благополучно.
Сырые данные с шагом пятнадцать секунд показывают другое: почти ежедневно свободное место проседает до 27-450 мегабайт. Ориентироваться в таком разбросе стоит на нижнее значение, а при 27 мегабайтах база писать уже не может.
Почему так: событие длиной в пятнадцать секунд оказывается в пятиминутной выборке примерно в одном случае из двадцати. Так что минимум прореженного ряда - минимум лишь по названию, на деле это случайная точка интервала.
Обратите внимание, что получилось. Одни и те же данные при одной и той же ошибке метода дали два ложных вывода, причём противоположных: диск якобы загружен постоянно, хотя свободен, а место якобы есть, хотя оно кончалось.
Отсюда рабочее правило: до спора о выводах выясните, с каким шагом строился график и что именно по нему считали. Агрегат должен соответствовать вопросу: про перегрузку спрашивают среднее и долю времени, про исчерпание ресурса - минимум по сырому ряду.
Какую цифру памяти вообще смотреть
Их две, и их постоянно путают.
- Резидент (
Working Set) - то, что процесс физически держит в оперативной памяти. Это показывает диспетчер задач. - Коммит (
Private Bytes) - то, что процесс попросил у системы и получил, независимо от того, вытеснено оно в подкачку или нет.
Разница бывает кратной: в нашем разборе аварийных
дампов попался процесс с резидентом 379 МБ
и коммитом 1371 МБ, в 3,6 раза больше. Мониторинг, следящий за резидентом, роста
просто не увидит, а отказы “не удалось выделить память” считаются по коммиту. Снимать
надо Private Bytes.
Шаг 2: собрать ряд и посчитать наклон
Отдельный инструмент не нужен, достаточно писать в CSV раз в минуту и хранить месяц:
# Сбор памяти рабочих процессов 1С в CSV. Ставится в планировщик
# на запуск при старте системы, с перезапуском при сбое.
$fajl = 'C:\metrics\rphost.csv'
if (-not (Test-Path $fajl)) {
'Vremya;Process;PID;PrivateMB;WorkingMB' | Out-File $fajl -Encoding utf8
}
while ($true) {
$moment = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
foreach ($p in Get-Process -Name rphost, rmngr, ragent -ErrorAction SilentlyContinue) {
$priv = [math]::Round($p.PrivateMemorySize64 / 1MB, 1)
$work = [math]::Round($p.WorkingSet64 / 1MB, 1)
"$moment;$($p.ProcessName);$($p.Id);$priv;$work" | Add-Content $fajl -Encoding utf8
}
Start-Sleep -Seconds 60
}
Тот же ряд снимается счётчиками производительности, если сборщик уже есть:
\Process(rphost*)\Private Bytes и \Process(rphost*)\Working Set.
Смотреть в собранном надо наклон, а не текущее значение: процесс имеет полное право держать десять гигабайт, если он их использует.
# Наклон роста внутри одного отрезка между перезапусками.
# PID у нового процесса другой, поэтому группируем по нему - это и есть отрезок.
Import-Csv 'C:\metrics\rphost.csv' -Delimiter ';' |
Where-Object { $_.Process -eq 'rphost' } |
Group-Object PID |
ForEach-Object {
$tochki = $_.Group | Sort-Object { [datetime]$_.Vremya }
$ot = $tochki[0]
$do = $tochki[-1]
$chasov = ([datetime]$do.Vremya - [datetime]$ot.Vremya).TotalHours
if ($chasov -gt 6) {
$rost = ([double]$do.PrivateMB - [double]$ot.PrivateMB) / 1024
[pscustomobject]@{
PID = $_.Name
Chasov = [math]::Round($chasov, 1)
StartGB = [math]::Round([double]$ot.PrivateMB / 1024, 2)
KonecGB = [math]::Round([double]$do.PrivateMB / 1024, 2)
GBvSutki = [math]::Round($rost / $chasov * 24, 2)
}
}
} | Format-Table -AutoSize
Как читать результат:
- за неделю подросла и к исходному уровню ни разу не вернулась - та самая история;
- ступеньки вниз - память отдаётся обратно, процесс нормально работает под нагрузкой;
- пила на графике за месяц - смотрите внутрь отрезка. С перезапуском сервиса ряд стартует заново, и при перезагрузках раз в неделю рост выглядит безобидно, а по всему окну наклон смазывается до нуля. Поэтому в скрипте группировка по PID: новый процесс - новый отрезок.
Шаг 3: заполнить четыре свойства кластера
Настраивается механизм в консоли администрирования кластера, в свойствах рабочего сервера, и из коробки он выключен целиком: все свойства придётся заполнять самим. Логика у него такая: если процесс пробыл выше порога дольше заданного интервала, новых соединений от кластера он больше не получает, текущие дорабатывают, а рядом стартует свежий процесс. При удачном раскладе пользователи ничего не заметят.
| Свойство | Когда срабатывает | Как выбирать |
|---|---|---|
| Объём памяти рабочих процессов, до превышения которого сервер считается производительным | мягкий порог, с него начинается вежливый перезапуск | считается от объёма машины: суммарный потолок процессов должен оставлять запас системе, СУБД и клиентам. При 32 ГБ и одном процессе - заметно ниже двенадцати |
| Интервал превышения допустимого объёма памяти | сколько секунд превышение терпится | от темпа роста. При полугигабайте в сутки счёт идёт на часы, минутные интервалы ни к чему |
| Максимальный объём памяти рабочих процессов | жёсткий потолок, аварийный стоп | в норме срабатывать не должен, поэтому вплотную к мягкому порогу не ставится: нужен зазор, в котором мягкий механизм успеет отработать |
| Безопасный расход памяти за один вызов | лимит на один серверный вызов | ловит запрос, решивший поднять в память полбазы. До постепенного роста ему дела нет, но без него один неудачный отчёт кладёт процесс вместе со всеми, кто в нём сидел |
Интервал задаётся в секундах, объёмы в байтах. Ноль везде означает “без ограничения”, и читается он обычно как “ограничений не нужно” - ровно та ошибка, из-за которой механизм у большинства выключен.
Прочитать и поменять из командной строки
Через консоль это делается мышью, но если серверов несколько или нужна
воспроизводимость, есть rac - консольный клиент администрирования (лежит рядом
с платформой, работает через службу ras).
# 1. Узнать идентификатор кластера
rac cluster list
# 2. Список рабочих серверов кластера
rac server list --cluster=<uuid-кластера>
# 3. Все свойства сервера с текущими значениями
rac server info --cluster=<uuid-кластера> --server=<uuid-сервера>
Третья команда главная: она печатает точные имена ключей вместе с текущими
значениями, и эти же имена принимает rac server update в виде --ключ=значение.
⚠️ Имена ключей берите из вывода info, а не по памяти и не из статей: между версиями
платформы они менялись, и команда с устаревшим ключом либо не отработает, либо
выставит не то свойство. Проверять результат надо повторным info, а не по факту
отсутствия ошибки.
Если службы ras нет, rac не с чем соединяться. Ищите её внимательно: у Windows есть
системные службы с теми же именами, к 1С они не относятся, но в списке выглядят как
нужные. Смотрите на путь к исполняемому файлу, а не на имя.
Сколько держать рабочих процессов
Если процесс на установку один, его перезапуск задевает всех разом. С двумя и более кластер может переселять соединения без заметной паузы. Настройки вида “сделать три” не существует: новый процесс появляется сам, как только кластер упрётся в “Количество соединений на процесс” либо в “Количество информационных баз на процесс”. А каждый процесс сам по себе ест память, и плавные перезапуски мы оплачиваем гигабайтами, что на машине с 32 ГБ ощутимо.
Шаг 4: проверить таймауты терминальных сеансов
Эта находка дешевле и обиднее предыдущей. Сеансы, отключённые на терминальном сервере, не завершались вообще: в политике все три таймаута стоят в нуле, а ноль тут значит “без ограничения”. В результате шесть мёртвых сеансов круглые сутки занимали 9-11,6 гигабайта. Толстый клиент плюс открытый браузер - это по полтора-два гигабайта на каждый сеанс, цена нормальная. Ненормально то, что сеансы живут вечно.
Треть памяти машины возвращается одной настройкой, прикладной код при этом никто не трогает.
Групповая политика: Конфигурация компьютера → Административные шаблоны → Компоненты Windows → Службы удалённых рабочих столов → Узел сеансов удалённых рабочих столов → Ограничения по времени для сеансов. Четыре параметра, и настраивать их надо вместе:
| Параметр | Про что | Стоит трогать |
|---|---|---|
| Ограничение по времени для отключённых сеансов | пользователь закрыл окно крестиком и ушёл | да, это чистая память, лежащая мёртвой |
| Ограничение для активных, но бездействующих сеансов | сессия открыта, в ней ничего не происходит | да |
| Ограничение по времени для активных сеансов | самый агрессивный, выкинет работающего | осторожно, обычно не нужен |
| Завершать сеанс при достижении ограничения | завершать или только отключать | обязательно, иначе “отключать” вернёт сеанс в первую категорию |
Последний пункт и есть ловушка: без него первые два не освобождают память, а переводят сеанс в состояние “отключён”, где он продолжает её держать.
Что реально выставлено, читается из реестра одной командой:
# Действующие ограничения сеансов RDS. Значения в миллисекундах,
# 0 или отсутствие значения означает "без ограничения".
$put = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
'MaxDisconnectionTime','MaxIdleTime','MaxConnectionTime','fResetBroken' | ForEach-Object {
$v = (Get-ItemProperty -Path $put -Name $_ -ErrorAction SilentlyContinue).$_
[pscustomobject]@{
Parametr = $_
Znachenie = if ($null -eq $v) { 'не задано' }
elseif ($_ -eq 'fResetBroken') { if ($v -eq 1) { 'завершать' } else { 'только отключать' } }
elseif ($v -eq 0) { 'без ограничения' }
else { '{0} мин' -f ($v / 60000) }
}
} | Format-Table -AutoSize
А это - кто висит прямо сейчас и сколько такой сеанс стоит:
# Отключённые сеансы и память, которую они держат.
# query session даёт состояние, Get-Process - вес по сеансам.
query session | Select-String 'Disc' | ForEach-Object { $_.Line }
Get-Process -ErrorAction SilentlyContinue |
Where-Object { $_.SessionId -ne 0 } |
Group-Object SessionId |
ForEach-Object {
[pscustomobject]@{
Sessiya = $_.Name
Processov = $_.Count
PrivateGB = [math]::Round((($_.Group | Measure-Object PrivateMemorySize64 -Sum).Sum) / 1GB, 2)
}
} | Sort-Object PrivateGB -Descending | Format-Table -AutoSize
Сопоставьте два вывода: сеансы в состоянии Disc с весом в полтора-два гигабайта
и есть мёртвые. Завершается такой сеанс командой logoff <идентификатор>, но
правильное лечение - политика, иначе они наберутся снова к концу недели.
Ошибка, которую легко повторить: арифметика объяснения
Этот раздел про метод, и он стоит отдельно, потому что ловушка тут не техническая.
Посреди наблюдения производительность в один из дней провалилась. Объяснение лежало на поверхности: 12,7 гигабайта у рабочего процесса, десять у мёртвых сеансов, в сумме 22,7 из 32, свободной памяти почти ноль, вот и провал.
Объяснение красивое, только арифметика в нём не сходится. До пика в 12,72 гигабайта процесс дорос к одиннадцатым суткам, а провал был на пятые. К тому дню у него было 7,22 плюс полгигабайта за каждый из четырёх дней, то есть примерно 9,2 гигабайта. С сеансами выходит 19,2 против 22,7, разница три с половиной гигабайта. По такому счёту свободных должно было остаться около 12,8 гигабайта, фактически же доступно было 3,15. Выходит, в день провала ещё около девяти с половиной гигабайт забрал кто-то третий: браузеры, клиенты или локальная СУБД. Этот вопрос в разборе остался открытым.
Сам механизм объяснили правильно. Ошиблись в числах: их взяли из конца периода и примерили к событию из его середины. Ошибка частая и плохо заметная: если объяснение сложилось и понравилось всем, его арифметику уже никто не перепроверяет. Нашли её случайно, когда по другому поводу взялись считать, сколько памяти осталось свободной.
Что памяти и правда не хватало, подтверждается косвенно: при 32 физических гигабайтах выделено было 33,49. С таким превышением без подкачки не обойтись, и отчёт её фиксирует. Только лечить нужно не подкачку.
Что проверить заранее, чтобы разбор не встал
На каждую из трёх вещей ушло время.
После перезагрузки служба сбора метрик не стартует и гибнет без единого сообщения. Отложенному автозапуску не хватает тридцати секунд, которые отводит менеджер служб, а действий восстановления никто не задал. Отсюда и пропуски в рядах в самый интересный момент, сразу после перезагрузок. Лечится двумя строчками:
# Перезапускать службу при сбое: первые два раза через минуту, дальше тоже.
sc.exe failure '<ИмяСлужбы>' reset= 86400 actions= restart/60000/restart/60000/restart/60000
sc.exe config '<ИмяСлужбы>' start= delayed-auto
Не зарегистрирована служба администрирования кластера: rac молчит, и настройки
остаётся вытаскивать из файла кластера вручную.
Мониторинга сеансов нет, правил оповещения ноль. Вдобавок с этой машины наружу не пускают трафик к сервису уведомлений, так что и настроенное правило ничего не смогло бы отправить. Проверьте доставку в день, когда появилось первое правило: молчащий мониторинг неотличим от спокойной системы.
Границы
Откуда рост памяти, не установлено. Темп и линейность известны, а что именно не освобождается, неясно. Лимиты лишь страхуют.
Замера “после” нет ни для одной из двух находок. На момент разбора таймауты и лимиты только предложены, и обещать результат в гигабайтах я не берусь.
Долю времени выше порога считали за неделю, хотя наблюдали две. По всему окну она вдвое ниже, и вывод “диск свободен” от этого только твёрже. Само число на другое окно не переносите.
Чек-лист
- Посчитать среднее и долю времени выше порога, максимум оставить на потом.
- Проверить разрешение ряда: для минимумов брать сырой, не прореженный.
- Снимать
Private Bytes, а неWorking Set. - Считать наклон внутри отрезка между перезапусками, группируя по PID.
- Заполнить четыре свойства кластера: по умолчанию там ноль, то есть без ограничений.
- Проверить три таймаута RDS и обязательно параметр “завершать сеанс”.
- Проверить арифметику собственного объяснения, особенно если оно всем понравилось.
Если разбираться некогда, а сервер уже упирается, мы делаем такие разборы как работу: снимаем метрики, отделяем перегрузку от регламентной операции, выставляем лимиты и показываем замер. Что входит - на странице обслуживания баз 1С.
Соседние разборы и инструменты
Если процесс не растёт, а падает, лечение совсем другое: там нужны дампы,
настройка их сбора и обращение в поддержку. Это отдельная история -
разбор аварийных дампов 1С без WinDbg,
на данных 354 дампов. Если память ровная, а rphost или rmngr держит процессор,
порядок поиска другой: сервер 1С грузит процессор.
Чек-ап СУБД под 1С - если СУБД стоит на той же совмещённой машине, что и сервер приложений, и делит с ним память. Больше сорока проверок с вердиктом, что не так в настройке.
Карта объёмов базы 1С - когда вслед за памятью подходит к концу и диск, первым делом разберитесь, из чего состоит сама база.
Анализ нагрузки кластера 1С - когда память съел конкретный сеанс, а не процесс вообще: обработка по снимкам сервера администрирования показывает, какой сеанс сколько потребил и кто держал блокировки.
Проверка после изменения настроек
После настройки перезапуска сравните расписание с возрастом процессов и активными соединениями. Порядок такой проверки описан в разборе настроек кластера 1С. Если рост памяти привязан к определённой операции, сбор технологического журнала поможет уточнить участок выполнения.