MAATRIX / Блог / Часы разошлись на две минуты и синхронизация перезаписала свежее старым

Часы разошлись на две минуты и синхронизация перезаписала свежее старым

MAATRIX

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

Как синхронизация решает, какая версия новее

Большинство инструментов синхронизации — от rsync без --checksum до rclone, Nextcloud-клиента, SMB и многих связок «сервер — бэкап-хранилище» — не сравнивают содержимое файлов побайтово при каждой сверке. Это дорого: чтобы посчитать контрольную сумму гигабайтного файла, нужно прочитать его целиком с диска на обеих сторонах. Вместо этого используется дешёвая эвристика: у каждого файла в файловой системе есть метаданные mtime — время последнего изменения содержимого, которое ядро ОС записывает при каждой операции записи, беря значение из системных часов в момент вызова.

Проверить это можно одной командой:

$ stat -c '%Y %n' report.docx
1756633920 report.docx

$ date -d @1756633920
Пт авг 29 14:32:00 UTC 2026

Дальше логика синхронизации проста: сравнить mtime (и обычно размер) одной и той же по имени пары файлов на источнике и приёмнике. Если mtime совпадает — файл не трогаем. Если отличается — побеждает тот, у кого mtime больше, то есть «более новый» с точки зрения часов. У rsync это явно видно во флаге --update («skip files that are newer on the receiver»): решение принимается строго по метке времени, без единого байта сравнения содержимого. Это быстро и в подавляющем большинстве случаев работает правильно — ровно до тех пор, пока часы на всех участниках синхронизации показывают одно и то же реальное время.

Откуда на самом деле берётся расхождение в пару минут

Расхождение в одну-три минуты — не экзотика, а типичная бытовая ситуация для инфраструктуры, где никто специально не следит за временем:

  • VPS без настроенного NTP. Аппаратные часы виртуальной машины дрейфуют относительно реального времени — на разных гипервизорах и при нагрузке на хост это может быть от долей секунды до заметных величин в сутки, и без демона синхронизации расхождение копится неделями.
  • Восстановление из снапшота или бэкапа. Виртуалка поднимается с часами, замороженными на момент снятия снимка, и если демон времени не запущен или синхронизируется редко, разница держится, пока кто-то не хватится.
  • Ноутбук, ушедший в сон. RTC (аппаратные часы) не всегда точно отслеживают время во сне, и на возврате из спящего режима часы могут «подвиснуть» до следующей синхронизации.
  • Закрытый исходящий трафик к пулу NTP. Если UDP 123 наружу заблокирован файрволом, демон времени не может достучаться до серверов, и часы свободно дрейфуют по внутреннему кварцевому генератору.
  • Ручная правка времени для отладки. date -s или смена времени в UI гипервизора для теста «а что будет через час» — и забыли вернуть обратно или перезапустить chronyd.
  • Смена летнего/зимнего времени и связанные с ней сбои — отдельная, но родственная тема: о том, что при этом происходит с плановыми задачами, есть отдельный разбор — что происходит с cron при переводе часов.

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

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

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

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

Механика катастрофы на конкретном примере

Возьмём условный пример — цифры здесь иллюстративные, чтобы показать сам механизм, а не результат измерений на реальном стенде. Есть два устройства с одним и тем же файлом config.yaml, между ними идёт двусторонняя синхронизация. На сервере A часы точны. На сервере B часы спешат на 5 минут — NTP там не настроен уже несколько недель.

Реальное времяСобытиеПоказания часов устройстваЗаписанный mtime
09:00Изменение на B (временный debug-флаг, который скоро станет ненужным)Часы B спешат на 5 мин09:05
09:03Настоящее важное исправление на A (корректная, действительно свежая версия)Часы A точны09:03
09:04Запускается плановая синхронизациясравнение mtime: 09:05 (B) vs 09:03 (A)

Формально mtime файла на B больше, чем на A — значит, с точки зрения синхронизации именно версия на B «свежее». Система переписывает A версией с B: устаревший debug-флаг замещает собой настоящее исправление, сделанное позже по факту, но с более ранней меткой времени из-за точных часов. Никакого предупреждения, никакой ошибки — просто тихая победа неверного времени над реальной хронологией событий. Заметить это можно только по косвенным признакам: пропавшее исправление, откатившееся поведение, «а мы же это точно правили».

Обратный вариант — отстающие, а не спешащие часы — даёт симметричный эффект: действительно новая версия получает заниженную метку и проигрывает сравнение уже существующей, устаревшей копии. Направление ошибки не важно — важно, что решение «что свежее» принимается по показаниям часов, а не по факту событий.

Где это бьёт больнее всего: инструменты и сценарии

Проблема не ограничивается одним инструментом — она встроена в саму идею «сравнивать по времени изменения», и всплывает везде, где эта идея применяется:

  • rsync без --checksum. Классический перенос конфигов или веб-контента между серверами по расписанию: если у источника и приёмника разошлись часы, --update может годами не докатывать реальные изменения, молча пропуская их как «уже не новее». О смежном классе проблем, когда rsync рапортует об успехе, а файл на приёмнике на самом деле другой, есть отдельный разбор: rsync отчитался об успехе, а файл на приёмнике другой.
  • rclone с политикой сравнения по mod-time между VPS и объектным хранилищем — тот же принцип: если часы облачного бакета (точные) и локального сервера (сбитые) расходятся, rclone sync может решить не перезаписывать устаревший объект, посчитав его «актуальнее».
  • Nextcloud/ownCloud-клиент и подобные двусторонние синхронизаторы. Обычно они создают конфликт-файл вместо тихой перезаписи, но какая из двух версий станет «основной», а какая — конфликт-копией с суффиксом, тоже решается по времени изменения. Конфликт-копии часто просто никто не открывает.
  • Общие тома по NFS/SMB между хостами с разным дрейфом часов. Клиенты кэшируют метаданные и полагаются на mtime при проверке актуальности кэша — рассинхронизация часов путает не только внешние синхронизаторы, но и саму файловую систему.
  • Скрипты деплоя, копирующие «только изменённые файлы». Если сервер выкладки на секунды или минуты отстаёт от сборочной машины, часть свежей выкладки может не докатиться — приложение обновится частично.
  • Дифференциальные бэкапы, использующие сравнение времени изменения вместо контрольных сумм для решения «нужно ли включать файл в новый инкремент» — при рассинхронизации часов свежие изменения могут быть пропущены как «уже учтённые».

