Меня зовут Денис Семёнов, я Senior Team Lead в Luxoft. Слаженная работа IT и банков сейчас кажется уже обычной. Мы привыкли делать переводы в один клик, ежедневно смотреть аналитику по своим инвестициям, пополнять вклады и считать затраты в приложениях. А что насчёт крупных клиентов банков? Я расскажу, как они проверяют данные по своим портфелям, при чём здесь оперативность и что могут улучшить программисты в финансовых расчётах.
Маржин — залог, обеспечивающий возможность получить во временное пользование кредит деньгами или товарами, которые используются для совершения спекулятивных биржевых сделок при маржинальной торговле.
Что было?
В среднем расчёт маржина составлял от 50 до 70 минут в зависимости от количества данных. На объём рыночных данных, которые получает и обрабатывает наша система, влияют объём торгов, волатильность на рынках и даже сезонность.
Почему было важно ускорить время расчёта? Проблема в том, что, если в данные от наших апстримов закралась ошибка (например, отсутствовал курс обмена для какой-нибудь из мировых валют) и необходимо было срочно перепрогнать расчёт, то требовалось от полутора до трёх часов, чтобы исправить ошибку, перегрузить данные, перезапустить батч и предоставить новые отчёты. В таких экстренных условиях, когда счёт времени важен, 50 минут, которые требовал расчёт маржина, — это слишком много.
Отсюда и возникла задача от топ-менеджмента — ускорить вычисления, насколько это возможно в рамках существующей архитектуры. С лёгкой руки нашего менеджера этот проект получил название Ten X (10Х) — ускорение в 10 раз (по аналогии с базой данных Times Ten от Oracle). Надо понимать, что название Ten X было взято из головы и первоначально никто не понимал, насколько удастся ускориться в реальности. Со стороны менеджмента банка эта задача нашла поддержку, и на её решение были выделены время и ресурсы.
Почему так?
Архитектура системы была сделана таким образом, что сам расчёт клиентской группы дробился на множество отдельных вычислений, которые хранились в базе данных (у нас для этих целей использовалась Cassandra) так же, как и промежуточные результаты. Для того, чтобы выполнить шаг, сервер загружал данные из базы, производил вычисления и потом сохранял промежуточные результаты обратно. Таким образом операции ввода-вывода существенно замедляли общую производительность системы.
Хороша или плоха архитектура, доставшаяся нам от предыдущих разработчиков, — вопрос дискуссионный, но общая логика подсказывала, что не всё в порядке. В среднем у нас от 800 до 900 фондов, у каждого фонда максимум до 50 000 позиций (позиция — это конкретной ценная бумага, находящаяся на счету у клиента) — по современным меркам такой объём данных даже не близок к big data. Расчёт маржина для 4 миллионов позиций (по факту и того меньше) на пяти серверах не должен занимать 50 минут. Анализ этого вычислительного движка позволил найти места, которые можно улучшить, не переписывая его заново. Оставляя внутреннюю реализацию engine, мы изменили некоторые вещи, касающиеся хранения и использования калькуляции и связанных с ней объектов.
Что решили сделать?
Очевидным было решение по максимуму перенести все вычисления в память. Для этого нам нужно было изменить архитектуру самого движка.
Для этого мы сделали три шага:
1 шаг — вынос всей reference data (информация о составе продукта, цены, данные о торгах) на кэширующие сервера (Gemfire) и разогрев этих серверов перед батчем (batch) — непосредственным расчётом;
2 шаг — минимизация количества операций ввода-вывода для вычислений, при этом требовалось максимально задействовать оперативную память;
3 шаг — оптимизация памяти. Эта задача вытекала из предыдущей: если мы хотим держать всё в памяти, то использование памяти должно быть максимально эффективно. В архитектуре системы имелась одна проблема, которая при хранении объектов в базе данных была не очень существенной, но при хранении всех вычислений в памяти создавала ограничения по её расходу. Суть проблемы в следующем: если у нас есть ссылка из одного бизнес-объекта на другой (например, объект Position имеет ссылку на объект Product), то для хранения подобной ссылки в поле объекта резервируется количество памяти, равное размеру объекта.
Каждый инстанс класса Position содержал в себе шэллоу-копию объекта Product, откуда нам нужен был только первичный ключ, а все остальные поля не использовались. Загрузка реального инстанса объекта происходила при вызове метода getProduct() у Position, который перехватывал аспект и, используя первичный ключ из продукта, запрашивал реальный объект из базы данных или из Gemfire-кэша. На примере объекта Product мы увидели, насколько данная схема хранения ссылок между объектами неэффективна: класс Product имел более 100 различных полей, но реально использовалось только 16 байт, зарезервированных для UUID.
Для решения данной проблемы мы решили использовать динамические подклассы, сгенерированные при помощи cglib. Чтобы минимизировать вмешательства в существующий код, генерация подклассов для хранения ссылок между объектами была реализована поверх существующей реализации, т. е. когда в поле объекта устанавливалась ссылка на другой объект, вместо создания целого объекта создавался динамически созданный инстанс класса, в котором из полей был только первичный ключ.
В нашем примере с классом Position в поле product теперь помещался инстанс динамически созданного класса Product$1, который реализовывал интерфейс Product. Таким образом экономия только на одном объекте составляла сотни байтов, но учитывая, что подобным образом мы изменили способ хранения ссылок между всеми бизнес-объектами, то экономия шла уже на десятки мегабайт.
Кроме того, мы ещё добавили пул для хранения ссылочных объектов, и теперь, если у нас было несколько объектов, ссылающихся на бизнес-объект типа Product c одинаковым ключом, то все они использовали тот же самый инстанс класса Product$1. Данная оптимизация также позволила обрабатывать даже самые большие фонды с размером памяти для JVM (heap) в 8 ГБ. Это принесло и другое преимущество, так как в скорости наше приложение должно было быть мигрировано в банковский k8s-клауд, в котором существующее ограничение для докер-контейнера составляет 8 ГБ.
Как стало?
После того, как все три вышеизложенных шага были реализованы, время расчёта батча для всех клиентов сократилось с 50–60 минут до 5–8 минут в зависимости от объёма рыночных данных на текущий день. Собственно при возникновении необходимости перезапуск всех наших вычислений из-за неправильных данных от апстримов превратился из долгого ожидания в достаточно рутинную операцию, в которой подготовка к перезагрузке данных в базу иногда занимает больше времени, чем собственно вычисление.
В итоге мы ускорили расчёт в 10 раз и выполнили задачу, как и хотели клиенты. Не зная изначально, что будет в результате, мы сделали 10Х.
Кто клиент?
Клиентами банка являются крупные хедж-фонды, размер инвестиций которых может достигать десятки миллиардов долларов. Т. е. это не индивидуальный инвестор, который собрал несколько тысяч и хочет попробовать себя на фондовом рынке (для таких существуют различные брокеры типа Interactive Brokers), а компания с сотнями сотрудников, которая управляет финансами и пенсионными накоплениями миллионов граждан США и всего мира.
Где клиент сталкивается с нашим решением?
Для клиентов банка есть портал, где можно посмотреть ежедневный финансовый отчёт, который говорит о том, сколько денег они должны держать на счетах в банке, чтобы в случае ликвидации портфеля по какой-то причине банк не понёс убытки. Настройка параметров маржин-методологий подбирается индивидуально для каждого клиента и является строго конфиденциальной (как впрочем, и всё остальное, начиная с имени клиента и состава его портфеля до размера маржина, который он платит банку, и размера капитала на счетах).
Методология расчёта, если говорить про США, состоит из двух частей: minimum — то, что спускается регулятором, FINRA (Агентство по регулированию деятельности финансовых институтов США), которое требует минимальный размер маржина на определённый размер портфеля, и house — внутренние правила банка. К примеру, пользователь — риск-менеджер — может, меняя параметры house-методологии, запускать вычисления и смотреть, как изменится маржин, если он чуть ослабит требования для определённых типов финансовых инструментов.
Помимо вычислений, которые идут в пакетном батче — когда мы делаем расчёт для всех клиентов — есть ещё и индивидуальные вычисления. К примеру, в ситуации, когда тот же риск-менеджер хочет проанализировать состояние портфеля клиента и запускает вычисления только для него.
Для этой задачи понимать банковскую сферу не обязательно, но нельзя разрабатывать то, чего не понимаешь. Поэтому базовые знания всё равно приходится получать. К примеру, на Инвестопедии — сайте, где хорошо описаны ключевые моменты.
Немного о нашем опыте
Мы сотрудничаем с одним из крупнейших международных банков около 10 лет, за это время было сделано несколько крутых и долгосрочных проектов по направлению бизнес-девелопмента. По требованию клиентов мы разрабатываем новые методологии расчёта маржина для различных типов ценных бумаг: акций, облигаций, опционов, фьючерсов и т. д. Среди технических задач — обновление фреймворков, миграция с устаревших библиотек на новые, работа с платформой, которая отвечает за маркетмейкинг — поддержание котировок в жизненном состоянии на рынках по отдельным специфичным видам продуктов. Об одном из проектов я сейчас расскажу.Ускорение расчёта
В прошлом году к нам обратились с задачей под проект «10Х» = ускорение расчёта маржина в 10 раз для существующего объёма фондов.Маржин — залог, обеспечивающий возможность получить во временное пользование кредит деньгами или товарами, которые используются для совершения спекулятивных биржевых сделок при маржинальной торговле.
Что было?
В среднем расчёт маржина составлял от 50 до 70 минут в зависимости от количества данных. На объём рыночных данных, которые получает и обрабатывает наша система, влияют объём торгов, волатильность на рынках и даже сезонность.
Почему было важно ускорить время расчёта? Проблема в том, что, если в данные от наших апстримов закралась ошибка (например, отсутствовал курс обмена для какой-нибудь из мировых валют) и необходимо было срочно перепрогнать расчёт, то требовалось от полутора до трёх часов, чтобы исправить ошибку, перегрузить данные, перезапустить батч и предоставить новые отчёты. В таких экстренных условиях, когда счёт времени важен, 50 минут, которые требовал расчёт маржина, — это слишком много.
Отсюда и возникла задача от топ-менеджмента — ускорить вычисления, насколько это возможно в рамках существующей архитектуры. С лёгкой руки нашего менеджера этот проект получил название Ten X (10Х) — ускорение в 10 раз (по аналогии с базой данных Times Ten от Oracle). Надо понимать, что название Ten X было взято из головы и первоначально никто не понимал, насколько удастся ускориться в реальности. Со стороны менеджмента банка эта задача нашла поддержку, и на её решение были выделены время и ресурсы.
Почему так?
Архитектура системы была сделана таким образом, что сам расчёт клиентской группы дробился на множество отдельных вычислений, которые хранились в базе данных (у нас для этих целей использовалась Cassandra) так же, как и промежуточные результаты. Для того, чтобы выполнить шаг, сервер загружал данные из базы, производил вычисления и потом сохранял промежуточные результаты обратно. Таким образом операции ввода-вывода существенно замедляли общую производительность системы.
Хороша или плоха архитектура, доставшаяся нам от предыдущих разработчиков, — вопрос дискуссионный, но общая логика подсказывала, что не всё в порядке. В среднем у нас от 800 до 900 фондов, у каждого фонда максимум до 50 000 позиций (позиция — это конкретной ценная бумага, находящаяся на счету у клиента) — по современным меркам такой объём данных даже не близок к big data. Расчёт маржина для 4 миллионов позиций (по факту и того меньше) на пяти серверах не должен занимать 50 минут. Анализ этого вычислительного движка позволил найти места, которые можно улучшить, не переписывая его заново. Оставляя внутреннюю реализацию engine, мы изменили некоторые вещи, касающиеся хранения и использования калькуляции и связанных с ней объектов.
Что решили сделать?
Очевидным было решение по максимуму перенести все вычисления в память. Для этого нам нужно было изменить архитектуру самого движка.
Для этого мы сделали три шага:
1 шаг — вынос всей reference data (информация о составе продукта, цены, данные о торгах) на кэширующие сервера (Gemfire) и разогрев этих серверов перед батчем (batch) — непосредственным расчётом;
2 шаг — минимизация количества операций ввода-вывода для вычислений, при этом требовалось максимально задействовать оперативную память;
3 шаг — оптимизация памяти. Эта задача вытекала из предыдущей: если мы хотим держать всё в памяти, то использование памяти должно быть максимально эффективно. В архитектуре системы имелась одна проблема, которая при хранении объектов в базе данных была не очень существенной, но при хранении всех вычислений в памяти создавала ограничения по её расходу. Суть проблемы в следующем: если у нас есть ссылка из одного бизнес-объекта на другой (например, объект Position имеет ссылку на объект Product), то для хранения подобной ссылки в поле объекта резервируется количество памяти, равное размеру объекта.
Каждый инстанс класса Position содержал в себе шэллоу-копию объекта Product, откуда нам нужен был только первичный ключ, а все остальные поля не использовались. Загрузка реального инстанса объекта происходила при вызове метода getProduct() у Position, который перехватывал аспект и, используя первичный ключ из продукта, запрашивал реальный объект из базы данных или из Gemfire-кэша. На примере объекта Product мы увидели, насколько данная схема хранения ссылок между объектами неэффективна: класс Product имел более 100 различных полей, но реально использовалось только 16 байт, зарезервированных для UUID.
Для решения данной проблемы мы решили использовать динамические подклассы, сгенерированные при помощи cglib. Чтобы минимизировать вмешательства в существующий код, генерация подклассов для хранения ссылок между объектами была реализована поверх существующей реализации, т. е. когда в поле объекта устанавливалась ссылка на другой объект, вместо создания целого объекта создавался динамически созданный инстанс класса, в котором из полей был только первичный ключ.
В нашем примере с классом Position в поле product теперь помещался инстанс динамически созданного класса Product$1, который реализовывал интерфейс Product. Таким образом экономия только на одном объекте составляла сотни байтов, но учитывая, что подобным образом мы изменили способ хранения ссылок между всеми бизнес-объектами, то экономия шла уже на десятки мегабайт.
Кроме того, мы ещё добавили пул для хранения ссылочных объектов, и теперь, если у нас было несколько объектов, ссылающихся на бизнес-объект типа Product c одинаковым ключом, то все они использовали тот же самый инстанс класса Product$1. Данная оптимизация также позволила обрабатывать даже самые большие фонды с размером памяти для JVM (heap) в 8 ГБ. Это принесло и другое преимущество, так как в скорости наше приложение должно было быть мигрировано в банковский k8s-клауд, в котором существующее ограничение для докер-контейнера составляет 8 ГБ.
Как стало?
После того, как все три вышеизложенных шага были реализованы, время расчёта батча для всех клиентов сократилось с 50–60 минут до 5–8 минут в зависимости от объёма рыночных данных на текущий день. Собственно при возникновении необходимости перезапуск всех наших вычислений из-за неправильных данных от апстримов превратился из долгого ожидания в достаточно рутинную операцию, в которой подготовка к перезагрузке данных в базу иногда занимает больше времени, чем собственно вычисление.
В итоге мы ускорили расчёт в 10 раз и выполнили задачу, как и хотели клиенты. Не зная изначально, что будет в результате, мы сделали 10Х.
Для кого делали проект?
Это проект для клиентов, чьим основным брокером является наш банк. У нас есть SLA (service level agreement) — согласно ему, до 7:00 утра по времени США у нас должны быть готовы отчёты о размерах маржина, доступного для наших клиентов. Если они недоступны, то возможны штрафные санкции — выплаты клиентам со стороны банка.Кто клиент?
Клиентами банка являются крупные хедж-фонды, размер инвестиций которых может достигать десятки миллиардов долларов. Т. е. это не индивидуальный инвестор, который собрал несколько тысяч и хочет попробовать себя на фондовом рынке (для таких существуют различные брокеры типа Interactive Brokers), а компания с сотнями сотрудников, которая управляет финансами и пенсионными накоплениями миллионов граждан США и всего мира.
Где клиент сталкивается с нашим решением?
Для клиентов банка есть портал, где можно посмотреть ежедневный финансовый отчёт, который говорит о том, сколько денег они должны держать на счетах в банке, чтобы в случае ликвидации портфеля по какой-то причине банк не понёс убытки. Настройка параметров маржин-методологий подбирается индивидуально для каждого клиента и является строго конфиденциальной (как впрочем, и всё остальное, начиная с имени клиента и состава его портфеля до размера маржина, который он платит банку, и размера капитала на счетах).
Методология расчёта, если говорить про США, состоит из двух частей: minimum — то, что спускается регулятором, FINRA (Агентство по регулированию деятельности финансовых институтов США), которое требует минимальный размер маржина на определённый размер портфеля, и house — внутренние правила банка. К примеру, пользователь — риск-менеджер — может, меняя параметры house-методологии, запускать вычисления и смотреть, как изменится маржин, если он чуть ослабит требования для определённых типов финансовых инструментов.
Помимо вычислений, которые идут в пакетном батче — когда мы делаем расчёт для всех клиентов — есть ещё и индивидуальные вычисления. К примеру, в ситуации, когда тот же риск-менеджер хочет проанализировать состояние портфеля клиента и запускает вычисления только для него.
Кто занимался проектом?
Решением занималось восемь человек из команды: Java-разработчики, PM, тестировщики. Задача QA была в том, чтобы мы ничего не сломали, не внесли регрессию в результате изменений в архитектуре.Для этой задачи понимать банковскую сферу не обязательно, но нельзя разрабатывать то, чего не понимаешь. Поэтому базовые знания всё равно приходится получать. К примеру, на Инвестопедии — сайте, где хорошо описаны ключевые моменты.
Как мы сократили расчёт залогового обеспечения в 10 раз
Меня зовут Денис Семёнов, я Senior Team Lead в Luxoft. Слаженная работа IT и банков сейчас кажется уже обычной. Мы привыкли делать переводы в один клик, ежедневно смотреть аналитику по своим...
habr.com