惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
Help Net Security
Help Net Security
P
Privacy International News Feed
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
AWS News Blog
AWS News Blog
K
Kaspersky official blog
A
Arctic Wolf
Latest news
Latest news
T
Threat Research - Cisco Blogs
L
LINUX DO - 最新话题
P
Privacy & Cybersecurity Law Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Google DeepMind News
Google DeepMind News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
月光博客
月光博客
N
News and Events Feed by Topic
Jina AI
Jina AI
博客园 - 司徒正美
WordPress大学
WordPress大学
罗磊的独立博客
雷峰网
雷峰网
AI
AI
Hugging Face - Blog
Hugging Face - Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Security @ Cisco Blogs
博客园 - 三生石上(FineUI控件)
H
Heimdal Security Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cisco Blogs
博客园 - 【当耐特】
The Hacker News
The Hacker News
有赞技术团队
有赞技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Schneier on Security
Schneier on Security
博客园 - Franky
S
SegmentFault 最新的问题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Cloudbric
Cloudbric
爱范儿
爱范儿
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
Last Week in AI
Last Week in AI
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Know Your Adversary
Know Your Adversary
Google DeepMind News
Google DeepMind News

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Синхронизация часов — это кошмар
interpres (R · 2026-05-04 · via Все публикации подряд на Хабре

Синхронизация часов — это кошмар

Уровень сложностиСредний

Время на прочтение14 мин

Охват и читатели12

Обзор

Перевод

Кажется, что время — это просто. Но мы, инженеры, теряем сон из-за такой простой задачи, как синхронизация часов.

Причина этого в том, что не существует каких-то глобальных часов. У нас есть тысячи машин, распределённых по дата-центрам, континентам и часовым поясам; каждая из них работает независимо от других, поэтому ответ на простой вопрос «сколько сейчас времени?» оказывается на удивление сложным.

Синхронизация часов становится основой самых сложных задач в распределённых системах, она влияет на всё, от согласованности баз данных и отладки до финансовых транзакций.

Иллюзия точного времени

У каждого компьютера есть внутренние часы, обычно управляемые кварцевым резонатором. При подаче напряжения на эти резонаторы они колеблются на определённой частоте. Для большинства компьютерных часов стандартом выбрана частота 32768 Гц, потому что она является степенью двойки и упрощает подсчёт с точностью до одной секунды.

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

Самый важный — это температура. У стандартных кварцевых кристаллов дрейф частот при изменении температуры может составлять до десятков частей на миллион. Изменение температуры примерно на 10 градусов Цельсия может вызывать дрейф, эквивалентный примерно 110 секундам в год. В зависимости от температуры окружающей среды кристалл колеблется быстрее или медленнее, а окружающая среда в дата-центрах контролируется неидеально.

Ещё один фактор — различия при производстве. Невозможно найти двух идентичных кристаллов. Кристаллы даже из одной производственной партии будут иметь немного различающиеся характеристики. Усугубляет эту проблему старение, потому что свойства кристаллов со временем меняются.

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

Почему рассинхронизация часов приводит к поломкам

Рассинхронизация — это разница во времени двух часов в конкретный момент времени. Дрейф — это скорость, с которой показания часов расходятся со временем. Оба этих фактора вызывают серьёзные проблемы в распределённых системах.

Рассмотрим простой пример с распределённой системой make. Вы редактируете файл исходников на своей клиентской машине, часы которой слегка отстают от часов сервера, где находятся скомпилированные объектные файлы. При выполнении make она сравнивает временные метки. Если часы сервера опережают часы клиента, то кажется, что объектный файл новее, чем отредактированный вами файл исходников, и make не выполнит повторную компиляцию. Ваши изменения незаметно пропадут из сборки.

Сценарий: make UNIX с рассинхронизованными часами

Часы на клиентской машине: 10:00:00 (отстают)
Часы на сервере: 10:00:05 (опережают)

1. Редактируем util.c во время клиента 10:00:00
2. util.o на сервере имеет временную метку 10:00:03
3. Make выполняет сравнение: util.o (10:00:03) и util.c (10:00:00)
4. Вывод: util.o новее, повторная компиляция не выполняется
5. Результат: ваши изменения игнорируются

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

Представьте банковскую систему, в которой клиент кладёт деньги на депозит в одном отделении (Узел A) и мгновенно снимает их в другом (Узел Б). Если часы Узла Б отстают от часов Узла A, транзакция снятия может получить временную метку раньше, чем транзакция депозита. Снэпшот, сделанный в неподходящее время, может отразить только снятие, но не депозит, из-за чего может показаться, что клиент снял деньги, которых у него не было.

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

Физическая синхронизация часов

Простейшее решение для синхронизации часов заключается в периодической отправке запросов надёжному серверу времени и соответствующей настройке локальных часов. Давайте рассмотрим различные алгоритмы и решения, основанные на нём.

Алгоритм Кристиана

Предложенный в 1989 году алгоритм Кристиана работает с централизованным сервером времени, который считается источником точного времени. Клиент запрашивает время, сервер отправляет ему ответ с текущим временем, и клиент настраивает свои часы.

Сложность заключается в сетевых задержках. Ко времени прибытия ответа время сервера устареет. В алгоритме Кристиана задержка в одну сторону принимается равной половине времени передачи данных туда-обратно.

# Алгоритм Кристиана
def synchronize_clock():
    t0 = local_time()           # Записываем время перед запросом
    server_time = request_time_from_server()
    t1 = local_time()           # Записываем время после запроса
    
    round_trip = t1 - t0
    one_way_delay = round_trip / 2
    
    # Меняем показания локальных часов
    new_time = server_time + one_way_delay
    set_local_clock(new_time)
    
    # Пределы погрешности: +/- (t1 - t0) / 2

Когда сетевые задержки симметричны (то есть когда для запроса и ответа требуется одинаковое время), такая система работает относительно неплохо. Однако на практике задержки часто бывают асимметричными из-за разных путей маршрутизации, варьирующейся загруженности сети и задержек при обработке.

Алгоритм Беркли

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

Специальный демон времени периодически опрашивает значения часов у всех машин. Он вычисляет среднее, отсекает выбросы и сообщает каждой машине, на сколько ей нужно исправить показания. Вместо того, чтобы посылать абсолютные значения времени, на которые могут влиять сетевые задержки, он передаёт относительные изменения.

Этапы алгоритма Беркли:
1. Демон времени опрашивает машины: "Сколько у вас сейчас времени?"
2. Ответы: Машина A: 10:00:05, Машина Б: 10:00:02, Машина В: 10:00:08
3. Показания часов демона времени: 10:00:04
4. Среднее: (5 + 2 + 8 + 4) / 4 = 4.75 → 10:00:05
5. Отправляемые исправления:
   - Машина A: замедлись на 0 с (уже имеет нужное время)
   - Машина Б: ускорься на 3 с
   - Машина В: замедлись на 3 с
   - Демон: ускорься на 1 с

Очень важная деталь: компьютеры никогда не должны переводить свои часы назад скачком. Это нарушило бы допущение о монотонном времени, от которого зависят многие алгоритмы. Для синхронизации алгоритм Беркли вместо перемотки постепенно замедляет часы.

Network Time Protocol

В NTP используется иерархическая модель серверов времени, упорядоченных в страты.

Устройства страты 0 — это высокопрецизионные источники времени, например, атомные часы и GPS-приёмники. Серверы страты 1 соединены непосредственно с источниками страты 0. Каждая более низкая страта синхронизируется с уровнем выше; страт может быть до пятнадцати.

В публичном Интернете NTP обычно поддерживает точность времени в пределах десятков миллисекунд, а в локальных сетях может достигать погрешности меньше миллисекунды. Однако его точность ограничивает множество факторов.

Пределы точности NTP:
- Публичный Интернет: обычно 10-100 мс
- LAN с хорошими условиями: 100-500 мкс
- Асимметрия сети: может вызывать погрешности до 100+ мс
- Переменные задержки: добавляют неустойчивость
- Задержки операционных систем: временные метки ПО добавляют микросекунды

Особенно проблематична асимметрия сети. Если путь от клиента до сервера отличается от пути от сервера до клиента, то нарушается допущение о том, что односторонняя задержка равна половине времени пути туда-обратно. Классический пример этого — спутниковая связь, при которой восходящий и нисходящий каналы имеют разные задержки.

Оверхед операционных систем добавляет неопределённость. При поступлении пакета NTP он проходит через сетевой стек, получает временную метку ядра, а затем достигает демона NTP. Каждый этап добавляет задержки переменной величины в микросекундах.

Миллисекунд недостаточно

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

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

Телекоммуникационным системам синхронизация требуется для TDM (Time Division Multiplexing), при котором разные пользователи по очереди занимают общий канал. В случае дрейфа таймингов происходит конфликт передач от разных пользователей.

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

Precision Time Protocol

PTP, определённый в IEEE 1588, достигает точности меньше микросекунды благодаря аппаратному добавлению временных меток. Вместо того, чтобы время прибытия пакета записывала операционная система, специализированный сетевой интерфейс ставит временные метки на пакеты при получении, устраняя программные задержки.

Сравнение точности PTP и NTP:
- NTP: миллисекунды (программные временные метки)
- PTP: наносекунды (аппаратные временные метки)

Основные преимущества PTP:
- Аппаратные временные метки на уровне NIC
- Граничные часы в коммутаторах, поддерживающие точность
- Двухсторонний обмен сообщениями вычисляет асимметричные задержки

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

В 2022 году компания Meta*объявила о миграции с NTP на PTP в своих дата-центрах. Инвестиции в инфраструктуру PTP оправдали себя снижением погрешностей и расширением возможностей отладки.

Логические часы и причинно-следственные связи

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

События, не объединённые причинно-следственной связью, считаются конкурентными. Они могли произойти в любом порядке, и, с точки зрения системы, его невозможно определить.

Временные метки Лэмпорта

Это наблюдение реализовано в простом алгоритме временных меток Лэмпорта. Каждый процесс имеет свой счётчик. Перед любым событием он увеличивает значение счётчика. При отправке сообщения в него включается значение счётчика. При получении сообщения счётчик устанавливается на максимальное значение из текущего и полученного, а затем выполняется инкремент.

class LamportClock:
    def __init__(self):
        self.time = 0
    
    def local_event(self):
        self.time += 1
        return self.time
    
    def send_event(self):
        self.time += 1
        return self.time  # Включается в сообщение
    
    def receive_event(self, received_time):
        self.time = max(self.time, received_time) + 1
        return self.time

Если временная метка Лэмпорта у события А меньше, чем у события Б, это значит одно из двух: или А произошло до Б, или они конкурентны. Обратное гарантировано: если A произошло до Б, То временная метка А меньше, чем у Б.

Процесс P1: [0] ---(1)---> отправка m ---(2)---> локальное событие
                    |
                    v
Процесс P2: [0] --------> получение m ---(2)---> локальное событие ---(3)

События P1: (1, 2)
События P2: (2, 3)
Получение на P2 происходит после отправки на P1 (причинно-следственная связь сохранена)

Ограничение заключается в том, что временные метки Лэмпорта не могут сообщить нам, конкурентны ли два события. События с временными метками 5 и 7 могут иметь причинно-следственную связь или произойти независимо в разных процессах без коммуникации между ними.

Векторные часы

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

class VectorClock:
    def __init__(self, process_id, num_processes):
        self.id = process_id
        self.clock = [0] * num_processes
    
    def local_event(self):
        self.clock[self.id] += 1
        return self.clock.copy()
    
    def send_event(self):
        self.clock[self.id] += 1
        return self.clock.copy()
    
    def receive_event(self, received_clock):
        for i in range(len(self.clock)):
            self.clock[i] = max(self.clock[i], received_clock[i])
        self.clock[self.id] += 1
        return self.clock.copy()
    
    @staticmethod
    def compare(vc1, vc2):
        less = any(vc1[i] < vc2[i] for i in range(len(vc1)))
        greater = any(vc1[i] > vc2[i] for i in range(len(vc1)))
        
        if less and not greater:
            return "vc1 happened before vc2"
        elif greater and not less:
            return "vc2 happened before vc1"
        elif not less and not greater:
            return "equal"
        else:
            return "concurrent"

Сравнение двух векторных часов точно сообщает нам, произошло ли одно событие до другого или конкурентны ли они. Если каждая запись в VC1 меньше или равна соответствующей записи в VC2, и хотя бы одна строго меньше, то VC1 произошло до VC2.

Пример векторных часов (3 процесса: P1, P2, P3):

P1: [1,0,0] ---> [2,0,0] ---отправка m---> [3,0,0]
                              |
P2: [0,1,0] ---------> [0,2,0] ---получение m---> [3,3,0]
                              
P3: [0,0,1] ---> [0,0,2]

Сравниваем [2,0,0] и [0,0,2]:
- 2 > 0 (первый элемент)
- 0 < 2 (третий элемент)
Результат: КОНКУРЕНТНОСТЬ (причинно-следственная связь отсутствует)

Сравниваем [2,0,0] и [3,3,0]:
- 2 < 3 (первый элемент)
- 0 < 3 (второй элемент)
Результат: [2,0,0] произошло до [3,3,0]

Недостаток векторных часов — оверхед занимаемого места. При N процессах каждая временная метка требует O(N) пространства. В случае больших распределённых систем с тысячами узлов такая система перестаёт быть практичной. Существуют различные её оптимизации, в том числе сжатые векторные часы и часы деревьев интервалов, однако фундаментальная проблема масштабирования никуда не пропадает.

Google Spanner и TrueTime

В беспрецедентных масштабах с проблемой синхронизации часов Google столкнулась при работе с глобальной распределённой базой данных Spanner. Компании требовались надёжные гарантии согласованности между дата-центрами на разных континентах, для чего необходимо было знать порядок транзакций.

Я объяснял это в своём видео.

Решением компании стала TrueTime — глобальная распределённая инфраструктура часов, сообщающая время с ограниченной неопределённостью.

Для каждого дата-центра TrueTime использует два типа источников времени. GPS-приёмники получают время непосредственно со спутников. Атомные часы обеспечивают резервирование и перекрёстную валидацию. Использование обоих типов обеспечивает надёжность, поскольку они имеют независимые режимы отказа. Сбой GPS может быть вызван проблемами с антеннами или помехами сигнала. У атомных часов может возникать дрейф, но на них не влияют те же проблемы.

Ключевой инновацией стало то, что TrueTime возвращает не одну временную метку, а интервал, гарантированно содержащий истинное время.

- TT.now() возвращает [earliest, latest]
  - Истинное время гарантированно будет находиться внутри этого интервала
  - Ширина интервала - это предел неопределённости ε
  
- TT.after(t) возвращает true, если t точно находилось в прошлом
- TT.before(t) возвращает true, если t точно находится в будущем

Типичная степень неопределённости: 1-7 миллисекунд

При коммите транзакции Spanner она получает временную метку. Перед тем, как сообщить о коммите клиенту, Spanner ждёт, пока TT.after(commit_timestamp) не вернёт true. Это «ожидание коммита» гарантирует, что любая последующая транзакция, начавшаяся после ожидания, получит более позднюю временную метку.

Ожидание коммита Spanner:

1. Транзакция T1 подготавливается к коммиту
2. T1 получает временную метку коммита ts = TT.now().latest
3. T1 ждёт, пока TT.after(ts) не вернёт true
4. T1 сообщает о коммите клиенту

Почему это работает:
- После ожидания мы точно знаем, что ts произошла в прошлом
- Любая транзакция T2, начинающаяся после этой точки,
  получит временную метку > ts
- Следовательно: коммит T1 выполнен до начала T2 → ts(T1) < ts(T2)
- Внешняя согласованность сохранена

В худшем случае период ожидания обычно примерно равен 7 миллисекундам. Кажется, что это существенное повышение задержек, но на самом деле оно накладывается на обработку других коммитов, а высокая степень согласованности в случае Spanner оправдывает затраты.

Гибридные логические часы

Не у всех есть ресурсы Google, позволяющие установить атомные часы в каждом дата-центре. CockroachDB, вдохновлённая Spanner, но предназначенная для работы на обычном оборудовании, использует гибридные логические часы (hybrid logical clock, HLC).

У меня был подкаст с ведущим архитектором CockroachDB Беном Дарнеллом, где мы подробно обсудили эту тему.

HLC комбинирует физическое время с логическим компонентом. Физическая часть остаётся близкой к тому, что показывают физические часы. Логическая часть обрабатывает случаи, когда множественные события происходят в пределах одного такта физических часов или когда рассинхронизация часов вызывает проблемы.

class HybridLogicalClock:
    def __init__(self):
        self.physical = 0  # Физическое время
        self.logical = 0   # Логический счётчик
    
    def now(self, wall_time):
        if wall_time > self.physical:
            self.physical = wall_time
            self.logical = 0
        else:
            self.logical += 1
        return (self.physical, self.logical)
    
    def receive(self, wall_time, received_physical, received_logical):
        if wall_time > self.physical and wall_time > received_physical:
            self.physical = wall_time
            self.logical = 0
        elif received_physical > self.physical:
            self.physical = received_physical
            self.logical = received_logical + 1
        elif self.physical > received_physical:
            self.logical += 1
        else:  # Равные показания физического времени
            self.logical = max(self.logical, received_logical) + 1
        return (self.physical, self.logical)

HLC обладает свойством близости временных меток к физическому времени, что упрощает отладку. Взглянув на временную метку HLC, можно приблизительно понять, когда произошло событие. Логический компонент гарантирует всегда правильный порядок событий, находящихся в причинно-следственной связи, даже в случае рассинхронизации физических часов.

Физические часы Узла А: 100
Физические часы Узла Б: 98 (немного отстают)

Событие Узла А: HLC = (100, 0)
Узел А отправляет сообщение Узлу Б

Узел Б получает его в своё физическое время 99:
- Полученное HLC = (100, 0)
- Локальное физическое время = 99
- Так как 100 > 99: новое HLC = (100, 1)

Событие Узла Б корректно располагается после события Узла А, несмотря на отставание
физических часов Узла Б.

CockroachDB применяет HLC с конфигурируемым максимальным смещением часов; обычно в локальном окружении оно равно 500 миллисекундам. Если узел обнаруживает, что его часы слишком далеки от остальных, то он удаляет себя из кластера вместо того, чтобы рисковать нарушением согласованности.

YugabyteDB тоже использует HLC и недавно интегрировалась с AWS Time Sync Service для более плотной синхронизации. Бенчмарки разработчиков показали снижение задержек вплоть до троекратного при транзакциях с использованием прецизионных источников времени, потому что благодаря малым границам неопределённости время ожидания при транзакциях снижается.

Как выбрать подходящую систему

Выбор детализированности синхронизации

Какой уровень синхронизации нужен вам на самом деле? Многим системам вполне подходит NTP, обеспечивающий миллисекундную точность. Если ваши транзакции и так требуют десятки миллисекунд, рассинхронизация часов на менее чем миллисекунду не важна.

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

В случае баз данных, требующих внешней согласованности, или систем, для которых важно физическое время (логи аудитов, финансовые записи), следует инвестировать в улучшение инфраструктуры синхронизации. Оборудование PTP, выделенные серверы времени и продуманная сетевая архитектура могут обеспечить точность в пределах микросекунд.

Работа с аномалиями часов

Часы могут перепрыгивать назад. NTP может внезапно исправить часы, дрейф которых был слишком большим. Миграции виртуальных машин могут вызвать разрывы в показаниях часов. Баги операционных систем могут приводить к скачкам времени.

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

class SafeClock:
    def __init__(self):
        self.last_time = 0
        self.offset = 0
    
    def now(self):
        system_time = get_system_time()
        
        if system_time < self.last_time:
            # Часы скакнули назад!
            # Регулируем смещение для обеспечения монотонности
            self.offset += (self.last_time - system_time) + 1
            log_warning("Clock jumped backward")
        
        result = system_time + self.offset
        self.last_time = result
        return result

Работа с високосной секундой

Иногда в течение времени вставляются високосные (корректировочные) секунды для согласования UTC с вращением Земли. Минута с високосной секундой содержит 61 секунду. Многие системы плохо справляются с такой ситуацией.

Високосная секунда: 2016-12-31 23:59:60 UTC

Сценарии возникновения проблем:
- Вылет систем, предполагающих, что в минутах всегда по 60 секунд
- При планировании на основе времени создаются дубликаты событий
- В логах появляются невозможные временные метки

Решение:
- Размазывание високосной секунды: постепенное регулирование в течение нескольких часов (Google, AWS)
- Пошаговая регулировка: изменение часов скачком (традиционный NTP)
- Игнорирование: применение TAI вместо UTC (в специализированных системах)

Google и AWS используют «размазывание високосной секунды», при котором эта секунда распределяется на долгий период времени, из-за его каждая секунда становится немного длиннее или короче. Это позволяет избежать разрывов, но часы в период размазывания не будут точно соответствовать UTC.

Радостная новость заключается в том, что Международное бюро мер и весов решило к 2035 году перестать добавлять високосные секунды, что позволяет устранить эту проблему в системах будущего.

Фундаментальный компромисс

Синхронизация часов в распределённых системах всегда требует компромисса между точностью, задержками и сложностью.

Плотная синхронизация требует или качественного оборудования (атомных часов, GPS, коммутаторов с поддержкой PTP), или дополнительного оверхеда коммуникаций (частых сообщений синхронизации, протоколов обеспечения консенсуса). За оба эти решения приходится расплачиваться.

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

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

Какого-то единого правильного решения нет, оно зависит от ваших требований, бюджета и допустимой сложности.

Заключение

Синхронизация часов — интересная и сложная задача, решения которой находятся в спектре от простого NTP до инфраструктуры атомных часов Google для глобальных баз данных.

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