Как защититься: синхронизация времени — это не эстетика логов

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

Практический минимум:

  • Держите NTP-клиент (chrony или systemd-timesyncd) запущенным как сервис, а не как разовую задачу по cron. Разовый запуск раз в сутки оставляет часы дрейфовать всё оставшееся время — а именно в этом окне и происходит рассинхронизация. Подробная настройка описана здесь: настройка NTP — синхронизация времени сервера.
  • Используйте один и тот же пул времени для всех участников синхронизации, где это возможно — расхождение между двумя серверами, которые оба точно синхронизированы с одним пулом, стремится к нулю, в отличие от ситуации «один настроен, другой — нет».
  • Проверяйте фактическое расхождение, а не факт того, что демон запущен:
$ chronyc tracking
Reference ID    : 5EC5C1CE (ntp1.example.net)
Stratum         : 3
System time     : 0.000412 seconds fast of NTP time
Last offset     : +0.000398 seconds

Ключевая строка — System time. Секунды — это норма; если там регулярно всплывают десятки секунд или минуты, значит синхронизация не справляется или не работает вовсе.

  • Заведите алерт на отклонение, а не полагайтесь на «раз настроили — значит, работает»: у node_exporter для Prometheus есть метрика node_timex_offset_seconds, у Zabbix — стандартный шаблон NTP-проверки. Порог в несколько секунд для боевой синхронизации — разумная точка тревоги.
  • Для действительно критичных данных не полагайтесь на mtime в принципе. Там, где цена ошибки высока, используйте сравнение по контрольной сумме (rsync -c, rclone --checksum) или инструменты, которые изначально не строятся на «last write wins» по времени — версионируемые хранилища, системы с явным конфликт-резолюшеном (тот же Syncthing с включённым сохранением конфликт-копий) или git-подобные механизмы, где решение принимается по содержимому, а не по часам.

Как обнаружить, что часы уже разошлись — и что уже могло пострадать

Если есть подозрение, что перезапись уже случилась, порядок действий такой:

  1. Сравните часы на всех участниках синхронизации прямо сейчас.
$ ssh server-a 'date -u +%s'; ssh server-b 'date -u +%s'

Даже разница в несколько секунд стоит зафиксировать — она подскажет, был ли дрейф системным или разовым.

  1. Проверьте статус службы времени на каждом хосте:
$ timedatectl timesync-status
$ chronyc tracking

Если chronyd не запущен или последняя синхронизация была давно — это прямое объяснение произошедшего.

  1. Поищите конфликт-копии, если инструмент их создаёт. Syncthing, Nextcloud-клиент и похожие системы часто не удаляют проигравшую версию полностью, а сохраняют её рядом с суффиксом вроде .sync-conflict-... — именно там может обнаружиться «потерянная» свежая версия.
  1. Посмотрите историю версионированного хранилища, если оно есть — ZFS-снапшоты, Borg/Restic-бэкапы или встроенное версионирование объектного хранилища иногда позволяют достать промежуточную версию файла и восстановить то, что было перезаписано.
  1. Если версионирования не было — это, к сожалению, весомый аргумент в пользу профилактики, а не лечения: без истории версий отличить, что именно и когда было потеряно, чаще всего невозможно.

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

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

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

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

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

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

Если синхронизация уже сравнивает контрольные суммы, а не только время — грозит ли эта проблема?

Нет, сравнение по хэшу содержимого не зависит от часов вообще. Проблема специфична именно для эвристики «сравнить по mtime и размеру», которую многие инструменты используют по умолчанию ради скорости.

Как быстро NTP выравнивает большое расхождение после того, как демон наконец запущен?

Зависит от настроек и величины расхождения: небольшие отклонения chrony обычно сглаживает постепенно (slew), не ломая монотонность времени для работающих процессов, а слишком большие — может скорректировать одномоментным скачком (step), в зависимости от конфигурации порогов. Точных цифр без замера на конкретной системе не дам — ориентируйтесь на chronyc tracking после запуска службы.

Это та же проблема, что и путаница с часовыми поясами?

Механизм смежный, но не тот же самый: здесь речь про абсолютное время (сбитые часы), а не про то, в каком поясе оно интерпретируется. Про вторую проблему — отдельный разбор: почему база и приложение спорят о часовом поясе.

Можно ли просто вручную поправить mtime файла, чтобы победить в сравнении?

Технически да, touch -d "2026-08-29 14:32:00" file изменит метку — но это лечит симптом на один раз и никак не защищает от следующей рассинхронизации. Если пользоваться этим регулярно как обходным путём, риск запутать историю изменений только растёт.

Достаточно ли синхронизировать время раз в сутки по cron?

Нет. Именно в промежутке между запусками часы успевают разойтись настолько, что становятся причиной описанной проблемы. Нужен постоянно работающий демон синхронизации, который непрерывно подстраивает часы, а не разовая коррекция.

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

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

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