Рекомендация контента неизвестным/анонимным пользователям в масштабе

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

Постановка задачи

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

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

Решение

Байесовские бандиты с выборкой Томсона отметили все пункты следующим образом:
1. Для начала не требуется никаких данных или меньше
2. Он изучает входящих пользователей/данные и начинает рекомендовать подходящие баннеры, которые улучшают показатели вовлеченности< br /> 3. Может работать с новыми баннерами, добавленными в качестве рук

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

Развертывание

Сборка и развертывание проекта были разбиты на два технических этапа/этапа:
1. Тестирование модели и документирование результатов в предпроизводственной среде с использованием рабочих данных, определение схемы ввода и вывода для модели, которая будет использоваться командой инженеров данных для создания конвейера потоковой передачи.
2. Настройте модель для использования потоковой передачи данных о событиях в реальном времени и отправки ответов через конечную точку REST с рекомендуемым списком баннеров для пользователей
Внешний интерфейс мобильного приложения настроен на время отклика в одну секунду по сравнению с серверным. Это означало, что приложение попытается сгенерировать динамические баннеры на экране пользователя на основе наших рекомендаций или вернуться к статическим баннерам, если мы не сможем дать ответ в течение секунды, что добавило еще один уровень сложности на втором этапе. Ожидалось, что наши API будут поддерживать широкий диапазон пользовательской нагрузки, начиная с нескольких сотен и заканчивая миллионами запросов по всему региону.

Инфраструктуру развертывания можно разделить на три основных компонента:
1. Надежный конвейер сборки и развертывания
2. Автоматическое тестирование производительности
3. Мониторинг рабочей среды и оповещения.

Инструменты, используемые для полной настройки:
1. Jenkins
2. Artifactory
3. Docker
4. Сканирование образа Aquasec
5. Статическое сканирование кода Fortify
6.. Сканирование открытого исходного кода Sonar Nexus
7. Kubernetes
8. Predator
9. Prometheus
10. Grafana
11. Bitbucket

Наше прикладное решение представляет собой набор образов докеров, которые потребляют/производят контент в темах Kafka.

Шаг 1 — Получение кода и проверка изменений

Наш конвейер начинается с получения кода из репозитория Bitbucket. Мы храним код в структуре папок для 4 разных образов докеров, которые должны быть созданы. Мы проверяем, был ли файл изменен, прежде чем начинать сборку файлов в этой папке. Код в конвейере Jenkins показан ниже для одной из папок с названием «генератор».

# Code to check if any changes made in latest commit in a particular folder
script{
GIT_RESULT = sh(script: '''git diff --quiet HEAD "$(git rev-parse @~1)" -- generator''',
                returnStatus: true
                )
                echo "GIT RESULT -- ${GIT_RESULT} -- ${params.branchname}"
              }

Шаг 2

Следующим шагом является запуск полного статического сканирования кода с помощью Fortify.

sh '''
      echo "=================================================="
      echo "========--- SAST - Fortify Scan: Start ---========"
      echo "=================================================="
      hostname
      whoami
      ls -ahl
      echo 'WORKSPACE: ' $WORKSPACE
      cd $WORKSPACE
      pwd
      sourceanalyzer -v
      sourceanalyzer -b ${fortify_app_name} -clean
      sourceanalyzer -b ${fortify_app_name} -python-version ${python_version} -python-path ${python_path} ${fortify_scan_files}
      sourceanalyzer -b ${fortify_app_name} -scan -f ${fortify_app_name}.fpr
      fortifyclient -url https://sast.intranet.asia/ssc -authtoken "${fortify_upload_token}" uploadFPR -file ${fortify_app_name}.fpr -project ${fortify_app_name} -version ${fortify_app_version}
     '''

Шаг 3

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

