image

Привет, хабр. Многие слышали про наш сервис, кто-то пользовался, и вот мы созрели до того, чтобы рассказать про свою внутреннюю IT-кухню. Мы начинали в 2014 году с квартиры-офиса на Арбате (с переговоркой в кухне), 300 клиентов и организацией всего “руками”. Вся информация фиксировалась в экселе, а разработкой и не пахло. Со временем количество клиентов увеличивалось, потребовалась автоматизация, и сегодня Qlean — это зрелая компания, в которой отдел разработки насчитывает более 25 человек. Сегодня через наш сервис делается в среднем около 1000 уборок в день силами 3000 подключенных к системе исполнителей. Мы третьи в мире после зарубежных Handy и Helpling по объемам уборок, и работаем в Москве, Санкт-Петербурге и Екатеринбурге. В этом посте мы проведем экскурсию по системам нашего сервиса и проанонсируем дальнейшие темы блога.

Технологии и как мы их реализовываем


image

Мы не зацикливаемся на каком-то языке программирования, но исторически сложилось так, что сервис был построен на Ruby on Rails с coffeescript и прочими чудесами. Сейчас от всего этого осталось только api, а от рельс — ActiveRecord и набор стандартных гемов. Мы не сильно упарываемся в модные нынче TrailBlazer и прочее, так как стараемся писать простой и понятный код, который просто поддерживать. Помимо основного приложения у нас есть микросервисы на чистом Ruby, например, для CRM, и системы найма исполнителей, несколько сервисов на Go и парочка на Clojure. Сервисы стараемся делить по принципам DDD, то есть каждый сервис отвечает за какой-либо бизнес-процесс: найм, оплату заказа, назначение исполнителя и т.п. Язык выбираем исходя из конкретной задачи и требований. Весь фронтенд у нас на React+Redux, а мобильные приложения недавно полностью переписали на React.Native, что позволяет писать меньше кода и даёт возможность реализовывать один и тот же функционал на разных платформах (и сайт, и мобильные приложения) силами одних и тех же разработчиков. О профите и трудностях перевода нативных приложений на React.Native мы расскажем дополнительно.

Всего у нас около 30 различных сервисов, которые живут на более чем 70 виртуальных машинах, поэтому выкладка кода не самая тривиальная: мы используем Ansible, Docker и Nomad в качестве оркестратора. При выкладке прогоняется огромное количество тестов (как юнит, так и интеграционных), плюс мы всё проверяем силами QA инженеров, используем canary deploy для сложных задач, поэтому мы не боимся много и часто выкладывать. В неделю у нас в среднем 15 релизов.

А подробнее?


image
Система состоит из 3-х больших функциональных частей: работа с клиентами, исполнителями и распределением заказов по исполнителям.

Работа с клиентами


Для общения с клиентами у нас есть сайт и мобильное приложение. Тут в принципе всё примерно стандартно: маркетинг, маркетинг и ещё раз маркетинг. За одним большим нюансом: мы повёрнуты на аналитике, поэтому отслеживаем и анализируем все действия, что совершает пользователь. Со всех устройств эвенты поступают на микросервис на Go, который обрабатывает и складывает информацию в базу данных. Так как мы любим всё мерять, у нас сильно развита культура экспериментов и А/B тестов. Любую гипотезу мы сначала проверяем, и на механику одного и того же функционала может быть одновременно до 5 разных реализаций, начиная от каких-то мелких вещей вроде формы ввода данных кредитной карты и заканчивая моделями штрафов и подписок. У каждого эксперимента есть свой дашборд в системе аналитики (мы используем Periscope) и метрики, по которым мы сравниваем. Всегда есть контрольная группа и мы следим за тем, чтобы эксперимент был «чистым», то есть группы пользователей были максимально идентичными. Про аналитику и A/B тесты мы расскажем отдельно.

Работа с клинерами


