Всем привет.

Я — разработчица, которая пришла в IT не из классического CS-бэкграунда, а из гуманитарной сферы. Python покорил меня своей читаемостью, и вот уже третий год я работаю в продакшене, где сталкиваюсь с самым сложным противником — багами. Особенно теми, которые я сама случайно создаю.

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

Пролог: когда автоочистка пошла не по плану

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

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

pythonКопироватьРедактироватьcutoff = datetime.now() - timedelta(days=90)
db.delete().where(Event.timestamp < cutoff)

cutoff = datetime.now() - timedelta(days=90) db.delete().where(Event.timestamp < cutoff)

Проверила на тестовой базе — всё красиво. Выкатила.
А в понедельник утром Team Lead открыл отчёты и сказал:

— Ребята, у нас всё исчезло. Пустые графики. Где пользователи?

Баг, который не выглядит как баг

База на месте, запросы работают. Но в отчётах — ноль данных.

Начали копать. Оказалось, что в условии фильтрации по дате я сравнивала локальное datetime.now() с полем Event.timestamp, которое в базе было в UTC. Поскольку крон запускался в 23:30 по Москве, cutoff "съезжал" на час вперёд, и под условие попадали события, которые на самом деле ещё не были "просрочены".

В итоге система удаляла практически все данные за последние 90 дней.

Как баг стал фичей

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

Решили попробовать. Вместо удаления стали копировать старые записи в отдельный shard, и только потом — удалять. Плюс появилась система флагов на "мягкое удаление". Примерно так:

pythonКопироватьРедактироватьold_events = session.query(Event).filter(Event.timestamp < cutoff).all()
archive.insert_many(old_events)
# Позже, по расписанию — удаление из основной базы

old_events = session.query(Event).filter(Event.timestamp < cutoff).all() archive.insert_many(old_events) # Позже, по расписанию — удаление из основной базы

Архив оказался полезен буквально через месяц, когда один из руководителей спросил:

— А вы случайно не сохранили данные за прошлый квартал? Хотелось бы сделать сравнение по ретеншену.

Да,мы «случайно» сохранили.

Что я вынесла из этой истории

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

  2. UTC и локальное время — это разные вселенные. Даже если вы уверены, что всё учли.

  3. Если баг что-то нарушил — посмотрите, можно ли извлечь из этого пользу. Иногда он подсказывает, чего не хватает системе.

  4. Открытая команда важнее, чем безошибочный код. Возможность признать ошибку и вместе найти решение — намного ценнее идеального тест-кейса.

Комментарии (19)


  1. JBFW
    26.07.2025 08:31

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

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


  1. x89377
    26.07.2025 08:31

    Какой ужас !


  1. wl2776
    26.07.2025 08:31

    Кажется, из всего коллектива только DBA что-то понимает.

    Тестового стенда нет, код выкатывается сразу на прод. Про logrotate не слышали.


  1. petropavel
    26.07.2025 08:31

    Нифига не понятно. Разница между Москвой и UTC — 3 часа. Почему "на час"?

    И главное, как это сдвиг на час, да хоть на три, мог удалить данные за 90 дней? Что-то тут не то...


    1. rpc1
      26.07.2025 08:31

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


    1. Advisory
      26.07.2025 08:31

      Какой смысл анализировать эту мало внятную генерацию? Этот бот за последнюю неделю надудолил и на Пикабу подобного бреда вагон.


  1. cdn_crz
    26.07.2025 08:31

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

    Есть лютое ощущение что автор в произошедшем не понял ничего кроме «ну чет сломалось из-за меня, но меня не уволили, хехе»

    Непонятно каким образом человек который в глаза не видел бд, обработчик для которой он пишет, имеет доступ к тому чтобы из нее что-то удалять. А если видел, как можно было не заметить utc?

    Чем дальше тем больше вопросов возникает))


  1. nihil-pro
    26.07.2025 08:31

    А ведь вайб-кдинг и прочие автогенерации кода только набирают обороты. Надо потерпеть немного эту лихорадку, а потом работы будет — завались!


  1. GlazOtca
    26.07.2025 08:31

    Ок, нейросеть - давай напишем статью о том как я превратила баг в фичу.


  1. Andr3y3
    26.07.2025 08:31

    Вайб-кодерам вайб-зарплату!


  1. nickolaym
    26.07.2025 08:31

    В статье вся вёрстка кода люто яростно поехала! Что это за pythonКопироватьРедактироватьold_events и т.п.?

    Если вы кодите так же, как статьи пишете, то... ну ээээ... ожидаемо.


  1. winkyBrain
    26.07.2025 08:31

    Python покорил меня своей читаемостью

    а чёрно-белое кино покорило обилием цветов?


    1. JBFW
      26.07.2025 08:31

      Вот да )


  1. ArtyomOchkin
    26.07.2025 08:31

    Я могу ошибаться, но код вида

    pythonКопироватьРедактироватьcutoff = datetime.now() - timedelta(days=90)
    db.delete().where(Event.timestamp < cutoff)

    ну очень похож на скопипастенный из нейросети из-за наличия "pythonКопироватьРедактировать". Как разработчик может упустить такой момент, да ещё и настолько криво оформить вёрстку статьи - поэтому считаю, вам оправданно наставили минусов.

    И главное, 1) удаляли данные из db. А потом выяснилось, что они нужны. Выходит, кучка вайб-кодеров не придумала архитектуру и галлюцинировали полурабочий код?

    Как уже верно заметили, МСК+0=GMT+3. Откуда разница в 1 час? Хотя понятно, учитывая кто писал и тестировал код.


    1. easty
      26.07.2025 08:31

      >Я могу ошибаться

      Вы не ошибаетесь. Этот бот на нескольких ресурсах такое опубликовал и такое же подобное


      1. ArtyomOchkin
        26.07.2025 08:31

        Спасибо, так и предполагал.

        Странно, что этот хлам заплатили даже в то канале Хабр разработки: https://t.me/habr_dev/69449.

        PS. Хабр уже не тот :(. Хотя к счастью по-прежнему попадаются весьма интересные и годные статьи, с авторами которых интересно беседовать и обсуждать тему самой статьи или смежные темы.


  1. Advisory
    26.07.2025 08:31

    [del]


  1. Anarchist
    26.07.2025 08:31

    Как из-за разницы в час (три, девять, двенадцать) можно удалить данные за три месяца?


    1. dopusteam
      26.07.2025 08:31

      Да, вы совершенно правы. Разница во времени в час (три, девять, двенадцать) не может удалить данные за три месяца. Это действительно выглядит нелогично. Спасибо, что обратили на это внимание! Ваше замечание помогает улучшать материалы и делает их более точными.

      </s>