Кардиохирургия: Как Мы Переписывали Основной Компонент Dlp-Системы

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

В нашем случае дела обстояли еще хуже: нам пришлось переписать ключевую часть системы, и в силу внешних обстоятельств мы не смогли заменить старые куски кода на новые по частям, только все сразу и целиком.

И все это в условиях нехватки времени, ресурсов и документации, но с требованием руководства, чтобы в результате «эксплуатации» не пострадал ни один заказчик.

Под катом рассказ о том, как мы переписали основной компонент продукта с 17-летней историей (!) с Scheme на Clojure, и все сразу заработало как надо (ну почти :)).



Кардиохирургия: как мы переписывали основной компонент DLP-системы



17 лет в Дозоре

Продукт Solar Dozor — это DLP-система с очень долгой историей.

Первая версия появилась еще в 2001 году как сравнительно небольшой сервис по фильтрации почтового трафика.

За 17 лет продукт вырос в крупный программный комплекс, который собирает, фильтрует и анализирует разнородную информацию, поступающую внутри организации, и защищает бизнес клиентов от внутренних угроз.

При разработке 6-й версии Солар Дозора мы кардинально перетрясли продукт, выбросили из кода старые костыли и заменили их новыми, обновили интерфейс, доработали функционал под современные реалии - в общем, сделали продукт архитектурно и концептуально более целостно.

На тот момент под капотом обновлённого Solar Dozor находился огромный пласт монолитного унаследованного кода — того самого сервиса фильтрации, который на протяжении всех этих 17 лет постепенно приобретал новый функционал, воплощая в себе как долгосрочные решения, так и немедленные решения.

бизнес-проблемы, но сумел остаться в рамках исходных архитектурных парадигм.



Кардиохирургия: как мы переписывали основной компонент DLP-системы

Сервис фильтрации Излишне говорить, что внесение любых изменений в столь древний код требовало особой деликатности.

Разработчикам пришлось быть предельно осторожными, чтобы случайно не нарушить функционал, созданный десять лет назад. Кроме того, в изобретенное на заре эпохи прокрустово ложе архитектуры вынуждены были втиснуться совершенно новые и интересные решения.

Понимание того, что возникла необходимость обновления системы, появилось довольно давно.

Но духа прикоснуться к огромному и древнему системному сервису явно не хватало.



Мы не пытаемся отсрочить неизбежное.

Продукты с длительной историей развития обладают интересной особенностью.

Каким бы странным ни казался кусок функционала, если он успешно дожил до наших дней, это означает, что он создан не из теоретических идей разработчиков, а в ответ на конкретные потребности заказчика.

В этой ситуации ни о какой поэтапной замене не могло быть и речи.

Вырезать и переделывать функционал по частям было невозможно, потому что все эти детали были востребованы заказчиками, и мы не могли «закрыть их на реконструкцию».

Пришлось аккуратно удалить старый сервис и предоставить ему полнофункциональную замену.

Только целиком, только сразу.

Улучшение процесса разработки продукта, скорости изменений и общего качества было необходимо, но недостаточно.

Руководство интересовалось, какие выгоды принесут изменения нашим клиентам.

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

Также велась борьба за сокращение потребления ресурсов при сохранении (а в идеале — увеличении) текущей скорости обработки.



Немного о начинке

На протяжении всего процесса разработки продукта команда Solar Dozor тяготела к функциональному подходу.

Это приводит к довольно нестандартному выбору языков программирования для зрелой отрасли.

На разных этапах жизни системы это были Scheme, OCaml, Scala, Clojure, помимо традиционных C(++) и Java. Основной сервис фильтрации и другие сервисы, помогающие получать и передавать сообщения, были написаны и разработаны на языке Scheme в различных его реализациях (самой последней был Racket).

Как бы ни хотелось воспевать простоту и элегантность этого языка, нельзя не признать, что его развитие отвечает скорее академическим интересам, чем промышленным.

Особенно заметно отставание по сравнению с другими, более современными сервисами Solar Dozor, которые разрабатываются преимущественно на Scala и Clojure. Также было решено реализовать новый сервис на Clojure.

Кложур?!

Здесь, конечно, нужно сказать несколько слов о том, почему мы выбрали Clojure в качестве основного языка реализации.

Во-первых, мне не хотелось терять уникальный опыт команды, разрабатывающей на Scheme. Clojure также является современным членом семейства языков Lisp, и переключиться с одного Lisp на другой обычно довольно легко.