Любой сервис строится на исполнителях. В нашем случае исполнители приходят в квартиры клиентов, так что их качество и профессионализм очень важны. Прежде чем стать нашим клинером, человеку надо пройти несколько процедур: собеседование, проверку службы безопасности, инструктаж и тест на знание регламентов, выдачу инвентаря. До недавнего времени все эти процедуры происходили полностью в оффлайне, что накладывало свои ограничения на запуск сервиса в других регионах: нужен склад, нужен офис, нужен отдельный колл-центр. Сейчас же мы экспериментируем с полностью автоматизированной системой найма и снабжения исполнителя. И если с наймом проблем не возникло, то со снабжением ещё предстоит долгий путь: мы пока не умеем автоматически контролировать инвентарь клинера. В любом случае основные наши страхи пока не оправдались: система удалённого найма не влияет на качество исполнителей и сервиса. Поэтому мы будем развиваться в этом направлении дальше.

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

Распределение заказов


Вообще, с распределением заказов связан наш основной челлендж. Основные мотивации клинера работать через систему — стабильный заработок и гарантированный поток заказов. Кажется, что всё просто: назначил клинеров на заказ и вперёд, пусть машут шваброй. На деле оказывается, что кто-то готов убираться только по средам после обеда, кто-то не любит кошек и балконы, а кто-то хочет убирать только трёшки в Бутово. Со стороны клиентов тоже есть предпочтения, но другого характера: мы должны минимизировать количество разных клинеров, в идеале за 10 заказов клиенту должно приезжать 2-3 клинера, ведь мало кому понравится, что его постельное бельё трогает 50 незнакомых людей в год. Получается, что система должна учитывать все эти предпочтения и подбирать заказы по наилучшему совпадению. Казалось бы, классическая задача о назначениях но, так как исполнитель может отказаться от назначенного заказа, который ему не понравился, задача становится нетривиальной. А если учесть, что клиент может внезапно вообще перенести или отменить заказ, то мы получаем очень жёсткие требования на быстродействие алгоритма. Мы используем сложные модели, предсказывающие вероятность отмены заказа, вероятность выхода клинера на конкретный заказ и автоматически подбираем клинеру заказ на который она скорее всего согласится по истории её предыдущих заказов. Наша следующая статья как раз прольёт свет на эту тему.

В любом случае, после назначения остаётся какое-то количество неразобранных заказов, они разбираются самими клинерами через приложение. Сейчас мы тестируем модель с аукционом и динамической ценой в зависимости от количества отказов от конкретного заказа. После ручного забора остаётся уже незначительное количество неразобранных заказов. За сутки до заказа наш колл-центр вручную прозванивает наиболее подходящих исполнителей, чтобы клиенты не остались без клинера. Иногда случается и такое, что мы не можем подобрать исполнителя вообще никак (в основном из-за поздних переносов), но мы работаем над минимизацией этого параметра (сейчас до 5 заказов в день остаются без исполнителей).

На самом деле существует ещё множество деталей системы: собственная CRM, работа со складом, но для короткой обзорной статьи уже и так много информации.

Будем рады ответить на ваши вопросы.



Промокод на первую уборку для пользователей хабра: habr
Github

