Движок синка для трекинга расходов на AI-инструменты
Клиент использовал несколько аккаунтов AI-ассистента для разработки — разные места для разных членов команды, одни на бизнес-планах, другие на индивидуальных подписках. Каждый месяц сверка расходов была ручным процессом: зайти в каждый аккаунт, посмотреть дашборд использования, сделать скриншот цифр, вставить их в таблицу. С пятью аккаунтами это раздражало. По мере роста команды к пятнадцати это стало невыносимо. Им нужна была централизованная система, которая автоматически тянет данные использования из каждого аккаунта и держит их в синхронизации.
polling и нормализация
Мы построили движок polling и синхронизации, который аутентифицируется в каждый аккаунт через сохранённые куки сессии и собирает данные использования через внутренние API-эндпоинты инструмента. Система ходит по нескольким эндпоинтам на аккаунт: фильтрованные события использования (отдельные запросы с именами моделей, количеством токенов и стоимостью), сводки использования (агрегированные итоги по биллинговому периоду), счета, жёсткие лимиты и метаданные аккаунта. Каждый цикл polling работает независимо на аккаунт по cron-расписанию, поэтому медленный ответ одного аккаунта не блокирует другие. Сырые ответы API нормализуются через слой трансформации, который конвертирует центы в доллары, Unix-таймстемпы в нормальные datetime и сопоставляет внутренние идентификаторы моделей с человекочитаемыми названиями. Нормализованные события апсертятся в PostgreSQL с уникальным ограничением на комбинацию ID аккаунта, времени события и модели — это гарантирует, что повторный polling тех же данных никогда не создаст дубликатов.
идемпотентность и целостность
Идемпотентные апсерты звучат просто, пока вы не натыкаетесь на крайние случаи. Первая версия использовала уникальное ограничение только на ID аккаунта и время, и это работало, пока два запроса к разным моделям не происходили в одну и ту же миллисекунду — одно событие молча перезаписывало другое. Переход на составное ограничение по ID аккаунта, времени и модели решил проблему целостности данных, но всплыла другая: API иногда возвращает одно и то же событие со слегка разными значениями стоимости в последовательных опросах, видимо из-за пересчёта тарифа на их стороне. Мы обрабатываем это стратегией «update on conflict», которая всегда берёт последнее значение, в паре с журналом изменений, фиксирующим, когда ранее синхронизированное событие меняется. Это защитная мера, но когда вы строите систему, которой люди пользуются для биллинга, ошибка даже на несколько центов подрывает доверие.
сессии и прокси
Управление куки сессий оказалось самой операционно хрупкой частью. Куки истекают непредсказуемо — иногда через дни, иногда через недели — и механизма refresh-токена нет, потому что мы работаем с куки сессии браузера, а не с официальным API. Когда куки умирает, цикл polling возвращает ошибки аутентификации, и аккаунт уходит в тень до тех пор, пока кто-то не предоставит свежий куки. Мы построили детекцию для этого: если цикл polling получает auth-ошибку, система помечает аккаунт как «session expired», прекращает его опрашивать, чтобы избежать штрафов за rate-limit, и отправляет алерт. Не элегантно, но без официального API элегантного решения нет.
Ротация прокси была поздним дополнением, рождённым необходимостью. В период интенсивного polling — выгрузки исторических данных для свежедобавленной пачки аккаунтов — основной IP получил rate-limit. Запросы стали возвращать 429-е, а период кулдауна был достаточно долгим, чтобы весь пайплайн синхронизации стал бесполезным на часы. Мы добавили поддержку ротации прокси-IP через пул, распределяя запросы по нескольким точкам выхода. Слой прокси сидит между движком polling и API, и он опциональный — аккаунты, которые опрашиваются нечасто, могут ходить напрямую, а аккаунты с интенсивным polling маршрутизируются через пул прокси. Конфигурация задаётся на аккаунт, что сохраняет гибкость, не добавляя лишней сложности в обычном случае.
результаты
Движок теперь надёжно синхронизирует данные использования по всем аккаунтам, нормализованные и дедуплицированные записи попадают в PostgreSQL, готовые к запросам со стороны аналитического дашборда. Вывод из этого проекта: работа без официального API — это осознанный компромисс. Вы получаете доступ к данным, которого иначе бы не было, но наследуете все нестабильности нижележащей системы — управление сессиями, изменения схем, rate-limit. Слои нормализации и идемпотентности — это не просто аккуратная инженерия; это единственное, что стоит между клиентом и базой данных, полной конфликтующих чисел. Если когда-нибудь выйдет официальный API, половину этой системы можно будет заменить за день. До тех пор — работает.