Канал о разработке СмИТ Биллинга
Биллинг для небольших интернет-провайдеров: абоненты, тарифы, RADIUS, деньги, поддержка, CRM. Работает у трёх операторов, 6 400 клиентов.
Здесь — инженерная часть: что сломалось и почему, как чинили, какие цифры получились. Без обещаний и «инновационных решений».
Раз в неделю, иногда чаще.
· Продукт и цены — billing.smit34.ru
· Документация — docs.billing.smit34.ru
· Написать — @uspeshnyy
Биллинг для небольших интернет-провайдеров: абоненты, тарифы, RADIUS, деньги, поддержка, CRM. Работает у трёх операторов, 6 400 клиентов.
Здесь — инженерная часть: что сломалось и почему, как чинили, какие цифры получились. Без обещаний и «инновационных решений».
Раз в неделю, иногда чаще.
· Продукт и цены — billing.smit34.ru
· Документация — docs.billing.smit34.ru
· Написать — @uspeshnyy
СмИТ Биллинг разработка pinned «Канал о разработке СмИТ Биллинга Биллинг для небольших интернет-провайдеров: абоненты, тарифы, RADIUS, деньги, поддержка, CRM. Работает у трёх операторов, 6 400 клиентов. Здесь — инженерная часть: что сломалось и почему, как чинили, какие цифры получились.…»
Модуль стоял два месяца из-за одной строки
Банковские выписки разбирались, платежи в очередь попадали, а вручную привязать платёж к клиенту было невозможно. 69 платежей висели нетронутыми.
Причина оказалась не в логике. Поиск клиента дёргал
То есть форма выглядела рабочей: список появлялся, кнопка нажималась. Просто в списке были случайные люди, а привязка не происходила.
Теперь поиск свой: ФИО, договор, телефон по последним 10 цифрам, e-mail, ID и ИНН. Совпавший ИНН поднимает клиента наверх, а незнакомый ИНН система находит в реестре налоговой через DaData.
Мораль скучная: «форма отрисовалась» — не то же самое, что «форма работает». Проверять надо результат действия, а не наличие интерфейса.
Банковские выписки разбирались, платежи в очередь попадали, а вручную привязать платёж к клиенту было невозможно. 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.
Если у вас есть демо-стенд с копией боевой базы — проверьте не только имена, но и печати, реквизиты и текстовые поля с назначением платежа.
Демо-стенд обновлялся ночным копированием с боевого сервера. Вместе с базой туда приезжали: 5 735 настоящих ФИО, 6 053 телефона, 73 874 значения атрибутов, 10 240 переписок с клиентами, 35 473 записи звонков.
Обезличили. А потом заметили ещё два слоя.
В счетах и актах стояли боевые реквизиты компании — ИНН, расчётный счёт, БИК — и факсимиле печати с подписью директора. Любой посетитель стенда мог выписать себе документ с нашей печатью.
В описаниях 15 381 финансовой операции остались фамилии плательщиков: «ДОГОВОР 0336 ОВЧИННИКОВА ОКСАНА ВИКТОРОВНА», «АльфаБанк · от АБДУЛОВ И. И.». Прежнее правило обезличивания искало «Иванов Иван» — с заглавной и строчными, а в базе всё капсом.
Главное: без встраивания в ночную синхронизацию всё это возвращалось бы каждое утро в 4:00.
Если у вас есть демо-стенд с копией боевой базы — проверьте не только имена, но и печати, реквизиты и текстовые поля с назначением платежа.
Заблокированных абонентов перестало отключать: лимит процессов заняли 38 тысяч зомби
Искали в логах ошибки 500, а нашли строку, которая повторялась 140 раз в час:
Лимит задач контейнера — 38 460, занято 38 458. Процессов при этом пять. Остальное — 38 453 зомби
Цепочка такая. Команда на MikroTik идёт через
Проверили по часам: 38 375 зомби на 40 017 таймаутов, корреляция 1,000.
Починили так: по таймауту убиваем дерево процессов снизу вверх и ждём, пока живой родитель подберёт потомков. После выкладки — 47 таймаутов за восемь минут и ни одного зомби. Следующий шаг —
Мораль: если функция при ошибке возвращает пустую строку, а вызывающий код пишет «OK», сбой проживёт дольше любого другого.
Разбор и команды для самопроверки: billing.smit34.ru/blog/38-tysyach-zombi
Искали в логах ошибки 500, а нашли строку, которая повторялась 140 раз в час:
can't start new thread. Воркер, который разрывает сессии заблокированных абонентов, полтора дня не мог запустить ни поток, ни процесс. В логе рядом с каждой ошибкой стояло «disconnect: OK».Лимит задач контейнера — 38 460, занято 38 458. Процессов при этом пять. Остальное — 38 453 зомби
ssh.Цепочка такая. Команда на MikroTik идёт через
sshpass → ssh с таймаутом 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
СмИТ Биллинг
38 тысяч зомби: как восьмисекундный таймаут выключил отключение должников — блог СмИТ Биллинг
Воркер, который отключает должников, полтора дня молча не работал: его лимит процессов заняли зомби. Как один забытый ssh на каждый таймаут превращается в аварию через пять суток и почему в логе всё было «OK».
Регрессионный тест прошёл на старом коде — из-за
Писали тест к багу с зомби-процессами: настоящие
На старом коде тест обязан был упасть. Он прошёл.
Причина — хвост команды запуска:
Убрали пайп — PID 1 стал python, тест покраснел ровно на баге. Потом он же поймал гонку в первой версии фикса:
Мораль: сначала убедитесь, что тест красный на старом коде. Зелёный тест, который ни разу не был красным, ничего не доказывает.
| 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 финального ответа прибавлялся внутри цикла работы с инструментами — перед
С Claude была обратная ошибка: usage брался только из последнего ответа, а итерации с вызовом инструментов не считались вовсе.
Исправили, пересчитали журнал (350 строк) и начисления. Расход на Gemini за месяц: 361 ₽ → 203 ₽.
Отдельная находка:
Считали токены 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
Четыре минуты, живые экраны: письмо из банка → разбор выписки → сопоставление плательщика по ИНН и назначению → спорные платежи → привязка → зачисление → счёт и акт юрлицу → чек в ОФД.
Одну сцену оставили намеренно неудобной: 153 платежа висят на модерации, потому что система не смогла опознать плательщика. Соблазн был показать только зачисленные 120 и красивую цифру автоматизации. Но человек, который завтра поставит это себе, всё равно увидит очередь — лучше на четвёртой минуте ролика, чем на второй неделе работы.
Экраны сняты с обезличенного стенда: плательщики вымышленные, реквизиты в счёте недействительные.
Смотреть: storage.googleapis.com/uspeshnyy-projects/smit/billing/video/money-bez-buhgaltera.mp4