> ## Content Index
> Fetch the complete content index at: https://www.rozhkov.me/llms.txt
> Use this file to discover other available public pages before exploring further.

# Страхи збуваються
- URL: https://www.rozhkov.me/fear-became-true/
- Published: 2024-12-18T07:24:10.000Z
- Updated: 2024-12-18T07:24:10.000Z
- Author: Vova Rozhkov
- Tags: donate1024, кулсторі

Телеграм-бот [@Donate1024Bot](https://t.me/Donate1024Bot) щоранку розсилає всім підписникам новий збір. Щоб уникнути випадкового подвійного спрацювання, цикл розсилки схований всередину `select for update`.

Коли робив [інтеграцію з Bluesky](https://www.rozhkov.me/bluesky/), то додав код, який також публікує збір у Bluesky. Звичайно, я не перевіряв E2E виклик, і просто скопіпастив рядок з реплу. Виглядало це якось так:

```
Lock.find_by(key: "daily_post").with_lock do
  random_post = Post.random_post

  TgUser.pluck(:tg_id).each do |tg_id|
    SendDailyPostToUserJob.perform_later(tg_id, random_post.id)
  end
  random_post.update(posted_count: random_post.posted_count + 1)
  SendDailyPostToBskyJob.perform_later(post_id)
end
```

Запушив код опівночі та й ліг спати.

І звичайно що я побачив наступного ранку? Вірно, 6 однакових постів у себе.

Справдився [мій найбільший страх](https://www.rozhkov.me/double-spend-fear/) — зациклити відправку однакових повідомлень клієнтам.

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

Уважний читач звісно помітить тривіальну помилку.

Також я не мав перевірок на те, що збір вже було відправлено конкретному підписнику. Тому створювалась пачка джоб які асинхронно відправляли його в окремих транзакціях, а транзакція, що мала б проставити посту прапорець «опубліковано» не комітилась, джоба ретраїлась, і все йшло по колу.

Добре що я помітив це достатньо швидко та мав можливість виправити, погано що недостатньо швидко і всім таки прийшло шість однакових повідомлень.

Ймовірно через це декілька людей відписались та не задонатили гроші, і тепер сума зборів впала на 3-5 тисяч гривень😔

Зараз я також ще раз подивився на цей код та знайшов там декілька способів хороших оптимізацій, але вже боюсь його пушити, бо немає тестів 🤦‍♂️️. Схоже що настав час таки їх зробити.

🥺Вибачте всі кому прийшли зайві повідомлення. Я знаю що тих хто досі читає бот, а не закинув його в архів, лише кілька десятків, але мені дуже цінна підтримка кожного з вас.

P.S.: в мене була купа тестів на те що правильно обчислюється щоденний збір — що він у коректному статусі, не дублює попередні, публікується у правильному порядку і так далі. Але це все були юніти, а такого, щоб перевірив повністю як відпрацював лок та джоби та ще у декількох потоках — ні. Бо складно та й так все два з половиною роки працювало як годинник.

P.P.S.: можете порадувати ріжка та підписатися на бот [@Donate1024Bot](https://t.me/Donate1024Bot) і донатити щоденно хоча б по сотні гривень — то вже буде досить потужно🥺

*update: після того, як я написав пост, натрапив на таку штуку в Rails як [запуск джоб після коміту транзакції](https://x.com/siaw23/status/1859318910576730184)*🤯*. Хтось уже не раз зіткнувся з проблемою як у мене і написав для неї коробочне рішення. Читайте доки!*