В связи с ростом темпов жизни среднестатистического человека и дороговизной содержания собственного автомобиля возрастает спрос к услугам такси. С развитием технологий пришли удобные мобильные приложения, агрегирующие множество компаний, предоставляющих услуги развозки пасса-жиров (таксопарков), и предоставляющие удобный интерфейс. В разработке и поддержке такого приложения возникает много проблем, которые требуют оперативного решения. Одна из таких проблем - мотивация водителей принимать заказы, которые, возможно, им не выгодно исполнять по некоторым причинам (точка А далеко от текущего местоположения кандидата на заказ). Очевидно, что случаи, когда пассажир не может уехать из-за того, что все водители отказываются от поездки, плохо влияет на их лояльность по отношению к сервису. Возникает задача мотивации водителей принимать такие заказы. На начальном этапе это происходило при помощи нескольких правил, внедренных в программный код (например, за три отказа от заказа подряд снять 1 рейтинг), которые абсолютно не покрывали требования бизнеса.
Постановка задачи
Целью работы является разработать такой инструмент для сервиса такси, с помощью которого можно генерировать реакции на события с определенными условиями. События в сервисе такси имеют разную структуру и подразумевают разные состояние заказа. Например, событие reject_manual означает отказ водителя от заказа, а order_complete - успешно выполненный заказ. Под реакцией подразумевается любое действие из заранее заданного множества (отправка push-уведомлений, начисление рейтинга, блокировка). Правила должны быть гибкими и легко конфигурируемыми, чтобы любое их изменение не привлекало к работе инженера программного обеспечения. Пример правила может быть таким: отправить push-уведомление водителю, если за последний час он отказался от трех заказов, расчетная длительность которых не превышала 20 минут. Необходимо создать новый функционал по генерации реакций на определенные события в отдельном микросервисе с удобной и масштабируемой архитектурой. Добавление новой реакции (поддержка начисления другой характеристики водителя за определенные действия) должно занимать не больше двух дней, в зависимости от ее специфики. Так, к примеру, начисление абстрактной величины будет легко поддержать в силу того, что функционал похож на уже имеющееся начисление рейтинга, а вот блокировка водителя будет требовать написания нового модуля. Таким образом приложение должно иметь модульную архитектуру и быть легко имплементируемым.
Реализовать возможность начисления рейтинга на основе заранее за-данного признакового пространства с использованием алгоритма машинного обучения с целью избавиться от необходимости вручную задавать правила, по которым он начисляется, и сделать эту процедуру более гибкой.
Обзор литературы
В силу предполагаемой высокой нагрузки на разрабатываемый сервис обработку событий планируется распределить на несколько потоков. В источнике [2] автор определяет основную проблему параллельной работы про-граммы. Он указывает на условия Бернстайна, благодаря которым можно определить, является ли последовательность команд (или их часть) детерминированной. Псевдопараллельное выполнение набора программ заключается в том, что отдельные процессы внутри себя выполняют инструкции строго последовательно, но при этом между собой управление переходит недетерминированно. Детерминированной называется такая последовательность действий, которая при любом псведопараллельном выполнении отдает один и тот же состав выходных данных. Недетерминированная последовательность действий, которая в ходе выполнения при различных псевдопараллельных выполнениях отдает различный состав выходных данных, называется также критическим местом в приложении. На основе данных определений можно выявить такие места в сервисе и решить каждую проблему по отдельности или обобщить решение на все случаи. Автор перечисляет два подхода для решения - взаимосинхронизация и взаимоисключение. Первый подразумевает отсутствие (предотвращение) пересечения множества входных данных алгоритмов, а второй - синхронизацию и блокировку данных, находящихся в общей памяти. Решение проблемы состояния гонки не должно базироваться на относительном времени выполнения процесса или количества процессоров, потоков (например, основная обработка потока входных данных длится 300 мс, поэтому новые данные отправляем каждые 400 мс). Минус такого подхода, очевидно, в том, что сложно отследить увеличение времени обработки и вовремя подкорректировать связанные с этим показатели. Впоследствии найти ошибку связанную с race condition является нетривиальной задачей.
В [10] автор предоставляет обширное множество советов и рекомендаций в разработке распределенного приложения. В книге описаны способы сохранения порядка сообщений в очереди, журналирование, репликация распределенных баз данных, особенности транзакций. В источнике [3] описаны несколько популярных и широко используемых паттернов проектирования программного обеспечения. Один из описанных там паттернов - шаблон проектирования “Канал событий”, благодаря которому реализована система подписчиков на канал событий (отправление push-уведомлений с помощью API). Паттерн Класс-строитель позволяет реализовать интерфейс действия, которое будет произведено при некоторых, заранее заданных, правилах. Он позволит в зависимости от сработавшего правила легко возвращать необходимый экземпляр класса, что позволит легко добавлять новый функционал. Способ реализации реакций основан на шаблоне Команда. Паттерн команда описывает реализацию интерфейса реакции с описанным необходимым функционалом, от которого наследуются любые действия с имплементацией этих методов.
В статье [9] произведен анализ производительности и точности алгоритмов, основанных на градиентном бустинге: Adaboost, LightGBM, XGBoost и Catboost. Авторы отмечают, что наибольшее качество и наименьшие требования к CPU предоставляет алгоритм машинного обучения, реализованный в библиотеке Catboost.
Сервис обработки потока сообщений, разработанный в рамках первой главы, успешно выполняет поставленную перед ним задачу. Выбранный формат генерации реакций на сообщения покрывает большое множество возможных использований. Возможностей языка python и хорошо продуманной архитектуры оказалось достаточно для выдерживания нагрузки кратной 10 от текущей, что позволяет за крайне небольшой промежуток времени обработать очередь в сотни тысяч событий. Лучший результат из рассматриваемых моделей для предсказания рейтинга за заказ показал метод градиентного бустинга, что вполне объяснимо. Задача интуитивно может быть решена путем построения дерева решений. К примеру, если рассматривать последнего кандидата на заказ, которых было 10, то необходимо предложить максимальный рейтинг за заказ, чтобы клиент не ушел к конкуренту.
В дальнейшем возможны следующие улучшения работы сервиса:
1. Возможность задания реакции при помощи удобного UI с валидацией на исторических правилах
2. Динамическое обучение модели прогноза рейтинга
3. Возможность задавать различные условия для исторических событий и их комбинации (2 отмены заказа и 2 принятия заказа, где была хотя бы одна оплата банковской картой)
[1] Дональд Кнут Искусство программирования, том3. Сортировка и поиск. 2-е изд. М.: Вильямс, 2007.
[2] Основы операционных систем // intuit.ru URL: https://www.intuit.ru/studies/courses/2192/31/info(дата обращения: 23.01.2020).
[3] Приемы объектно-ориентированного проектирования. Паттерны проектирования / Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж., Спб.: Питер, 2001.
[4] About Python // python.org URL: https://www.python.org/(дата обращения: 10.12.2019).
[5] ABM Moniruzzaman, Syed Akhter Hossain NoSQL Database: New Era of Databases for Big data Analytics - Classification, Characteristics and Comparison // arXiv. 2013.
[6] CatBoost is a high-performance open source library for gradient boosting on decision trees // catboost.ai URL: https://catboost.ai/(дата обращения: 10.05.2020).
[7] ECMA-404 The JSON Data Interchange Standard. // json.org URL: https://www.json.org/json-ru.html(дата обращения: 21.02.2020).
[8] Hong Zhu, Patrick A. . Hall, John H.R. May Software unit test coverage and adequacy // ACM Computing Surveys. 1997.
[9] Junaid Magdum, Ritesh Ghorse, Chetan Chaku, Rahul Barhate, Shyam Deshmukh A Computational Evaluation of Distributed Machine Learning Algorithms // IEEE.
[10] Kleppmann M. Designing Data-Intensive Applications. 1-е изд. Sebastopol: O’Reilly Media, 2017.
[11] Monique Borg Inguanez Regularization in Regression: Partial Least Squares and Related Models. Leeds: University of Leeds, 2015.
[12] PL/Python — процедурный язык Python // postgrespro.ru URL: https://postgrespro.ru/docs/postgresql/12/plpython(дата обращения: 12.02.2020).
[13] Open Machine Learning Course. Topic 10. Gradient Boosting // medium.com
URL: https://medium.com/open-machine-learning-course/open-machine-
learning-course-topic-10-gradient-boosting-c751538131ac (дата обращения: 05.05.2020).
[14] Paul C. Jorgensen, Carl Erickson Object-Oriented Integration Testing // ACM Computing Surveys. 1994.
[15] S. BhatnagarH.L. PrasadL.A. Prashanth Stochastic Recursive Algorithms for Optimization. London: Springer, 2013.
[16] scikit-learn Machine Learning in Python // scikit-learn.org URL: https://scikit-learn.org/stable/index.html(дата обращения: 20.05.2020).
[17] T. Chai, R. R. Draxler Root mean square error (RMSE) or mean absolute error (MAE)? - Arguments against avoiding RMSE in the literature // Geosci. Model Dev. 2014. №7.
[18] The Importance of Idempotence // antoineleclair.ca URL: http://antoineleclair.ca/2014/08/15/the-importance-of-idempotence/(да¬та обращения: 20.02.2020).