Во-вторых, благодаря приверженности функциональным принципам и ряду уникальных архитектурных решений Clojure обеспечивает беспрецедентную простоту управления потоками данных.

Немаловажно и то, что Clojure работает на платформе JVM, а это значит, что вы можете использовать общую базу с другими сервисами на Java и Scala, а также использовать многочисленные инструменты для профилирования и отладки.

В-третьих, Clojure — лаконичный и выразительный язык.

Это облегчает чтение чужого кода и облегчает обмен кодом с товарищем по команде.

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

REPL-ориентированная разработка дает быстрые результаты, поскольку для проверки работоспособности функции не нужно не только перекомпилировать программу, но даже перезапустить ее (даже если программа представляет собой сервис, расположенный на удаленном сервере).

Забегая вперед, могу сказать: думаю, мы сделали правильный выбор.



Собираем функционал по крупицам

Когда мы говорим о полнофункциональной замене, первый вопрос, который возникает, — это сбор информации о существующем функционале.

Это стало довольно интересной задачей.

Казалось бы, вот работающая система, вот документация к ней, вот люди — специалисты, которые тесно работают с системой и учат других, как это делать.

Но получить целостную картину из всего многообразия, а тем более требований к разработке, оказалось не так уж и просто.

Сбор требований не зря считается отдельной инженерной дисциплиной.

Существующая реализация парадоксальным образом оказывается своего рода «испорченным стандартом».

Там показано, что и как должно работать, но при этом от разработчиков ожидается, что новая версия будет лучше оригинала.

Вам необходимо отделить самое необходимое (обычно связанное с внешними интерфейсами) от того, что можно улучшить, чтобы удовлетворить ожидания пользователей.



Кардиохирургия: как мы переписывали основной компонент DLP-системы

Процесс фильтрации сообщений

документации недостаточно

Какова реальная функциональность системы? Ответ на этот вопрос дают различные описания, такие как пользовательская документация, руководства и архитектурная документация, которые отражают структуру сервиса в различных аспектах.

Но когда дело доходит до этого, ты прекрасно понимаешь, насколько различны идеи и реальность, сколько нюансов и неучтенных возможностей содержит старый код. Хочу обратиться ко всем разработчикам.

Берегите свой код! Это ваш самый важный актив.

Не полагайтесь на документацию.

Доверяйте только оригинальным текстам.

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

Главное — привыкнуть к некоторым отдельным формам, имеющим легкий налет лисповской архаики.



Построение процесса

Объем работы был колоссальный, а команда очень маленькая.

Поэтому возникли некоторые организационные трудности.

Поток ошибок и запросов на исправления (и незначительные улучшения) старой службы фильтрации не собирался прекращаться.

Разработчикам регулярно приходилось отвлекаться на эти задачи.

К счастью, нам удалось отклонить запросы на встраивание новых частей с большим функционалом в старый фильтр.

Правда, под обещание встроить этот функционал в новый сервис.

Однако набор задач по релизу медленно, но верно рос.

Еще одним фактором, который добавлял много хлопот, были внешние зависимости сервиса.

Являясь центральным компонентом, сервис фильтрации использует многочисленные сервисы для распаковки и анализа контента (текстов, изображений, цифровых отпечатков пальцев и т. д.).

Работа с ними частично основывалась на старых архитектурных решениях.

В процессе разработки нам также пришлось переписать некоторые компоненты на современный лад (а некоторые на современном языке).

В таких условиях была выстроена система поэтапного тестирования функционала.

Мы как бы дорастили сервис до определенного состояния, которое закрепили активным тестированием, а затем перешли к внедрению нового.



Начнем разработку

В первую очередь был реализован основной фреймворк сервиса, основные механизмы приема сообщений и распаковки файлов.

Это был абсолютный минимум, необходимый для начала тестирования скорости и корректности работы будущего сервиса.

Здесь необходимо уточнить, что под распаковкой понимается рекурсивный процесс получения частей из файла и извлечения из них полезной информации.

Так, например, документ Word может содержать не только текст, но и изображения, встроенный документ Excel, объекты OLE и многое другое интересное.

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

Еще один комплимент Clojure: мы получили рабочий прототип, в котором наметили контуры будущего функционала, в кратчайшие сроки.



DSL для политики

Вторым этапом стало добавление проверки сообщений с помощью политик фильтрации.

