Когда у тебя два источника данных о наградах в блокчейне — 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.