sh """
                echo ${ARTIFACTORY_PASSWORD} | docker login -u ${ARTIFACTORY_USERNAME} --password-stdin docker-registry:8443
                cd generator
                docker build --file Docker-dev \
                 --build-arg HTTPS_PROXY=http://ip-address \
                 --build-arg ARTIFACTORY_USERNAME=${ARTIFACTORY_USERNAME} \
                 --build-arg ARTIFACTORY_PASSWORD=${ARTIFACTORY_PASSWORD} \
                 -t ${env.generator_image_latest} .
                docker tag ${env.generator_image_latest} ${env.generator_image_name}
                docker push ${env.generator_image_latest}
                docker push ${env.generator_image_name}
                docker logout docker-pcaaicoe.pruregistry.intranet.asia:8443
                cd ..
        """

Шаг 4

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

sh """
         echo "=================================================="
         echo "=============--- OSS - Nexus Scan ---============="
         echo "=================================================="
                docker save -o generator-dev.tar ${env.generator_image_latest}
                """
                String result = nexusscan("pcaaicoeaipulsenudgesgeneratordev", "$WORKSPACE", "build");
                echo result;
                sh """
                rm -f generator-dev.tar
                """
                sh """
                echo "=================================================="
    echo "=============--- CSEC - Aquasec Scan ---=========="
                echo "=================================================="
                """
                aquasecscan("${env.generator_image_latest}")

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

Шаг 5. Развертывание в AKS

Теперь мы переходим к этапу, на котором мы сможем фактически развернуть и запустить наши образы. Чтобы развернуть наше решение, нам нужны запущенные и работающие Redis DB и кластер Kafka. Мы развертываем наши образы докеров, используя следующий код:

sh '''
            set +x
            echo "---- preparing options ----"
            export HTTPS_PROXY=ip-address:8080
            export KUBE_NAMESPACE="internal-namespace"
            export KC_OPTS=${KC_OPTS}" --kubeconfig=${KUBE_CONFIG}"
            export KC_OPTS=${KC_OPTS}" --insecure-skip-tls-verify=true"
            export KC_OPTS=${KC_OPTS}" --namespace=${KUBE_NAMESPACE}"
            
            echo "---- prepared options ----"
            echo "---- preparing alias ----"
            alias kc="kubectl ${KC_OPTS} $*"
            echo "---- alias prepared ----"
            
            echo "---- applying manifest ----"
   
   
   kc apply -f configmap.yaml
   
   if [ $which_app = "generator" ];then
     if [ $image_version = "latest" ];then
       kc delete deploy ai-pulse-nudges-events-reader||echo
     fi
     sed -i "s!GENERATOR_VERSION!$image_version!g" "generator.yaml"
     kc apply -f generator.yaml
   fi  
   
   if [ $which_app = "aggregator" ];then
     if [ $image_version = "latest" ];then
       kc delete deploy ai-pulse-nudges-click-counter||echo
     fi
     sed -i "s!AGGREGATOR_VERSION!$image_version!g" "aggregator.yaml"
     kc apply -f aggregator.yaml
   fi
   
   if [ $which_app = "detector" ];then
     if [ $image_version = "latest" ];then
       kc delete deploy ai-pulse-nudges-engine||echo
     fi
     sed -i "s!DETECTOR_VERSION!$image_version!g" "detector.yaml" 
     kc apply -f detector.yaml
   fi
   
   if [ $which_app = "restapi" ];then
     if [ $image_version = "latest" ];then
       kc delete deploy ai-pulse-nudges-restapi||echo
        fi
     sed -i "s!REST_VERSION!$image_version!g" "restapi.yaml"
     kc apply -f restapi.yaml
            fi
if [ $which_app = "all" ];then
     if [ $image_version = "latest" ];then
     kc delete deploy ai-pulse-nudges-events-reader||echo
     kc delete deploy ai-pulse-nudges-click-counter||echo
     kc delete deploy ai-pulse-nudges-engine||echo
     kc delete deploy ai-pulse-nudges-restapi||echo
     fi
     
     sed -i "s!GENERATOR_VERSION!$image_version!g" "generator.yaml"
     sed -i "s!AGGREGATOR_VERSION!$image_version!g" "aggregator.yaml"
     sed -i "s!DETECTOR_VERSION!$image_version!g" "detector.yaml" 
     sed -i "s!REST_VERSION!$image_version!g" "restapi.yaml"
     
     kc apply -f generator.yaml
     kc apply -f aggregator.yaml
     kc apply -f detector.yaml
     kc apply -f restapi.yaml
            fi
   
   
   
            echo "---- manifest applied ----"
echo "---- checking result ----"
            
            echo " >> Deployments "
            kc get deployments
            
            echo " >> Services"
            kc get svc
            
            echo " >> Ingress"
            kc get ingress
            
            echo " >> Pods"
            kc get pods
            
            echo "---- Done ----"
          '''