Комментарии (35)


  1. therhino
    03.04.2018 15:37
    -2

    Какой-то зоопарк для такого простого сервиса.


    1. ZloyHobbit
      03.04.2018 15:55
      +2

      Почему бы не ипользовать наиболее удобный инструмент для каждой задачи? В целом DDD на это и ориентирован, и это куда лучше возьни с монолитом и вебсокетами на руби.


      1. therhino
        03.04.2018 16:38
        +2

        По микросервису на каждый из 10 запросов в сутки — это же наверное удобнее в поддержке и разработке? Всего 1000 заказов в сутки, но языков я уже насчитал 5 (ruby, node, clojure, python, go).
        Надо вебсокеты — бери и пили монолит на ноде. Хотя я уверен, что и long-polling'a на jquery и рельсе с головой хватит.
        Короче, я так понял, компания не любит легких решений, палит из пушки по вообразимому хайлоаду и каждый пишет на том, на чем хочет.


        1. ZloyHobbit
          03.04.2018 16:46
          +3

          Если не хочешь иметь отдельных ios/android разработчиков с соответсвующими языками, а хочешь общий интерфейс для web и мобильного приложения, то берешь react/react-native, который на js и автоматом тянет ноду.
          Потом у тебя становится больше тысячи клиентов, работающих друг с другом и ты хочешь знать, как лучше совмещать их потребности — нужен анализ данных и тут же появляется питон. Неужели переписать numphy на руби было бы проще?
          Какие-то задачи становятся очень высоконагруженными и хочется их распараллелить — появляется go, как наиболее подходящий для этого язык. В rails сообществе не просто так появилась реализация вебсокетов AnyCable, с бэком на go, руби для подобного просто не годится.
          Из всего вышеперечисленного мне только кложа непонятна, но может там что-то хорошо описывающееся функциональщиной крутится.


          1. eao197
            03.04.2018 17:43

            Какие-то задачи становятся очень высоконагруженными и хочется их распараллелить — появляется go, как наиболее подходящий для этого язык.
            А какие задачи у вас становятся «высоконагруженными»? И что значит «высоконагруженными»?


            1. ZloyHobbit
              03.04.2018 17:53

              Это вы меня спрашиваете? Я лично go не использую )
              Хотя какой-нибудь общий сервис авторизации для микросервисной архитектуры начал бы писать на нем, скорее всего. Или чатик, или балансир запросов к чему-нибудь. Вариантов много.


              1. eao197
                03.04.2018 18:01

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


            1. unka Автор
              03.04.2018 18:10

              У нас в сутки выполняется 1000 заказов. Но нужно понимать, что хитов у нас гораздо больше. Суммарная медианная загрузка балансировщиков в районе 100rps. Да, половина из этого статика, но остальное — это заходы пользователей: работа с личным кабинетом, подпиской; различные акции, лендинги и тд. Перед нами стояла задача собирать аналитику по каждому действию пользователя как на сайте, так и в мобильном приложении и складывать это в нашу аналитическую базу. Мы взяли на клиентах решение на базе snowplow, допилили его под свои нужды, а в качестве бекенда написали простой сервис на Go, цель которого валидировать и обрабатывать поступающие данные и класть в базу в нужном формате. Ruby для этого по нашим тестам не подходил, поэтому выбрали go. Нагрузка тут не при чём, просто под конкретную задачу выбрали конкретный инструмент.


              1. therhino
                03.04.2018 19:20
                -1

                Ruby для этого по нашим тестам не подходил, поэтому выбрали go.

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

                Противоречие?

                Как всегда, «проблема» в том, что руби медленный. Это же какая хайлоад нагрузка, аж целых 100rps (отсюда еще надо отнять указанную вами статику)! Интересно, как это гитхабы и бейзкампы справляются с этим всем? Может кто-то не умеет его готовить? Та и словом «готовить» в данном случае сложно назвать даже.
                Мне кажется, что ваш отдел разработки можно было бы уменьшить с 25 человек до 3, если не заниматься херней.


                1. ZloyHobbit
                  03.04.2018 20:58

                  Это как-то совсем далеко от адекватности. Если вы посмотрите GoUsers, то найдете там и github и basecamp.
                  У вас какая-то обоснованная ненависть к микросервисной архитектуре, или просто кампания не нравится?


                  1. therhino
                    03.04.2018 22:15

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


              1. ZloyHobbit
                03.04.2018 21:02

                Я думаю, классический вопрос: «А почему не элексир?»
                Какие-то preformance тесты проводились?
                Сможете написать что-то на подобии habrahabr.ru/post/351012 по своему выбору?


                1. unka Автор
                  04.04.2018 11:12

                  На момент выбора эликсир не рассматривался как production-ready. Перфоманс тесты проводили, но по конкретному кейсу написать настолько подробно уже не сможем. Однако, в планах стоит написать подробную статью про переход с нативных приложений на react native: на что смотрели, как тестировали, как и почему делали выбор.


        1. SADKO
          03.04.2018 16:56

          … смотрите не ужасайтесь, некоторое время назад я изучал Qlean по просьбе потенциального конкурента, и тогда мне показалось что эта компания ради компании…
          Так часто выглядят проекты мажоров, в хорошем смысле этого слова, и разводил, в плохом. Компания делается нарочито правильно и модно с точки зрения менеджмента оной, разводилам так легче привлекать инвестиции и отмазываться потом, а мажоры просто верят что так правильно и наверняка взлетит (и иногда оно действительно взлетает, но очень редко)…
          Не знаю что у них сейчас, но этот пост, он «каг-бэ намекае»


  1. devpony
    03.04.2018 17:13

    Расскажите ещё про аналитику, мне кажется, она у вас достаточно интересная.


    1. unka Автор
      03.04.2018 17:57

      да, обязательно расскажем в следующей статье


  1. niko1aev
    04.04.2018 08:34

    image

    Что-то странное происходит в королевстве Qlean. За 6 месяцев по данным similarweb посещаемость сайта упала в 10 раз! Similarweb так сильно ошибаться не может.
    Сейчас основной трафик — прямые заходы, походу закончились деньги на рекламу ((

    И да, на 123k визитов в месяц 30 микросервисов / 70 виртуалок — явно многовато)
    Да и на 1.5 млн визитов в месяц достаточно 1 выделенного сервера, и Ruby on Rails прекрасно это тянет.


    1. unka Автор
      04.04.2018 12:03

      Спасибо за годный комментарий.
      На самом деле тут очень интересный опыт, который, возможно, достоин отдельного поста. В 2017 году мы очень экстенсивно развивались: запустили несколько городов, жгли кучу денег на маркетинг, скидки, акции, даже пробовали рекламу на ТВ. В какой-то момент мы заигрались и не сразу поняли, что результаты довольно грустные: да, у нас стабильно росла пользовательская база, мы регулярно отмечали рекорды по количеству уборок, но при этом падали когорты, средний чек и, вообще, всё что касалось юнит экономики. В итоге, в ноябре приняли решение что пока низкий сезон (в январе-марте традиционно меньше уборок) мы займёмся работой над ошибками: автоматизируем то что не было автоматизировано, оптимизируем операционку, настроим сегменты и поймём что кому можно предлагать. Сейчас мы опять вкладываемся в маркетинг, но теперь у нас гораздо больше опыта, знаний и понимания того чего мы хотим и как этого достигнуть. В итоге, оглядываясь назад, мы воспринимаем эту ошибку как очень дорогой, но ценный опыт.

      Про микросервисы и выделенный сервер — тут я не вижу связи. Суммарная вычислительная мощность всей нашей системы небольшая и весь наш парк сервисов спокойно поместится на 3 сервера (третий для резервирования). Такое количество сервисов связано не с нагрузкой, а с логическим делением на какие-то небольшие независимые бизнес процессы: crm, найм исполнителей, аналитика, оплата/создание заказа и тд, плюс разделение на front и back.


      1. niko1aev
        04.04.2018 20:27

        Спасибо за развернутый ответ)

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

        Например мне не ясен смысл отдельного микросервиса для оплаты.
        Добавление оплаты в основном сервисе — это 1-2 контроллера, 1 gem, config, несколько полей в базе, несколько env значений окружения. Всё. А вот в отдельном сервисе придется делать дополнительно к этому:
        — config
        — подгрузку .env
        — deploy
        — логирование
        — отлов ошибок
        — модель Order
        — config database.yml
        — ORM
        etc
        А если все это еще и на отдельном языке, то список «дополнительного» становится еще длиннее.

        Я понимаю, зачем, например, Lenta делает микросервис для отдачи feed новостей на мобильные. У них во время каких-то критичных новостей и событий очень сильный пик нагрузки. А вот зачем вам — не понимаю. Данные по нагрузке приводил как раз к тому, что Rails вашу нагрузку держат без напряга, даже без горизонтального масштабирования.

        Но это холивар последний нескольких лет и он явно выходит за рамки этой статьи и тем более комментов)


  1. norlin
    04.04.2018 11:51

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


    1. unka Автор
      04.04.2018 13:17

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


      1. norlin
        04.04.2018 16:33

        Регулярная уборка такой квартиры у нас занимает максимум час-полтора (когда один человек убирается), если не торопиться. Ещё раз – речь про регулярную еженедельную уборку, а не про генеральную.


    1. niko1aev
      04.04.2018 20:33

      Не соглашусь с вами. У нас много лет приходила уборщица убирать квартиру 100м2.
      Два раза в неделю часа по 4. Это со стиркой, сушкой белья, глажкой.

      И не было разделения на генеральная уборка или нет. Просто если весна — то мылись окна, не все за раз, а по одному за приход. Когда-то мылся шкаф, когда-то холодильник. Когда-то чистился диван. Зато всегда чисто)

      Так что 4 часа, особенно если есть ребенок, на регулярную уборку — IMHO это нормально.


      1. norlin
        04.04.2018 20:34

        4 часа на 100 квадратов + стирка/сушка/глажка vs. 5-6 часов на 70 кв.м. только регулярная уборка. По-моему, есть разница...


        1. niko1aev
          04.04.2018 20:40

          В неделю: 8 часов на 100м2 || 5-6 часов на 70м2 — не такая уж и разница большая

          И еще надо учесть, что в нашем случае квартира в целом чистая, потому что убиралась 3-4 дня назад. Думаю, что Qlean неоднократно сталкивался с не очень чистыми квартирами, которые быстро не уберешь. Представьте себе 70м2, которые не убирали полгода?) Отсюда и запас по прочности + как правильно выше отметили, время на знакомство, и изучение, где что и как.


          1. norlin
            04.04.2018 20:43

            Хм, если у вас эти два раза в неделю задачи по уборке вообще не пересекаются (считаем это за одну уборку, а не за две с некоторыми отличиями) – то это тоже очень долго, ИМХО. Я вот не представляю для нас как это вообще может работать. Разве что все уходят на работу на целый день и никого в квартире нет.


            1. niko1aev
              04.04.2018 20:44

              Ну да. В нашем случае всегда так и было)
              Пришел вечером домой и чисто)) Приятно)


            1. alex_tewpin
              05.04.2018 01:06

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

              Сидеть над клинером нет нужды, на качество работы это не влияет.


              1. norlin
                05.04.2018 09:18

                Это всё понятно, я как раз про то и говорю – нет возможности уходить из дома на 5 часов всей семьёй.


  1. slavenski
    04.04.2018 15:25

    Спасибо за обзор, статья легко читается, и интересно! Вопрос вот в чем, я, наверное, не уловил идею. То есть при любом изменении в заказах (т.е. отказался исполнитель, отказался заказчик) происходит глобальный пересчет всего? И правильно ли я понял, задача в том, чтобы оптимально распределить исполнителей между заказчиками? Спасибо)


    1. rstrekalov
      04.04.2018 16:06

      Происходит пересчет, но не всего.
      Если отказался исполнитель, то нужно подобрать нового. Изначально, когда заказ создался, ему подбирается набор подходящих исполнителей и автоматически назначается лучший. Если он отказался, то подбирается следующий и т.д.
      Если заказчик перенес уборку, то исполнитель слетает с заказа (если у него есть другой в это время), либо остается на заказе. Если исполнитель слетел, то подбираем следующего, как написано выше.
      Если заказчик отказался, то никого искать не нужно)))
      Да, задача в том, чтобы оптимально распределить ВСЕ заказы без участия нашего колл-центра.


      1. slavenski
        04.04.2018 16:11

        Спасибо за ответ =)


  1. alexstup
    04.04.2018 16:30

    Прошу прощения, но более 25 человек — это сколько? 25 с половиной?


    1. unka Автор
      04.04.2018 16:36

      практически :) на текущий момент 27 человек в it отделе (QA/Front/Back).


  1. therhino
    05.04.2018 17:37
    +1

    О, 25 (поправка, 27) чел уже мало! Они еще ищут moikrug.ru/vacancies/1000041413
    чтобы поддерживать монстра, которого наворотили. Красавцы, давайте выходите уже на уровень «каждому отдельному клиенту по микросервису (или даже 2)».