Когда у тебя два источника данных о наградах в блокчейне — live API и индексированная база — переключаться между ними страшно. Инвесторы смотрят на цифры прямо сейчас, и если что-то сломается, они увидят это первыми. А ещё у меня был баг: награды показывались как unclaimed, хотя пользователь уже их забрал час назад. Cron-задача синхронизировала статус раз в N минут, и между запусками данные устаревали.

Два источника — две правды

В GonkaHub данные о наградах валидаторов шли из двух мест. Раньше это был NetworkParticipantReward — модель, которая тянула всё напрямую из blockchain API каждый раз. Работало, но медленно. Потом появилась индексированная база transactions с таблицей RewardHistory — там данные складывались заранее, и запросы летали быстрее.

Проблема: я не мог просто взять и переключиться. Надо было тестировать новый источник в production, но так, чтобы можно было откатиться за секунду, если что-то пойдёт не так. Деплой нового кода — это долго. Нужен был способ переключаться через конфиг.

Feature flags через environment variables

Сделал два флага:

REWARDS_DB_SOURCE=legacy      # или new
TRANSACTIONS_DB_SOURCE=legacy # или new

Если legacy — читаю из старых моделей и live API. Если new — из индексированной базы transactions. Переключение — просто меняешь env var и перезапускаешь контейнер. Никакого деплоя, никакого нового кода.

Написал RewardDataService — сервис-обёртку, которая проверяет флаг и решает, откуда брать данные:

class RewardDataService:
    @staticmethod
    def use_new_db() -> bool:
        return getattr(settings, 'REWARDS_DB_SOURCE', 'legacy') == 'new'

    @classmethod
    def get_participant_rewards(cls, address, limit=30):
        if cls.use_new_db():
            from transactions.models import RewardHistory
            return RewardHistory.objects.filter(
                participant_address=address
            ).order_by('-epoch_id')[:limit]
        else:
            from core.models import NetworkParticipantReward
            return NetworkParticipantReward.objects.filter(
                participant_address=address
            ).order_by('-epoch_id')[:limit]

Аналогично сделал TransactionDataService для транзакций. Там добавились новые типы: Authz (делегирование прав между адресами) и Governance (голосование за proposals в блокчейне). Раньше обрабатывал только Delegate, Redelegate, Undelegate. Теперь стало шире.

Баг с claimed статусом

Cron-задача синхронизировала награды каждые 15 минут. Представь: пользователь в 14:00 забрал награду (withdraw_rewards), но cron следующий раз запустится только в 14:15. Эти 15 минут в UI показывается unclaimed, хотя деньги уже давно на кошельке. Выглядит как баг, хотя технически данные просто устарели.

Фикс: перестал полагаться только на cron. Теперь при обработке событий withdraw_rewards сразу обновляю claimed статус в базе. Event-driven синхронизация вместо polling по расписанию. Пользователь забрал награду — статус поменялся моментально.

Плюс добавил is_estimated флаг. Некоторые суммы наград рассчитываю по формуле (до того, как блокчейн зафиксировал финальное значение). Честнее показать в UI, что это оценка, а не точная цифра. Зелёная галочка для verified данных, жёлтая тильда (~) для estimated. Прозрачный UX.

Celery tasks и идемпотентность

Celery задачи для синхронизации наград и транзакций стали надёжнее. Добавил max_retries=3 с exponential backoff — если blockchain API тормозит, задача повторится через минуту, потом через две, потом через четыре. Если и тогда не заработало — падает в error и шлёт алерт.

Идемпотентность: перед обработкой записи проверяю, не обработал ли я её уже. Если у RewardHistory есть запись с таким же epoch_id и participant_address — скипаю. Не создаю дубликаты, если задача запустилась дважды по ошибке.

Результат

17-18 января 2026 года развернул feature flags на production. Переключение между источниками данных стало безопасным — меняешь env var, смотришь метрики, если что-то не так — откатываешься за 30 секунд. Без деплоя, без downtime.

Баг с claimed статусом исчез. Теперь награды обновляются в real-time при событии withdraw_rewards, а не раз в N минут по cron. Пользователи видят актуальный статус всегда.

Добавил поддержку Authz и Governance транзакций — теперь отслеживаю не только делегирование, но и голосование за proposals в блокчейне. Расширил картину активности инвесторов.

И честный UX: в интерфейсе показываю, какие цифры точные (из блокчейна), а какие estimated (рассчитаны по формуле). Никакого обмана, только прозрачность.

Feature flags — это не про сложность. Это про контроль. Когда можешь переключить источник данных одной строчкой в конфиге и спать спокойно, зная, что откат займёт секунды — вот это настоящая уверенность в production.