СмИТ Биллинг разработка
4 subscribers
3 links
Как устроен биллинг для интернет-провайдера: разборы багов, замеры, решения. Пишем о том, что делаем сами — система работает у трёх операторов. Продукт: billing.smit34.ru · Вопросы: @uspeshnyy
Download Telegram
Канал о разработке СмИТ Биллинга

Биллинг для небольших интернет-провайдеров: абоненты, тарифы, RADIUS, деньги, поддержка, CRM. Работает у трёх операторов, 6 400 клиентов.

Здесь — инженерная часть: что сломалось и почему, как чинили, какие цифры получились. Без обещаний и «инновационных решений».

Раз в неделю, иногда чаще.

· Продукт и цены — billing.smit34.ru
· Документация — docs.billing.smit34.ru
· Написать — @uspeshnyy
СмИТ Биллинг разработка pinned «Канал о разработке СмИТ Биллинга Биллинг для небольших интернет-провайдеров: абоненты, тарифы, RADIUS, деньги, поддержка, CRM. Работает у трёх операторов, 6 400 клиентов. Здесь — инженерная часть: что сломалось и почему, как чинили, какие цифры получились.…»
Модуль стоял два месяца из-за одной строки

Банковские выписки разбирались, платежи в очередь попадали, а вручную привязать платёж к клиенту было невозможно. 69 платежей висели нетронутыми.

Причина оказалась не в логике. Поиск клиента дёргал /rest_api/v2/Abonents/?search=…&limit=10, а API не знает ни search, ни limit — и честно отдавал первые 50 абонентов подряд. В разметке при этом стояло a.pk, тогда как ключ в ответе называется id. Каждая строка рисовалась как #undefined, клик отправлял abonent_id=undefined.

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

Теперь поиск свой: ФИО, договор, телефон по последним 10 цифрам, e-mail, ID и ИНН. Совпавший ИНН поднимает клиента наверх, а незнакомый ИНН система находит в реестре налоговой через DaData.

Мораль скучная: «форма отрисовалась» — не то же самое, что «форма работает». Проверять надо результат действия, а не наличие интерфейса.
Готовили демо для показа клиентам и нашли на нём боевые данные

Демо-стенд обновлялся ночным копированием с боевого сервера. Вместе с базой туда приезжали: 5 735 настоящих ФИО, 6 053 телефона, 73 874 значения атрибутов, 10 240 переписок с клиентами, 35 473 записи звонков.

Обезличили. А потом заметили ещё два слоя.

В счетах и актах стояли боевые реквизиты компании — ИНН, расчётный счёт, БИК — и факсимиле печати с подписью директора. Любой посетитель стенда мог выписать себе документ с нашей печатью.

В описаниях 15 381 финансовой операции остались фамилии плательщиков: «ДОГОВОР 0336 ОВЧИННИКОВА ОКСАНА ВИКТОРОВНА», «АльфаБанк · от АБДУЛОВ И. И.». Прежнее правило обезличивания искало «Иванов Иван» — с заглавной и строчными, а в базе всё капсом.

Главное: без встраивания в ночную синхронизацию всё это возвращалось бы каждое утро в 4:00.

Если у вас есть демо-стенд с копией боевой базы — проверьте не только имена, но и печати, реквизиты и текстовые поля с назначением платежа.
Заблокированных абонентов перестало отключать: лимит процессов заняли 38 тысяч зомби

Искали в логах ошибки 500, а нашли строку, которая повторялась 140 раз в час: can't start new thread. Воркер, который разрывает сессии заблокированных абонентов, полтора дня не мог запустить ни поток, ни процесс. В логе рядом с каждой ошибкой стояло «disconnect: OK».

Лимит задач контейнера — 38 460, занято 38 458. Процессов при этом пять. Остальное — 38 453 зомби ssh.

Цепочка такая. Команда на MikroTik идёт через sshpassssh с таймаутом 8 секунд. По таймауту Python убивает sshpass, а ssh остаётся сиротой и уходит к PID 1. В контейнере без init PID 1 — сам воркер, и сирот он не подбирает. Два медленных NAS давали около 280 таймаутов в час, до потолка хватило 5,6 суток.

Проверили по часам: 38 375 зомби на 40 017 таймаутов, корреляция 1,000.

Починили так: по таймауту убиваем дерево процессов снизу вверх и ждём, пока живой родитель подберёт потомков. После выкладки — 47 таймаутов за восемь минут и ни одного зомби. Следующий шаг — init: true в compose.

Мораль: если функция при ошибке возвращает пустую строку, а вызывающий код пишет «OK», сбой проживёт дольше любого другого.

Разбор и команды для самопроверки: billing.smit34.ru/blog/38-tysyach-zombi
Регрессионный тест прошёл на старом коде — из-за | tail

Писали тест к багу с зомби-процессами: настоящие sshpass и ssh стучатся в порт, который принимает соединение и молчит, как зависший NAS. Запускается в одноразовом контейнере из боевого образа, где PID 1, как и на проде, осиротевших процессов не подбирает.

На старом коде тест обязан был упасть. Он прошёл.

Причина — хвост команды запуска: exec python -m pytest … | tail -25. Ради пайпа shell не отдаёт PID 1 питону и остаётся первым процессом сам. А shell, в отличие от воркера, сирот подбирает. Зомби исчезали, и проверка «ssh не остался» зеленела.

Убрали пайп — PID 1 стал python, тест покраснел ровно на баге. Потом он же поймал гонку в первой версии фикса: ssh и sshpass убивались одновременно, sshpass не успевал подобрать ssh, и тот снова уходил в зомби.

Мораль: сначала убедитесь, что тест красный на старом коде. Зелёный тест, который ни разу не был красным, ничего не доказывает.
Отчёт по AI показывал расход в два раза больше настоящего

Считали токены Gemini и OpenAI: usage финального ответа прибавлялся внутри цикла работы с инструментами — перед break — и ещё раз после цикла. Если инструменты не вызывались, получалось ровно ×2. countTokens говорил 11 532, журнал показывал 23 064.

С Claude была обратная ошибка: usage брался только из последнего ответа, а итерации с вызовом инструментов не считались вовсе.

Исправили, пересчитали журнал (350 строк) и начисления. Расход на Gemini за месяц: 361 ₽ → 203 ₽.

Отдельная находка: ServiceUsage.ts объявлено как auto_now_add, поэтому явная дата при create() молча игнорируется — коррекция за прошлый месяц падала в текущий. Ставится только через queryset.update(ts=…).
Сняли, как деньги проходят путь от письма банка до баланса клиента

Четыре минуты, живые экраны: письмо из банка → разбор выписки → сопоставление плательщика по ИНН и назначению → спорные платежи → привязка → зачисление → счёт и акт юрлицу → чек в ОФД.

Одну сцену оставили намеренно неудобной: 153 платежа висят на модерации, потому что система не смогла опознать плательщика. Соблазн был показать только зачисленные 120 и красивую цифру автоматизации. Но человек, который завтра поставит это себе, всё равно увидит очередь — лучше на четвёртой минуте ролика, чем на второй неделе работы.

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

Смотреть: storage.googleapis.com/uspeshnyy-projects/smit/billing/video/money-bez-buhgaltera.mp4