Какой бы сложной и функциональной ни была биллинговая система, для клиентов важна только одна ее цель – проведение автоматических расчетов с последующим выставлением счета. В конце концов, какая абоненту разница, как всё работает на стороне оператора, если оно работает надежно и безотказно – главное, сформированный счет.  Система массовой печати в биллинге, возможно, для кого-то и не представляет особого интереса, но для нас – это, прежде всего, воспоминание из прошлого, когда значительные технологические ограничения, архитектурные и инженерные сложности не смогли нам помешать предложить клиенту эффективное решение задачи массовой печати отчетов и счетов. Но, обо всем по порядку. Нашим первым клиентом в конце 90-х гг. была небольшая компания по предоставлению услуг мобильной связи. Сначала ее абонентская база не превышала нескольких тысяч клиентов, но позднее увеличилась до нескольких десятков тысяч абонентов. Вариантов построения системы формирования отчетов было не так много – для телекоммуникаций в то время только БД Oracle, разве что. На клиенте было сначала несколько десятков, а потом и больше окон. Быстро разработать и доработать клиента было достаточно сложно, и вариантов, опять же, не так много. Потому перешли на Delphi. Стандартный инструментарий построения отчетов – адское решение из-за неудобства и низкой скорости работы, поэтому его даже не рассматривали. В итоге выбрали FastReport. Да, поначалу все было более или менее просто и понятно. Имеется тонкий клиент, в него загружаем список отчетов за нужный период и отправляем на печать. Только позже у сотового оператора клиентская база разрослась до сотен тысяч абонентов, и вся простота формального решения мгновенно исчезала. Становилось сложнее, но все равно справиться с задачей можно было. С горизонтальным масштабированием удалось оптимизировать процесс массовой печати. Докупили принтеры, добавили фильтры в форму печати отчетов по всевозможным критериям, а сами задания на печать отправляли параллельно с нескольких рабочих мест. Пока что так. Но, спустя время у нас появляется другой клиент – региональный оператор проводной телефонной связи и доступа в Интернет. А как все прекрасно понимают, у таких операторов есть «золотое правило» в отношении счетов и отчетов – предоставляются они клиентам исключительно в бумажном виде, если, конечно, иное не оговаривалось в договоре, что было крайне редко, и никому не интересно. Да и какие были варианты доставки счетов за услуги в «нулевые» годы, кроме как в распечатанном на бумажном носителе виде, в конверте по почте. С учетом большой клиентской базы у оператора имелось собственное производство, которому бы позавидовала мини-типография – собственный цех печати с десятками промышленных принтеров, которые с огромной скоростью превращали листы бумаги в полноценные счета. И тут начинается кое-что интересное. Если с отправкой отчетов для физических лиц всё относительно просто, то с юридическими лицами было немного иначе. Для них пакет документов – это 4-5 отдельных документа, часть из которых с односторонней печатью, часть – с двусторонней, а еще с печатью и подписью. Решить задачу массовой печати с официальной печатью и подписью оператора удалось достаточно интересным способом. Вспомним нашего первого клиента – оператора мобильной связи. Его вопрос оттиска на отчетах почти не волновал – на франкировальной машине через штемпели наносились соответствующие оттиски на документах. А вот с текущим клиентом такой подход не работал – в его юридическом отделе сразу сказали, что это не печать вовсе, и, соответственно, никакой юридической силы документ иметь не будет. Ну, нет, так нет – сделаем иначе. Весь месяц сотрудники оператора готовили подобие бланков под счета – проставляли печати и подписывали чистые листы, а при последующей печати загружали их в отдельный лоток, откуда принтер брал бумагу для соответствующих документов, требующих подписи и оттиска. Далее это дело посредством конвертовальной машины упаковывалось в почтовый конверт. Но, не вручную же отправлять машине документы, которые она будет потом сворачивать по конвертам? Нет, конечно. Для этого воспользовались всеми возможностями штрих-кодов. Это был первый момент. Второй момент касался объединения и разбиения документов по конвертам. В один конверт при всем желании можно было упаковать не более 20 листов. И конверт все же почтовый, а у почты были свои требования к весу. Соответственно, не помещается пакет документов в один конверт, значит, разбивается на несколько. Обратная задача – объединить несколько документов в одно отправление – смысл платить за каждый конверт отдельно, если в нем 1-2 листа, когда можно для одного клиента с несколькими счетами сделать одно отправление одним конвертом.  Наконец, сугубо практическое решение для экономии бумаги и не только. При изменении порядка лицевых счетов одного клиента, отправляемых на печать, можно было не тратить зря бумагу, если, например, предыдущий отчет при двусторонней печати получался на 3 страницы, то следующий, двусторонний, можно было продолжать печатать на пустой обратной странице листа от предыдущего.     В итоге пришли к следующему – формируем отчеты по лицевым счетам с точки зрения оптимизации расхода бумаги, проставляем штрих-коды для последующего разбиения по конвертам и разбиваем задания на печать в зависимости от односторонней или двусторонней печати, обычные листы или с проставленными оттисками и подписями. Вот так скучнейшая задача формирования отчетов вдруг заиграла яркими красками. Формируем все отчеты по всем лицевым, оптимизируем порядок следования лицевых, разбиваем по конвертам (проставляем штрих-коды), разбиваем по заданиям принтеру - одно/двусторонний режим, номер лотка. Всё это дело, о котором шла речь выше, было реализовано посредством тонкого клиента «Абонентское обслуживание», основная задача которого, как понятно из названия – работа с данными абонентов в биллинге. Про удобство отправки заданий на печать на десяток принтеров из такого клиента мы скромно промолчим – все понятно и без комментария. Поэтому теперь проблема красиво реализованной системы массовой печати вставала всё более остро, и нужно было найти оптимальное решение. Что нужно было оператору? В целом, все вышеперечисленные возможности, но чтобы работало на стороне сервера, управлялось с клиентской стороны и можно было бы при этом где-то надежно хранить сформированные отчеты. Что же, нужно, так нужно. И мы реализовали это наиболее красивым из возможных на тот момент способов, но об этом расскажем в следующей части. В любом деле, когда сталкиваешься с, казалось бы, непреодолимым препятствием, и клиенту неинтересно, как будешь его обходить, есть два пути: начать всё с самого начала, т.е. разработать систему с нуля, или найти таки решения конкретной задачи. Огромный код готовой системы и существенные трудозатраты на одновременную поддержку работоспособности старой и вновь разрабатываемой системы – нет, пойти по первому пути мы не могли, поэтому искали возможные решения. Реализовать функциональность системы массовой печати нужно было на Delphi. У нас сервис, поэтому основная часть – была службой Windows. О модулях пока было смутное представление из-за вопроса многопоточной обработки и многопоточной печати.   В FastReport имелась на тот момент многопоточная обработка отчетов, но в документации речь больше шла про область видимости компонентов и приемы, которые можно было использовать. Путем тестирования удалось выяснить, что опасения по многопоточной обработке были напрасными. Также путем тестов определили, что и с многопоточной печатью проблем не возникало. А, раз все работает, значит, можно было разрабатывать монолитный сервис с минимизацией зависимостей и связей между подсистемами.  Архитектуру выбрали классическую – главный поток получал от клиента задания, запускал рабочие потоки и предоставлял клиенту отчеты о прогрессе. Поработали с кодом с клиента и сервера, переработали под новую архитектуру, протестировали – сбоев нет. Проблемы возникли с запуском в опытную эксплуатацию. Дело в том, что производительность генерации падала линейно по времени, причем, заметно – за 1 час почти в 2 раза. Впрочем, шаблоны и отчеты в FastReport формировались в XML, т.е. генерация – это интенсивная работа со строками, и возможен существенный рост потребления памяти из-за фрагментации, но не ожидали, что будет падать производительность. Поскольку это дело лечится перезапуском, то поступили так – выделили генерацию отчетов  в отдельный процесс, который будет перезапускаться. В итоге из нашего монолитного сервиса отделился «микросервис» генерации в DCOM сервер. Рабочий поток запускал DCOM сервер, отправлял ему задания, а через определенный промежуток времени перезапускал. Такой подход продемонстрировал нам следующее: Скорость генерации была стабильной, и больше не привязывалась ко времени. Скорость печати одинакова – что на 1, что на 10 принтеров. Конечно, в FastReport была защита от одновременного обращения к одному глобальному объекту «принтер» разных потоков, т.е. отправить-то мы могли сколько угодно потоков, но печатались отчеты по очереди. Иначе говоря, с одного процесса нельзя было печатать параллельно. Раз нельзя из одного, значит, будем печатать из разных. В итоге от нашего монолитного сервиса отделился и второй «микросервис» – сервер приложений DCOM, отвечающий за печать на закрепленный за ним принтер. И тут снова столкнулись с проблемой. Когда разные процессы отправляли на параллельную печать, то клиент получал множество сообщений о прогрессе выполнения – чуть ли не DDoS-атака! По каждому событию формировались уведомления для клиентов. Клиент пытался всё это дело красиво обрабатывать, отображая для всех текущих заданий на печать по сотни событий, а потом просто зависал. Причина – каждый рабочий поток самостоятельно отправлял уведомления. Решение – агрегация уведомлений главным потоком с удалением избыточных и отправкой собранного пакета за определенный промежуток времени единовременно. Взаимодействие клиента и сервиса строилась по следующей схеме: Клиент отсылает команды сервису. Клиент получал статистику по TCP обратно от сервиса. При соединении клиент открывал порт TCP для получения статистики от сервиса. На протяжении 3-4 дней не было никаких гарантий, что соединение не будет разорвано. Что делать? Опять же, два пути: бороться с потерями связи или пользоваться инструментами гарантированной доставки сообщений. Выбрали второй путь с ZMQ. Это легковесная библиотека с удобным механизмом распространения нотификаций.  Был у решения и минус – непривычная отправка команд с клиента на сервис. Обычно при отправке команд используется синхронная модель. Если вызов функции отправки команды прошел, значит, команда доставлена на сервис. С ZMQ успешный вызов функции означает только то, что команда успешно добавлена в очередь на отправку. Чтобы не ломать предыдущую логику, добавили синхронное ожидание подтверждения от сервиса - если в течение некоторого времени подтверждение не пришло, значит, команда не доставлена сервису. Не оставили без внимания и функциональность системы массовой печати. Реализовали формирование отчетов в PDF, возможность отправки отчетов по e-mail, отправку в системы электронного документооборота. Активная работа системы наблюдалась один раз в месяц, поэтому и разработку строили с учетом такой цикличности – до середины месяца новые решения и возможности добавлялись в очередную версию, которая потом тестировалась.  В итоге мы получили систему массовой печати отчетов, которая за 4 дня могла сгенерировать и напечатать свыше 2 млн. страниц отчетов. Вначале все модули были развернуты на 1 виртуальном сервере с 4-ядерным процессором и 16 Гб оперативной памяти. При работе в 10 параллельных потоков производительность составляла около 600 отчетов в минуту. Позднее для ускорения работы часть модулей перенесли на рабочую станцию. В качестве заключения отметим следующее – система массовой печати отчетов в биллинге может быть нудной и тривиальной, неоптимизированной, а может получить интересные и красивые решения, если нестандартно подойти к ее разработке и внедрению.