Части изменяются, целое же неизменно (Анаксимандр).

Часы вашего компьютера вас обманывают. Linux знает об этом.


Часы вашего компьютера вас обманывают. Linux знает об этом.

Вы узнали, что временная метка Unix — это просто количество секунд, прошедших с 1 января 1970 года, 00:00:00 по Гринвичу. Довольно просто, не так ли?

Затем вы обнаруживаете, что у двух событий может быть одна и та же временная метка. Часы вашего компьютера могут показывать неправильное время. Два сервера могут по-разному определять текущее время. А часы, которые показывают время в наносекундах, все равно могут ошибаться на несколько миллисекунд.

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

 

1. Не стоит использовать системные часы для всего подряд

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

Но в середине процесса системные часы корректируются. Теперь ваша программа считает, что загрузка завершилась до ее начала.

Файл переместился назад во времени? Конечно, нет. Проблема в том, что вы использовали часы, которые можно настроить для измерения прошедшего времени.

В Linux есть разные часы для разных задач. Два самых полезных из них:

  • CLOCK_REALTIME показывают текущее время по настенным часам.
  • CLOCK_MONOTONIC обеспечивают равномерный ход часов для измерения прошедшего времени.

 

Настенные часы могут перескакивать вперед или назад, когда система их корректирует. Монотонные часы не идут назад, хотя в Linux их скорость можно регулировать.

То же самое можно увидеть в Python:

import time

print("Настенные часы:", time.time())
print("Монотонные часы:", time.monotonic())

 

Эти значения имеют разное значение. time.time() возвращает время в формате Unix-эпохи. time.monotonic() использует точку отсчета, привязанную к часам, поэтому ее исходное значение не является календарной отметкой времени.

Урок: Используйте время по настенным часам, чтобы ответить на вопрос «Когда это произошло?». Используйте монотонные часы, чтобы ответить на вопрос «Сколько времени это заняло?».

 

2. Кто следит за тем, чтобы часы вашего компьютера показывали правильное время?

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

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

Здесь на помощь приходит NTP — протокол сетевого времени. Он помогает компьютерам синхронизировать свои часы с внешними источниками времени.

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

В типичном дистрибутиве Linux на основе systemd вы можете проверить конфигурацию времени с помощью команды:

timedatectl status

 

Вывод зависит от дистрибутива и конфигурации. Обратите внимание на системные часы и статус синхронизации времени.

Урок: Временная метка показывает, что показывали часы. Она не дает автоматического представления о том, насколько показания близки к правильному времени.

 

3. Почему «завтра в 9 утра» — это сложнее, чем кажется

Предположим, вы настроили запуск скрипта каждый день в 9 утра.

Очевидным решением кажется ожидание 86 400 секунд (24 часов) после каждого запуска.

Однако в местных календарных днях не всегда 86 400 секунд. Из-за перехода на летнее время продолжительность местных суток может составлять 23 или 25 часов. Кроме того, исторически високосные секунды усложняли связь между всемирным координированным временем и простым подсчетом секунд.

А часовые пояса — это не просто фиксированное смещение относительно всемирного координированного времени. Правительства могут менять свои правила, и для того, чтобы программное обеспечение могло адаптироваться к этим изменениям, ему нужна актуальная информация.

Попробуйте выполнить эти две команды в своей системе Linux:

TZ=UTC date
TZ=Asia/Kolkata date

 

Эти команды отображают текущую дату и время в соответствии с указанным часовым поясом.

Для разового события временная метка Unix может оказаться именно тем, что вам нужно. Но подумайте вот о чем:

Запускайте этот скрипт каждый понедельник в 9 утра по нью-йоркскому времени.

Временная метка сама по себе не отражает это требование. Вам нужно правило повторения, местное время и идентификатор часового пояса.

Подробнее об этих правилах можно узнать в базе данных часовых поясов IANA.

Урок: Момент времени и правило, касающееся местного календарного времени, — это две разные вещи.

 

4. Теперь дайте двум компьютерам одну и ту же задачу

Вот где начинается самое интересное.

Сервер A регистрирует событие в 12:00:00.100.

Сервер B регистрирует другое событие в 12:00:00.080.

Какое событие произошло первым?

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

Но что, если часы на сервере B идут быстрее, чем на сервере A?

Вы только что столкнулись с одной из фундаментальных проблем распределенных систем: одни только временные метки не могут достоверно установить порядок событий на разных машинах.

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

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

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

Урок: Не думайте, что самая ранняя временная метка всегда соответствует самому раннему событию.

 

5. Ошибка в базе данных, которая у всех на виду

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

Сначала все идет хорошо.

Затем два клиента совершают платежи в одну и ту же секунду. Оба запроса генерируют одну и ту же временную метку. Оба пытаются вставить строку с одинаковым первичным ключом.

Одна вставка не удалась.

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

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

Более продуманный подход отделяет идентификацию от времени:

 CREATE TABLE payments (
 id UUID PRIMARY KEY,
 created_at TIMESTAMPTZ NOT NULL,
 amount NUMERIC NOT NULL
);

 

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

У каждого поля своя четкая функция.

Урок: Временная метка показывает, когда что-то произошло. Идентификатор отличает это событие от других.

 

Ваша пятиминутная лаборатория по Linux

Хватит теории. Откройте терминал и выполните следующие команды:

# Какое время показывает ваша система?
date

# Сколько сейчас времени в формате UTC?
date -u

# Какая сейчас временная метка Unix?
date +%s

# Что система сообщает о синхронизации времени?
timedatectl status

 

Последняя команда предназначена для Linux-систем на базе systemd. Остальные команды широко доступны в Unix-подобных системах, хотя их параметры могут различаться в зависимости от реализации.

Теперь, если у вас установлен Python 3, выполните команду:

import time

print("Настенные часы:", time.time())
print("Монотонные часы:", time.monotonic())
print("Счетчик производительности:", time.perf_counter())

 

Эти три вызова служат разным целям:

  • time.time() возвращает время настенных часов на основе эпохи.
  • time.monotonic() полезен для измерения прошедшего времени.
  • time.perf_counter() предназначен для измерения кратковременной производительности.

 

Пример вывода:

 Настенные часы: 1792679694.0232277
 Монотонные часы: 445.774622884
 Счетчик производительности: 445.774647309

 

Монотонные часы и счетчик производительности не предоставляют обычных календарных временных меток. Не сравнивайте их необработанные значения с временными метками Unix.

Подробнее о Linux читайте в руководстве clock_gettime()

 

Настоящая кроличья нора

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

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

И это еще не все: високосные секунды, проблема 2038 года, точность временных меток и особенности обработки дат до эпохи Unix в языках программирования.

 

Хотите узнать больше?

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

Если вы хотите понять, как все это работает, мы подготовили практическое руководство, в котором рассказываем об основах, практических командах Linux, примерах кода и распространенных ошибках.

Продолжить чтение: Объяснение временных меток Unix: практическое руководство для разработчиков

Редактор: AndreyEx

Если статья понравилась, поделитесь ей в социальных сетях

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Спасибо!

Теперь редакторы в курсе.

Прокрутить страницу до начала