Шаг 6 — Тест производительности

Нам также нужно развернуть Predator — инструмент, который мы используем для тестирования производительности.

sh '''
            set +x
            echo "---- preparing options ----"
            export HTTPS_PROXY=ip-address:8080
            export KUBE_NAMESPACE="internal-namespace"
            export KC_OPTS=${KC_OPTS}" --kubeconfig=${KUBE_CONFIG}"
            export KC_OPTS=${KC_OPTS}" --insecure-skip-tls-verify=true"
            export KC_OPTS=${KC_OPTS}" --namespace=${KUBE_NAMESPACE}"
            
            echo "---- prepared options ----"
            echo "---- preparing alias ----"
            alias kc="kubectl ${KC_OPTS} $*"
            echo "---- alias prepared ----"
            
            echo "---- applying manifest ----"
   
   kc get deploy|grep predator|awk '{print $1 }' || echo
   kc get deploy|grep predator|awk '{print $1 }'|xargs kc delete deploy || echo
   
   for i in `seq $replica_count`
   do
     echo $i
     cp -rf predator/predator.yaml tmp.yaml
     sed -i "s!REPLICA_NO!$i""!g" "tmp.yaml"
     kc apply -f tmp.yaml
   done  
   
   
            # kc apply -f predator/predator.yaml
            
            echo "---- manifest applied ----"
echo "---- checking result ----"
            
            echo " >> Deployments "
            kc get deployments
            
            echo " >> Services"
            kc get svc
            
            echo " >> Ingress"
            kc get ingress
            
            echo " >> Pods"
            kc get pods
            
            echo "---- Done ----"
          '''

Predator — это замечательный инструмент, который позволяет нам использовать существующую инфраструктуру Kubernetes для неограниченного числа пользователей для тестирования. Подробнее об этом инструменте читайте здесь: https://medium.com/zooz-engineering/by-niv-lipetz-software-engineer-zooz-b5928da0b7a8
Мы используем существующие корпоративные Prometheus и Grafana для мониторинга модули приложений.

Уроки, извлеченные на следующий раз:
1. Мы начали писать код конвейера с нуля, тогда как это помогло бы сэкономить время, если бы существовал расширенный тип hello world для пустого конвейера, который можно было бы использовать в качестве структуры шаблона. Это позволило бы нам узнать, какие учетные данные и доступ требуются на каком этапе.
2. Для запуска конвейера требовалось много учетных данных и доступа. Было бы здорово сэкономить время и усилия, если бы у нас был создан один главный идентификатор службы и назначен конвейеру, который затем можно было бы использовать во всех инструментах в организации.
3. Очень сложно построить модель машинного обучения, и потоковые данные в реальном времени были дополнительной сложностью, но производство этой модели с потоковыми данными во много раз сложнее.

Соавторы: Гленн Бейн, Тьен Нгует Лонг, Джон Юэ, Зелдон Тай (郑育忠), Стивен Чау, Денис Панг, Филипп Гшепф, Сиам Банди, Ума Махешвари, Майкл Натуш