Три дня я искал баг в коде — оказалось, сервер жил в другом часовом поясе
Есть баги, которые тихо портят данные и обнаруживаются через месяцы. А есть такие, которые существуют годами, всех раздражают, но никто не может их воспроизвести — потому что они зависят от часового пояса.
Та система занималась планированием задач. Пользователь создаёт задачу на «завтра в 10:00» — система её выполняет в нужное время. Казалось бы, простейшая логика.
Жалобы начались в марте. Клиенты из Екатеринбурга писали: «задачи выполняются не вовремя, иногда с опозданием на два часа». Я смотрел на логи — по нашим данным задачи выполнялись точно в срок. Поддержка закрывала тикеты: «не воспроизводится». Клиенты злились.
Я взялся за это в начале апреля. Провёл два дня, читая код планировщика. Нашёл несколько мест где работа с временем выглядела подозрительно, отрефакторил — баг остался. Третий день — ревью всех datetime операций в системе. Ничего.
На четвёртый день Игорь из поддержки сказал фразу, которая сразу всё объяснила:
— Слушай, я заметил, что жалуются только пользователи из Екатеринбурга, Омска и вот этих городов...
Он показал список. Я посмотрел на карту. Это были города в часовых поясах UTC+5 и восточнее.
Зашёл на продакшн-сервер. Ввёл date. Увидел: Thu Apr 4 14:23:11 UTC 2024. Сервер работал в UTC. Это нормально. Проблема была в другом: когда пользователь из Екатеринбурга (UTC+5) создавал задачу на «завтра 10:00» через браузер, фронтенд отправлял строку 2024-04-05 10:00:00 — без timezone offset. Backend принимал её как UTC и сохранял в базу. Потом выполнял задачу в 10:00 UTC — что для пользователя в UTC+5 было 15:00 по местному времени.
Пять часов разницы. Это проявилось только когда начали приходить пользователи из регионов — потому что первые клиенты и команда разработки были из Москвы (UTC+3), и у них разница была три часа, а не пять.
Фикс: фронтенд теперь всегда отправляет datetime с timezone offset. Backend парсит только aware datetime объекты. На Хабре есть статья «Никогда не используйте naive datetime» — я её знал. Просто не я писал тот фрагмент. Теперь у нас есть линтер-правило, которое не даёт мержить код с naive datetime объектами.
Recommended Comments