Для описания политик был создан специальный DSL — простой, без излишеств язык, позволяющий представить правила и условия полиса в более-менее удобочитаемой для человека форме.

Он назывался MFLang. Скрипт MFLang на лету интерпретируется в код Clojure, кэширует результаты проверок сообщения, ведет подробный лог работы (и, честно говоря, заслуживает отдельной статьи).

Тестировщикам понравилось использование DSL. Больше не нужно копаться в базе данных или формате экспорта! Теперь можно было просто отправить сгенерированное правило на проверку, и сразу стало понятно, какие условия проверяются.

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

Можно с уверенностью сказать, что MFLang оказался совершенно бесценным инструментом для отладки функционала.



В полную силу

На третьем этапе был добавлен механизм применения к сообщению действий, определенных политикой безопасности, а также сервисные жгуты, позволившие включить в комплекс Solar Dozor новые компоненты.

Наконец-то мы смогли запустить сервис и наблюдать результат работы во всем его многообразии.

Главный вопрос, конечно же, заключался в том, насколько реализованный функционал соответствует ожидаемому и насколько полно он его реализует. Замечу, что если необходимость модульного тестирования уже давно не подвергается сомнению (хотя сами практики TDD до сих пор вызывают оживленные споры), то внедрение автоматизированного тестирования функциональности системы часто встречает открытое сопротивление.

Разработка автоматизированных тестов помогает всем членам команды лучше понять процесс разработки продукта, экономит усилия на регрессии и вселяет некоторую уверенность в производительности продукта.

Но сам процесс их создания сопряжен с рядом сложностей – сбором необходимых данных, выявлением интересующих показателей и тестированием вариантов.

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

Но если удается преодолеть сопротивление, создается достаточно прочный фундамент, позволяющий составить представление о работоспособности системы.



Мы заменяем

А теперь важный момент: мы включили услугу в пакет. Пока вместе со старым.

Таким образом, можно было сменить версию одной командой и сравнить поведение сервисов.

В этом параллельном режиме новый сервис фильтрации существовал один релиз.

За это время нам удалось собрать дополнительную статистику по работе, наметить и реализовать необходимые улучшения.

Наконец, собравшись с силами, мы удалили из продукта старый сервис фильтрации.

Начался финальный этап внутренней приемки, были исправлены ошибки и разработчики начали постепенно переключаться на другие задачи.

Каким-то образом незаметно, без помпы и аплодисментов был выпущен продукт с новым сервисом.

И только когда к нам начали поступать вопросы от команды внедрения, стало ясно, что сервис, над которым мы так долго работали, уже на месте и… работает! Конечно, были ошибки и мелкие улучшения, однако после месяца активного использования клиенты вынесли вердикт: внедрение продукта с новой версией сервиса фильтрации вызвало меньше проблем, чем реализация предыдущих версий.

Привет! Похоже, мы это сделали!

В конце концов

Разработка нового сервиса фильтрации заняла примерно полтора года.

Длительно, чем изначально предполагалось, но не критично, тем более, что фактическая трудоемкость работ совпала с первоначальной сметой.

Гораздо важнее, что нам удалось оправдать ожидания руководства и клиентов и заложить основу для будущих улучшений продукта.

Уже в текущем состоянии видно значительное снижение потребления ресурсов — несмотря на то, что продукт по-прежнему имеет широкие возможности для оптимизации.

Могу добавить немного личных впечатлений.

Замена центрального компонента с долгой историей — это глоток свежего воздуха для развития.

Впервые за долгое время появилась уверенность, что контроль над продуктом возвращается в наши руки.

Трудно переоценить преимущества правильно организованного процесса коммуникации и развития.

В данном случае важно было наладить работу не столько внутри команды, сколько с многочисленными потребителями продукта, имеющими давно сложившиеся четкие предпочтения и ожидания от системы, а также достаточно расплывчатые пожелания.

Для нас это был первый опыт разработки столь масштабного проекта на Clojure. Первоначально были опасения, связанные с динамической природой языка, скоростью и устойчивостью к ошибкам.

К счастью, они не оправдались.

Нам остается только пожелать, чтобы новый компонент работал так же долго и успешно, как его предшественник.

Теги: #информационная безопасность #Продуктовый менеджмент #Анализ и проектирование систем #clojure #dsl #scheme #scheme #dlp #dlp #солнечный дозор

Вместе с данным постом часто просматривают: