systemd timer accuracysec — задержка, которую никто не мерил

systemd timer accuracysec — задержка, которую никто не мерил

systemd timer accuracysec с замерами: дефолт 1min сдвигает срабатывания намеренно — 10 запланированных стали 5 в простое, у занятого соседа легла каждая метка.

HUMANавтор @msvram16h8 мин чтения

systemd timer accuracysec — запрос, который приводит тебя сюда: твой таймер с OnCalendar=*:0/15 упорно срабатывает на :07 после минуты, а journalctl -u уверяет, что всё в порядке. Всё действительно в порядке — systemd сдвинул твоё пробуждение намеренно. Этот пост — версия с замерами: что даёт дефолт, во что он обходится и какая одна строка возвращает твой график.

Чтобы systemd-таймер срабатывал точно по расписанию: задай AccuracySec=1s (или меньше) в секции [Timer] юнита-таймера. Дефолт — 1 минута: systemd выравнивает каждое пробуждение по грубой сетке и срабатывает с опозданием до 60 с, чтобы собрать несколько таймеров в одно пробуждение CPU. Реальную задержку проверяй через systemctl show your.timer -p LastTriggerUSec — значение обновляется при каждом срабатывании.

Последний тест 2026-11-14 · systemd 262 (262.1-1-arch) · свежая VM Arch. Каждый таймер ниже запускался по-настоящему; каждая квитанция вставлена с машины как есть.

простой — окно 1min, без соседей тот же таймер — один занятый 2-секундный сосед янтарный = срабатывание · призрак = меткаради которой CPU не будили
Рис. 1 — тот же календарь (*:*:00/15), тот же таймер, две машины. Простой: десять тиков сливаются в пять срабатываний — окно глотает каждую вторую метку. Занятый сосед: каждый тик попадает в свою метку, потому что пробуждение внутри окна уже существует.

Почему мой systemd-таймер срабатывает с опозданием?

Главное предложение: стоковый systemd-таймер может сработать на 60 секунд позже назначенного времени, и list-timers назовёт это «next elapse» без единого возражения. Это не баг и не дрейф часов — это AccuracySec, по умолчанию равный 1min, чья единственная работа — позволять планировщику опаздывать.

$ sudo systemctl show accA.timer -p AccuracyUSec
AccuracyUSec=1min
$ systemd-analyze cat-config /etc/systemd/system.conf 2>/dev/null | grep -i DefaultTimerAccuracy
#DefaultTimerAccuracySec=1min

проверка: вставлено со стендовой VM — стоковый Arch, без пользовательских конфигов, поэтому таймер показывает компилированный дефолт. Заметь написание свойства: в юнит-файле это AccuracySec=, а systemd 262 отдаёт его как AccuracyUSec.

Механика, одним абзацем: когда у таймера подходит время срабатывания, systemd будит CPU не сразу. Он вычисляет окно — от назначенного момента до AccuracySec спустя — и срабатывает в той его точке, которая позволяет разделить пробуждение с другими таймерами или растянуть сон CPU. Твой календарь *:0/15 говорит «четыре раза в минуту»; планировщик слышит «четыре шанса на попутку» — и на тихой машине берёт примерно один из них.

Насколько поздно срабатывает дефолт — с замерами?

Эксперимент, одним абзацем: таймер по расписанию OnCalendar=*:*:00/15 — четыре задуманных срабатывания в минуту — со стоковой точностью, запущенный дважды на одной и той же свежей VM Arch. Раз в одиночку на простаивающей машине, раз рядом с занятым 2-секундным таймером. Каждое срабатывание логирует своё реальное время исполнения; квитанции — сырые отметки времени.

Сначала простой — условия, в которых реально живёт большинство служебных таймеров:

accA 1794634092.128787380
accA 1794634125.384544834
accCo 1794634125.384546104   ← co-timer mark was :30 of the previous minute
accA 1794634152.127459587
accCo 1794634186.135416303   ← co-timer mark was :30 of the previous minute
accA 1794634186.135424133
accA 1794634212.133508500

проверка: прошло 10 меток, сервис отработал 5 раз — промежутки 26–34 с (в среднем 30.0) на календаре, который требовал каждые 15. Относительно ближайшей метки каждое срабатывание опоздало на 12.1, 0.4, 12.1, 1.1, 12.1 с: половина расписания, слитая в пять пробуждений.

Тот же таймер, тот же календарь — один занятый сосед (2-секундный таймер, AccuracySec=1us):

accA sched=1794633795101333 mono=193094591 exec=1794633795.114334868
accA sched=1794633795101333 mono=193094591 exec=1794633810.014311174
accA sched=1794633795101333 mono=193094591 exec=1794633825.116389742
  ⋮ (6 fires, 6 marks — every one on its mark)

проверка: раз пробуждение уже запланировано каждые 2 секунды, каждое из них попадает внутрь 60-секундного окна дефолтного таймера — и планировщик срабатывает на первом доступном пробуждении: точно в метку, максимум 119 мс опоздания. Слабина не уменьшилась — её потратило расписание соседа.

Эта пара квитанций — весь механизм на одном экране: окно — это предложение, а не дедлайн — systemd может сработать в любой момент внутри него и срабатывает на самом раннем пробуждении CPU, которое окно накрывает. Занятая машина: пробуждение есть сразу — твой таймер выглядит пунктуальным. Тихая машина: пробуждения нет, поэтому планировщик назначает его у дальнего края окна, и каждое накопившееся срабатывание внутри окна едет на нём. Простаивающие ноутбуки и одинокие серверы — там, где живёт большинство замен cron, — это и есть тихий случай. Поэтому один и тот же юнит — «на десктопе нормально, на NAS опаздывает».

Что на самом деле меняет AccuracySec?

Тот же календарь, та же VM, одна строка: AccuracySec=100ms. Теперь окно пробуждения шириной в одну десятую секунды, и таймер попадает туда, куда указывал календарь:

accB 1794634230.114374054
accB 1794634245.119370330
accB 1794634260.114446983

проверка: три срабатывания, три метки — в среднем 116 мс опоздания, максимум 119 мс, окно 100 мс; лишние ~15 мс — накладка диспетчеризации, которая есть у любой VM. Это весь регулятор целиком: ширина окна, больше ничего.

И сама группировка в пачки видна на таймере-соседе. Назначенный на :30 с дефолтной точностью на занятой машине, он ни разу не ждал полное окно — сработал через 15 мс после своей метки, оседлав пробуждение, которое 15-секундный таймер рядом с ним и так собирался сделать. Два таймера, одно пробуждение:

accCo sched=1794633810000832 mono=207994091 exec=1794633810.015037233
accCo sched=1794633810000832 mono=207994091 exec=1794633870.016986575
accCo sched=1794633810000832 mono=207994091 exec=1794633930.020077789
(accA exec at the same instants: 1794633795.114, 1794633810.014 …)

проверка: метка :30 у co — это и метка 15-секундного таймера — пробуждение на :30.000 уже было запланировано, и co присоединился к нему с опозданием 15 мс. На простаивающей машине он проделал тот же трюк без соседа на :30, попав в пробуждение таймера A с точностью до 1.2 µs (квитанция выше). То же окно, посадка умнее.

«systemd не опаздывает. Он ловит попутку — одно пробуждение в минуту, четыре пассажира. AccuracySec — это насколько полным должен быть автобус.»

Как заставить таймер срабатывать по расписанию?

Одна строка drop-in, без перезапуска чего-либо, кроме самого юнита-таймера:

$ sudo systemctl cat accB.timer | grep -A2 '^\[Timer\]'
[Timer]
OnCalendar=*:*:00/15
AccuracySec=100ms
$ sudo systemctl daemon-reload && sudo systemctl restart accB.timer
$ sudo systemctl show accB.timer -p AccuracyUSec,LastTriggerUSec
AccuracyUSec=100ms
LastTriggerUSec=Sat 2026-11-14 05:33:45 UTC

проверка: systemctl show печатает действующее окно — drop-in побеждает компилированный дефолт, а LastTriggerUSec обновляется при каждом срабатывании. Твоя квитанция «до/после» — один запрос, без раскопок в журнале.

Насколько низко опускаться: таймер C гонял AccuracySec=1us на 2-секундном календаре и выдал 11 срабатываний за 22 с с джиттером 23 мс в худшем из 11 — планирование с субмиллисекундной точностью доступно, если хочется. Но прежде чем раскатывать это на весь парк, взгляни на колонку цены:

Во сколько тебе обходится точность?

Простаивающая машина обслужила десять меток пятью пробуждениями; таймер 1 µs запросил бы семьдесят пять в том же окне. На ноутбуке это ровно та разница, от которой дефолт создан уберечь, — группировка в пачки не лень, а та же дисциплина энергосбережения, что собирает сбросы диска в одну кучу. Серверам всё равно; твоей батарее — нет. Разумные ступени: 1s для дедлайнов, которые замечают люди, 1min (дефолт) для служебных задач, которых не замечают, и ничего ниже 100ms, пока не замеришь зачем, — на этом этапе долгоживущий сервис с циклом сна бьёт лавину таймеров. А 1us на ноутбуке — это способ превратить батарею в грелку для рук.

Регулятор одним взглядом

AccuracySecкалендарьпо расписаниюсработалочто произошло
1min (default)*:*:00/15, idle box105промежутки 26–34 с, в среднем 30.0 — половина срабатываний
1min (default)*:*:00/15, busy neighbor66каждая метка, опоздание ≤119 мс
100ms*:*:00/1533каждая метка, опоздание ≤119 мс
1us*:*:00/21111каждая метка, опоздание ≤22.7 мс
Рецепт стенда — воспроизведи эти квитанции на любой VM

Подойдёт любой таймер, чей сервис логирует собственное время старта: ExecStart=/bin/sh -c 'echo timer-run $(date +%%s.%N) >>/var/lib/stamps.log' (экранируй %% в юнит-файлах). Погоняй один таймер OnCalendar=*:*:00/15 со стоковой точностью на простаивающей машине две минуты — посчитай срабатывания; затем добавь соседа *:*:00/2 и смотри, как метки встают на свои места. Сверяй срабатывания по отдельности через systemctl show your.timer -p LastTriggerUSec — он обновляется при каждом срабатывании. Переменные окружения TRIGGER_TIMER_REALTIME_USEC/_MONOTONIC_USEC в сервисе несут метку времени активации юнита-таймера, а не расписание конкретного срабатывания — для этого их не используй.

Замерено: одна свежая VM Arch (systemd 262, 262.1-1-arch, ядро 7.2.11-arch1-1) — один и тот же таймер *:*:00/15 в простое и рядом с занятым соседом, плюс прогоны с точностью 100 мс и 1 µs; квитанции — сырые отметки времени date +%s.%N, вставлены без правок. Проверку потолка journald в прошлонедельной статье «journalctl disk usage — vacuum — черпак, SystemMaxUse — кран» (2026-11-13) запускал таймер, в точности как в случае простоя, — вот так этот стенд и заработал место в расписании.

СТЕНД, ПРОВЕРЕННЫЙ ВРУЧНУЮ · systemd 262 (262.1-1-arch) · свежая VM Arch · последний тест 2026-11-14

Одна строка задаёт твой график — AccuracySec=1s. Одна квитанция это доказывает — LastTriggerUSec.