Ir al contenido
8367

Blog


2022 Artículo sobre los proyectos de prácticas de verano de DoorDash nº 2

26 de octubre de 2022

|
Taige Zhang

Taige Zhang

Shuyang Wu

Shuyang Wu

Zhiyang Zhang

Zhiyang Zhang

Andy Jiang

Andy Jiang

Kenny Chi

Kenny Chi

DoorDash ofrece una experiencia de prácticas inmersiva en la que todos nuestros becarios se integran plenamente con los equipos de ingeniería para obtener una experiencia real en el sector que no se enseña en las aulas. Esta es la segunda entrada del blog de una serie de artículos que muestran nuestros proyectos de prácticas de verano 2022. Si te perdiste el primer artículo, el enlace está aquí. A continuación puedes leer sobre cada proyecto.

Contenido: 


Escalado del programador de tareas para flujos asíncronos

Por Taige Zhang

Db-scheduler es un programador de tareas de código abierto, que utiliza la base de datos relacional como cola de tareas y soporta múltiples instancias del programador para cooperar en la programación de trabajos. Db-scheduler utiliza un modelo de datos más simple y es más escalable que otras soluciones alternativas, como Quartz.

Db-scheduler fue la tecnología de programación elegida por DoorDash para crear nuestro flujo de abandono de carritos (se están estudiando otros casos de uso), que notifica a los usuarios que han añadido artículos a su carrito pero no han realizado la compra. Nuestro objetivo era mejorar las capacidades de ejecución de tareas asíncronas de db-scheduler para que pudiera escalar el rendimiento.

En la Figura 1 se muestra un diagrama de la estructura de db-scheduler:

Figura 1: Db-scheduler programa una gran lista de tareas con un grupo más pequeño de subprocesos.
Figura 1: Db-scheduler programa una gran lista de tareas con un grupo más pequeño de subprocesos.

Por qué necesitamos la ejecución asíncrona de tareas para la programación

En la arquitectura orientada a microservicios actual, una tarea típica suele depender de un conjunto de microservicios externos, lo que implica que una tarea puede contener una o varias operaciones asíncronas (por ejemplo, llamadas gRPC). Por desgracia, el programador de base de datos heredado que utilizaba DoorDash se basa en el modelo de ejecución de threadpool bloqueante. Los hilos permanecen bloqueados durante toda la duración de la tarea. Si una tarea contiene operaciones asíncronas, simplemente bloqueará el hilo, un proceso que parece ineficiente en comparación con el modelo no bloqueante, que permite al planificador utilizar mejor los recursos del hilo en lugar de desperdiciar hilos esperando a que las tareas terminen su ejecución.

Cómo mejoramos las capacidades de ejecución de tareas asíncronas

Nuestra estrategia para mejorar la eficiencia de la programación mediante una mejor ejecución de tareas consistió en introducir APIs de ejecución de tareas basadas en el futuro (el principal cambio de API se muestra en la Figura 2), para desacoplar la programación de tareas (threadpool) de la ejecución de tareas (con operaciones asíncronas).

Para ser más específicos, aprovechamos CompletableFuture para hacer que los planificadores acepten los resultados pendientes de las ejecuciones de tareas, de modo que las tareas puedan ejecutarse de forma asíncrona. Esto supondría otra ventaja, ya que CompletableFuture es compatible con Kotlin Coroutines. Por ejemplo, si tenemos un objeto Deferred, es fácil transferirlo a Java CompletableFuture con el método incorporado Deferred { … }.asCompletableFuture().

Figura 2: Introducimos API de ejecución de tareas basadas en el futuro para permitir la ejecución asíncrona de tareas
Figura 2: Introducimos API de ejecución de tareas basadas en el futuro para permitir la ejecución asíncrona de tareas

Resultados

Realizamos pruebas de carga para confirmar el rendimiento de nuestra mejora. Utilizamos una tarea asíncrona de ejemplo que contiene un retardo de un segundo simulando llamadas asíncronas. Consulte los fragmentos de código del núcleo en la Figura 3:

Figura 3: La tarea de ejemplo se retrasa un segundo y vuelve como CompletableFuture
Figura 3: La tarea de ejemplo se retrasa un segundo y vuelve como CompletableFuture

Se realizaron pruebas con múltiples configuraciones para medir el rendimiento (ejecución/s) del db-scheduler original y del basado en el futuro:

  • Se utilizó PostgreSQL como base de datos.
  • El host contenedor tenía 12 núcleos físicos.
  • El límite inferior era 4,0 y el superior 20,0. Se trata de parámetros que controlan el número de ejecuciones de tareas debidas obtenidas por cada lote.
  • Cada instancia del planificador se ejecutaba en un contenedor independiente utilizando la estrategia lock-and-fetch, que ha demostrado tener un mejor rendimiento cuando hay planificadores compitiendo entre sí.
  • Las pruebas se realizaron con diferentes números de hilos (20, 100, 300).

Tras realizar algunas pruebas, pudimos comprobar si habíamos conseguido aumentar el rendimiento de ejecución de las tareas. A partir de los resultados de las pruebas mostrados en la Tabla 1, podemos ver que hay un gran salto en el rendimiento. Con la misma configuración, el rendimiento del db-scheduler basado en el futuro es mucho mayor que el de la versión original, hasta casi 27 veces cuando hay cuatro instancias del planificador compitiendo entre sí, cada una con 20 subprocesos. Además, el rendimiento del programador original es muy proporcional al número de subprocesos. Sin embargo, el programador db basado en el futuro no guarda una relación lineal con él. Estos resultados indican que hemos conseguido desvincular la ejecución de tareas del proceso de programación.

Además, para alcanzar 285 TPS, el db-scheduler original necesita 300 hilos, mientras que nuestra versión puede superarlo incluso con un solo hilo. Dado que los hilos ocupan recursos de memoria, esta comparación muestra el potencial de una reducción significativa de la sobrecarga de memoria.

Conclusión

En resumen, hemos realizado una mejora del db-scheduler introduciendo importantes cambios en la API basados en el futuro. Las pruebas de rendimiento han demostrado que nuestra mejora puede aumentar significativamente el rendimiento de las tareas y reducir la ocupación de los recursos del sistema. Ahora, el db-scheduler basado en el futuro es más competente para escenarios de alta concurrencia con tareas de larga duración y sistemas escalables. Nuestro trabajo conduciría a una situación en la que todos salen ganando. Lo que hemos hecho puede aportar beneficios a db-scheduler y a toda la comunidad de código abierto, ya que se trata de un cambio crítico. El nuevo db-scheduler también mejorará el rendimiento en casos de uso específicos de DoorDash, como el abandono de carritos.


Mejora de la experiencia de búsqueda de comestibles con la normalización de consultas e índices

Por Shuyang Wu

Parte de proporcionar una experiencia de descubrimiento de productos de calidad incluye mostrar a los clientes los productos que están buscando, pero a menudo puede ser más fácil decirlo que hacerlo. A veces, el motor de búsqueda de DoorDash muestra resultados diferentes con consultas similares, lo que no es una buena experiencia de búsqueda porque las consultas con la misma intención de búsqueda deberían mostrar los mismos resultados. Esta falta de uniformidad se debe a que no añadimos normalización al ejecutar los modelos de clasificación y precisión. La falta de normalización también aumenta la escasez de características que utilizamos en el modelo, lo que puede dar lugar a resultados de búsqueda no optimizados. La normalización toma el significado básico de una palabra, ignorando la forma (por ejemplo, pluralización, tiempo, etc.). Si se aplica correctamente, la normalización puede mejorar los resultados de búsqueda de los clientes.

Figura 1: Una búsqueda de comestibles sin la normalización adecuada produjo resultados incoherentes para consultas similares "uva" y "uvas".
Figura 1: Una búsqueda de comestibles sin la normalización adecuada produjo resultados incoherentes para consultas similares "uva" y "uvas".

Por qué la falta de normalización crea una experiencia de búsqueda negativa

Para consultas como "uva" y "uvas", es probable que los clientes tengan exactamente la misma intención de búsqueda y, por lo tanto, deberían ver los mismos resultados. Sin embargo, a veces nuestros resultados de búsqueda difieren mucho para estas dos consultas. Como muestra la figura 1, si los clientes buscan "uvas" pero no "uva", hay menos opciones entre las que elegir. Dado que ambas consultas tienen la misma intención de búsqueda, deberían mostrar los mismos resultados y no presentar una caída significativa.

Esto nos lleva a preguntarnos: ¿por qué nuestras búsquedas no muestran los mismos resultados? En Elasticsearch(ES), tenemos una normalización interna que convierte tanto "egg" como "eggs" en "egg", por lo que los elementos recuperados de esa consulta en ES deberían ser los mismos. El problema de la normalización se produce durante la parte de clasificación y filtrado del proceso que se ejecuta después de la recuperación, que es donde nos centramos principalmente. La normalización se produce en ES, pero el servicio de búsqueda obtiene documentos con los campos originales sin normalizar. Por lo tanto, utilizamos las consultas y los campos de artículo originales para calcular las características léxicas del texto del modelo y, a continuación, se muestran resultados diferentes a los consumidores de DoorDash. La falta de normalización también afecta a la calidad de estas características textuales y disminuye la relevancia general de la búsqueda, porque sin normalización, consideraríamos equivalentes diferentes palabras y diferentes formas de la misma palabra, y además aumentaría la espasticidad de las características y conduciría a resultados más pobres.

Para medir el rendimiento de la normalización de la importación y si puede mejorar la experiencia de búsqueda, utilizaremos principalmente las siguientes cuatro métricas basadas en la búsqueda para la evaluación:

  • Tasa de añadir al carrito (ATCr): el porcentaje de búsquedas que termina con un Cx añadiendo un artículo al carrito. 
  • Porcentaje de clics (CTR): el porcentaje de búsquedas que termina con un Cx haciendo clic en un elemento.
  • Tasa de búsqueda perfecta (PSR): porcentaje de búsquedas que terminan con una Cx que añade a su cesta un artículo situado en las 10 primeras posiciones de los resultados de búsqueda.
  • Tasa de búsquedas nulas (NSR): porcentaje de búsquedas que terminan con cero resultados mostrados a un Cx.

Cómo aplicar la normalización

Para la implementación, la idea básica es extraer la normalización de Elasticsearch. Implementamos nuestros propios métodos de normalización basados en las mismas características de Apache Lucene que utilizamos en ES. Para aplicar los mismos métodos a los campos de consulta e ítem para hacerlos coincidir, añadimos la normalización antes de enviarlos a ES en el lado de recuperación, y también al enriquecer los datos de menú y catálogo en el lado de indexación. De este modo, después de que se produzca la coincidencia en ES, podremos obtener elementos con campos normalizados para calcular las características de texto.

En DoorDash nos gustaría obtener resultados tempranos para probar la hipótesis con el mínimo esfuerzo. Además, como rellenar los campos de cada artículo en ES lleva mucho tiempo y es difícil eliminar los campos añadidos, no es buena idea hacer pruebas directamente en ES. La aplicación se dividió en tres pasos

  • Primero probamos distintos métodos de normalización a nivel local, filtrando manualmente los que presentaban una latencia elevada o un rendimiento deficiente. 
  • A continuación, aplicamos el resto de métodos tras la recuperación para realizar una comparación ligera. Con un ligero aumento de la latencia, normalizamos la consulta y todos los campos de los artículos durante el tiempo de búsqueda, de forma que pudiéramos utilizarlos para calcular y mejorar las características del texto. A continuación, realizamos el experimento en línea y hallamos el método de normalización más adecuado en los escenarios de comestibles. 
  • El último hito del proyecto fue trasladar la normalización del tiempo de ejecución a la indexación. Para reducir la latencia, utilizamos el método de normalización seleccionado para rellenar los nombres y descripciones normalizados de los elementos en los documentos de indexación, de modo que pudiéramos acceder directamente a ellos sin necesidad de realizar cálculos en tiempo de ejecución.

Personalización de la normalización en una tienda de comestibles

Además de contar con características optimizadas del modelo y resultados estables para los clientes, aplicar nuestra propia normalización también podría darnos la posibilidad de personalizar mejor la normalización en el escenario de la tienda de ultramarinos. Al principio del proyecto, basándonos en lo que utilizábamos en ES, propusimos una docena de combinaciones diferentes de algoritmos de normalización, y seleccionamos la mejor tras rondas de experimentos.

También con la personalización, eliminamos los acentos en lengua latina no inglesa, para que los clientes pudieran obtener artículos en "creme" y "crème" al buscar cualquiera de ellos, lo que se ajusta a los hábitos de escritura de diferentes personas, resuelve el problema de los nombres de artículos mal escritos y también ayuda a la gente a buscar palabras no inglesas en Estados Unidos como "jalapeño" o "L'Oréal". También hemos normalizado las puntuaciones, incluyendo la fusión de comillas rectas y rizadas, y convirtiendo algunos símbolos comunes en su nombre para obtener coincidencias más precisas en ES.

Interacciones con otras funciones del servicio de búsqueda

La búsqueda no se limita a hacer coincidir la consulta y los artículos, también ofrecemos otras funciones para mejorar la experiencia de búsqueda de los clientes. Para asegurarnos de que cada función funciona, también es importante tener en cuenta las interacciones entre la normalización y estas funciones.

  • Corrección ortográfica: corrige los errores tipográficos automáticamente. La normalización pretende trabajar sobre las palabras correctas, por lo que debería ejecutarse después de la corrección ortográfica. Como permitimos a los clientes desactivar el corrector ortográfico, lo que significa que a los clientes les gustaría buscar exactamente lo que han escrito, también deberíamos desactivar la normalización y evitar editar la consulta.
  • Sinónimos - búsqueda de elementos relacionados. Los sinónimos funcionan en ES, por lo que debemos evitar normalizar y cambiar la consulta en la lista de sinónimos o la búsqueda de sinónimos no se activará. Por lo tanto, debemos mantener la misma lista de sinónimos en el servicio de búsqueda que en ES.
  • Autocompletar - facilita la escritura. Por la misma razón que el corrector ortográfico, dado que autocompletar tiende a funcionar para palabras parciales, deberíamos desactivar la normalización en esta situación. Cuando los clientes hagan clic en las sugerencias de consulta de autocompletar, proporcionadas por nosotros y correctas, el corrector ortográfico quedaría desactivado y lo mismo ocurriría con la normalización.
  • Idiomas - Como el método de normalización propuesto pretende funcionar en el contexto inglés, inicialmente pensamos limitarlo a las búsquedas en inglés. Pero al proporcionar la eliminación de acentos para búsquedas no inglesas, observamos una mejora significativa en las métricas del experimento. Así que planeamos mantenerlo para todas las búsquedas.

Resultado

Según nuestro experimento, con la nueva normalización propuesta, pudimos observar una disminución del NSR de alrededor del 3% relativo, un ligero aumento del ATCr y el CTR de alrededor del 0,5% relativo y un PSR neutro. Es más, con datos limitados para búsquedas en otros idiomas, pudimos observar una mejora significativa de un 20% de disminución en NSR y un 13% de aumento en PSR, y ATCr y CTR neutros. Tampoco observamos un aumento notable de la latencia en las búsquedas de comestibles después de añadir la normalización.


Potenciación del etiquetado de productos alimenticios y comerciantes con los candidatos a la taxonomía de agrupación y el proceso de publicación

Por Zhiyang Zhang

El servicio de catálogo de alimentos dedicado de DoorDash utiliza modelos ML para etiquetar los alimentos y los comerciantes en toda la plataforma, lo que mejora la precisión de los resultados de búsqueda y las recomendaciones.Para construir un modelo ML predictivo que impulse estas categorizaciones, necesitamos proporcionar al equipo de ML datos de taxonomía que tengan suficiente cobertura. Anteriormente, teníamos un proceso manual que requería un gran esfuerzo para proporcionar al equipo de ML datos de taxonomía y podía dar lugar a numerosos errores humanos. Para ampliar la cobertura de la taxonomía de nuestro catálogo de alimentos de forma más eficiente y proporcionar una única fuente de verdad para la taxonomía, necesitábamos un proceso automatizado para importar y liberar trabajos. Automatizar estos procesos y proporcionar API permitiría a nuestro equipo de taxonomía centrarse en el trabajo más urgente de refinar y ampliar la taxonomía de forma activa.

Los problemas del flujo de trabajo de ampliación de la taxonomía heredada

Anteriormente, la producción de una versión madura de la taxonomía (una colección de taxonomías que se pueden publicar) requería una gran implicación por parte del equipo de taxonomía:

  1. En primer lugar, construir una taxonomía significaba filtrar manualmente los datos sucios para encontrar candidatos valiosos, que son los alimentos que gustan a mucha gente pero que no existen en la taxonomía actual. Este proceso comenzó separando cada día los candidatos valiosos a la taxonomía de los datos brutos.
  2. A continuación, una vez importadas las taxonomías candidatas a Graphite de Synaptica (nuestra plataforma de organización de taxonomías), el equipo de taxonomía las revisa y organiza en versiones de taxonomía maduras.
  3. Una vez actualizada la taxonomía, es necesario actualizar esa nueva versión en nuestra base de datos de servicios de taxonomía. Este proceso implica la exportación manual de la taxonomía como CSV y la carga manual en la tabla Snowflake de taxonomía.

Ahora, la taxonomía actualizada está en la base de datos, y el equipo de ML puede utilizar la taxonomía más reciente para entrenar el modelo y el servicio de catálogo de alimentos puede etiquetar los alimentos o los comerciantes existentes utilizando el modelo. 

La figura 1 muestra un desglose de este proceso.

Figura 1: Flujo de trabajo de ampliación de la taxonomía heredada
Figura 1: Flujo de trabajo de ampliación de la taxonomía heredada

Todo el proceso descrito anteriormente lleva mucho tiempo y presenta varios problemas:

  • Poca eficacia y escasa escalabilidad: El equipo de taxonomía espera tener una expansión activa de la taxonomía. Este proceso es demasiado lento para iteraciones rápidas
  • Falta de pipeline de liberación: El equipo de ML depende de la taxonomía para entrenar modelos y el servicio de catálogos de alimentos también necesita consumirla. Nos falta una forma de que los equipos importen y utilicen fácilmente la versión más reciente. 
  • Control de versiones ad hoc: El control de versiones también es manual y no está centralizado. Es difícil establecer una correspondencia entre la versión de la taxonomía y la versión del modelo de ML cuando actualizamos nuevas versiones de la taxonomía.

Ejecución diaria de un proceso ETL para agrupar las taxonomías candidatas y una API para liberar las versiones maduras de las taxonomías.

Para resolver los problemas de eficiencia, fiabilidad y manualidad mencionados anteriormente, hemos creado un trabajo ETL que se ejecuta a diario para agrupar las taxonomías candidatas y también hemos creado un punto final gRPC para liberar las versiones maduras de las taxonomías. Esta canalización liberará a nuestro equipo de taxonomía del doloroso proceso de importación y exportación.

En el proceso de importación, ejecutamos un trabajo ETL de Airflow cada día para obtener los datos en bruto y, a continuación, utilizamos algoritmos de agrupación para encontrar candidatos populares (los alimentos que gustan a mucha gente pero que no existen en la taxonomía de DoorDash). Después, podemos importar todos estos candidatos a Graphite a través de su API.

En el proceso de exportación, el equipo de taxonomía puede realizar una simple llamada gRPC y, a continuación, nuestro servicio de gestión de versiones de taxonomía registrará la información de publicación, lo que permite el seguimiento y la gestión de las versiones del historial. Por último, nuestro servicio escribirá las versiones maduras de la taxonomía en la tabla Snowflake que proporciona las taxonomías como metadatos de los modelos ML de formación. Nuestro servicio puede escribir varias versiones simultáneamente.

De este modo podemos automatizar el aspecto altamente manual de la recogida y filtrado de datos de taxonomía.

Figura 2: Proceso automatizado de agrupación de candidatos y publicación de versiones taxonómicas
Figura 2: Proceso automatizado de agrupación de candidatos y publicación de versiones taxonómicas

¿Qué es la agrupación y cómo encontrar los candidatos a taxonomía popular?

En las decenas de miles de datos de anotación en bruto, muchos datos tienen pocas diferencias y representan la misma taxonomía. Para encontrar tal taxonomía, necesitamos agrupar estos datos similares en un grupo. Como resultado, el tamaño de la agrupación puede indicar la popularidad de la taxonomía, y los datos más frecuentes en esta agrupación es probable que sea el candidato taxonomía. Nos hemos esforzado mucho en el diseño de este proceso crucial de agrupación, ya que es la base de todo el flujo de trabajo. Nos gustaría compartir los detalles de cómo conseguimos una gran precisión en este proceso crítico.

  • En primer lugar, aplicamos la normalización del texto para limpiar los datos sucios antes de introducirlos en el algoritmo de agrupación.
    • Al proceder de elementos de menú de DoorDash, los datos de anotación en bruto tienen muchos formatos extraños y no deseados, como paréntesis de medición (como "(30 ozs)"), número de secuencia (como "c6.") y puntuaciones (como ";"). Todas estas partes no deseadas darán lugar a agrupaciones poco cohesivas porque no tienen ningún significado en la taxonomía.
    • Así que primero utilizaremos expresiones regulares para normalizar caracteres de puntuación como "&" a "y" y para eliminar todos los caracteres innecesarios. A continuación, aplicamos funciones como lower() y trim() para realizar una normalización adicional del texto y eliminar los caracteres de espacio en blanco iniciales y finales. Tras aplicar la normalización del texto, el tamaño de los grupos disminuye más de un 15%.
  • En segundo lugar, ajustamos el modelo a los datos limpios utilizando DBSCAN como algoritmo de agrupación, junto con la distancia Levenshtein como métrica.
    • Entre muchos algoritmos de agrupación, elegimos el algoritmo de agrupación DBSCAN teniendo en cuenta la geometría no plana de nuestro conjunto de datos y el mejor rendimiento de DBSCAN en comparación con otros algoritmos de agrupación populares.
    • Como la métrica existente no se aplica al conjunto de datos de cadenas, recurrimos a la distancia Levenshtein. Levenshtein calculará la distancia de edición entre cada par de datos brutos y, a continuación, transformará la matriz de cadenas en la matriz de números, en la que DBSCAN puede ajustar un modelo.
    • Como resultado, los datos de anotación en bruto se agrupan de tal forma que los nombres de entidades de un mismo clúster son más similares entre sí que los de otros clústeres. Así, el tamaño de un clúster refleja la popularidad con la que es elegido por la gente. A continuación, podemos importar a Graphite los candidatos que sólo tengan suficiente popularidad.

Al final, esto permitiría al equipo de taxonomía trabajar en un puñado de candidatos importantes en lugar de procesar varios miles de candidatos redundantes y distribuidos aleatoriamente.

Impacto de importar sólo candidatos populares y proporcionar la API gRPC para liberar la taxonomía

Nuestro pipeline puede manejar automáticamente decenas de miles de datos sin procesar e importar los 50 candidatos más frecuentes a Graphite. Esto ahorra al equipo de taxonomía horas cada día filtrando candidatos a taxonomía y exportando versiones maduras de taxonomía. Esto significa que pueden ser más eficientes y centrarse más en ampliar la taxonomía de forma eficiente. Además, proporciona al equipo de ML la única fuente de tablas de verdad para las versiones de taxonomía y pueden consumir y entrenar modelos fácilmente.

En conjunto, podríamos conseguir eliminar horas de mano de obra a la semana con este esfuerzo y sustituir procesos manuales repetitivos y propensos a errores por procesos automatizados y canales adecuados. Así, nuestro equipo de taxonomía y nuestro equipo de ML pueden centrarse en su propia área dedicada a mejorar nuestra experiencia de usuario y satisfacer las necesidades de facilidad y comodidad de los consumidores.


Optimización y migración del sistema de anuncios Dasher fuera de Nimda

Autor: Andy Jiang

Actualmente, DoorDash depende en gran medida de los correos electrónicos, los SMS y las notificaciones push para comunicarse con los Dashers; sin embargo, no podemos controlar estos canales, lo que nos impide adaptarlos a casos de uso específicos. Debido a que las comunicaciones como correos electrónicos y SMS pueden tener problemas de entrega o depender de servicios de terceros, hemos estado cambiando lentamente hacia anuncios dentro de la aplicación, que ayudan a los usuarios a obtener información y recursos en nuestra plataforma sin tener que salir de nuestra aplicación. Esta comunicación es especialmente importante para los Dashers, nuestro nombre para los repartidores, porque completar un Dash, nuestro nombre para una entrega, puede implicar varias notificaciones que son más fáciles de digerir en una sola experiencia de aplicación. Lamentablemente, no podemos aprovechar al máximo las notificaciones dentro de la aplicación como canal de comunicación con los Dasher debido a los problemas de fiabilidad de la herramienta heredada. Aquí hablaremos de cómo hemos podido construir una nueva herramienta web para el sistema de anuncios Dasher y ampliar sus capacidades.

Visión general de los anuncios de Dasher in-app 

Los operadores utilizan el sistema de anuncios de la aplicación para presentar mensajes a los Dasher en la aplicación. Muestra actualizaciones importantes y puede localizarse pulsando el icono de la campana en la pantalla Dash de la aplicación Dasher (véase la Figura 1). 

Figura 1: Pantalla de anuncios de la aplicación Dasher.

La principal herramienta que utilizamos para crear y desplegar anuncios en Dashers es con Nimda, que es una herramienta de administración empaquetada y construida dentro del framework Django.

Los problemas del antiguo sistema de anuncios Dasher 

Nuestro antiguo canal de comunicación in-app Dasher no satisfacía todas nuestras necesidades. El sistema heredado presentaba dos problemas principales.

Figura 2: Herramienta interna heredada utilizada por los operadores para gestionar los anuncios.

Por un lado, la herramienta era poco fiable y su interfaz de usuario poco intuitiva (véase la Figura 2). El uso de anuncios ha superado a Nimda a medida que lo ampliamos a públicos más amplios, y los operadores suelen tener problemas con la herramienta. Además, la herramienta no se diseñó para crear anuncios dirigidos a un público amplio. La herramienta sólo permite a los operadores enviar anuncios a un máximo de 10.000 Dashers a la vez y la selección de una gran mayoría de los submercados en la herramienta hará que la solicitud se agote y no se envíe nada. Los operadores tampoco pueden programar los anuncios y sólo pueden hacer envíos puntuales en tiempo real, lo que hace que esta tarea manual sea menos cómoda y más propensa al error humano.

Figura 3: Lista de anuncios de puntos finales que causan una alta utilización de la CPU en los pods.

El segundo problema es que hay un cuello de botella en el esquema actual, ya que la lista de identificadores Dasher de la lista blanca se guarda como un bloque JSON dentro del modelo de datos. Esto es un problema, ya que para determinar los anuncios relevantes para mostrar a los Dasher, tendríamos que analizar la lista blanca JSON blob para determinar si el Dasher ID está dentro de ella. Esta lógica provoca picos de CPU y alta latencia (> 600ms) cuando hacemos esto con anuncios que contienen una lista blanca con miles de IDs (ver Figura 3).

Cómo pudimos solucionar estos problemas 

Antes de adentrarnos en nuestra solución, es importante comprender la arquitectura actual del sistema de anuncios Dasher.

Parte 1: Implementación de puntos finales gRPC de anuncios CRUD

Figura 4: Esquema de arquitectura que muestra la implementación de puntos finales gRPC de anuncios en dasher-service, utilizados por la capa BFF (admin-gateway).

Si nos fijamos en el esquema de la arquitectura, anteriormente Nimda gestionaba todas las operaciones relacionadas con la actualización/eliminación/creación de anuncios y los clientes móviles simplemente enviaban una solicitud a la capa Dasher Mobile back-for-frontend (BFF) que se comunicaba con Dasher Service (microservicio Dasher) para obtener los anuncios apropiados para mostrar a los Dashers.

Así que primero extrajimos el anuncio Dasher de Nimda implementando puntos finales gRPC de anuncio CRUD en el servicio Dasher (ver Figura 4). Esta extracción nos permite modificar la lógica subyacente de las operaciones de anuncio para adaptarlas a nuestros casos de uso. También aprovechamos el procesamiento asíncrono y el control granular de los valores de latencia y tiempo de espera para resolver los problemas de tiempo de espera con la creación de anuncios. 

Parte 2: Implementación de puntos finales gRPC de anuncios CRUD

Figura 5: Esquema de arquitectura que muestra la implementación de la caché de redis en los puntos finales CRUD gRPC en Dasher-service y también el punto final de lista existente. 

Además, para resolver el cuello de botella que provoca la alta latencia al listar anuncios, decidimos almacenar en caché la columna de la lista blanca cuando la creamos (véase la Figura 5).

Figura 6: Esquema de arquitectura que muestra el proceso anterior (sin caché) de creación y recuperación de anuncios relevantes para los Dashers

Para dar un poco de contexto, antes de utilizar una caché, durante el proceso de creación, tomamos el nuevo objeto de anuncio Dasher y lo almacenamos en la base de datos Dasher. Después, para listar los anuncios apropiados para los Dasher, tomamos todos los anuncios posibles que podrían ser relevantes para ese Dasher y para cada objeto de anuncio Dasher, analizamos la lista blanca y comprobamos si el ID del Dasher está en la lista (ver Figura 6). Ahora bien, al hacer esto en listas blancas con más de 10.000 IDs para varios anuncios, esto puede causar una latencia extremadamente alta y degradar nuestros pods.

Figura 7: Esquema de arquitectura que muestra el nuevo proceso (con caché) de creación y recuperación de anuncios relevantes para los Dashers.
Figura 7: Esquema de arquitectura que muestra el nuevo proceso (con caché) de creación y recuperación de anuncios relevantes para los Dashers.

Para mejorar estos procesos y reducir la latencia, decidimos almacenar en caché la columna de la lista blanca al crear anuncios. Almacenamos en caché la columna utilizando Redis hash con la clave siendo el ID del anuncio y luego agrupando todos los Dasher IDs en esa lista blanca bajo la clave como campos para hacer más fácil obtener la lista blanca de un anuncio y también distinguir entre caché perdido vs no almacenado en caché. Entonces, sólo tendríamos que comprobar si el announcement_id + Dasher ID está en la caché utilizando una serie de comandos Redis para determinar si un anuncio debe ser mostrado a un Dasher (ver Figura 7). 

Parte 3: Creación de la nueva herramienta web

Figura 8: Esquema de arquitectura que muestra cómo se construyeron la capa BFF (admin-gateway) y el frontend para dar soporte a la nueva herramienta web de anuncios.

A continuación, para construir la herramienta web, incorporamos anuncios a Forward, que es un marco interno para la gestión de entidades con formularios JSON, en admin-gateway. Para ello, creamos un nuevo cliente para que los puntos finales gRPC de los anuncios se comunicaran con los puntos finales que creamos en el servicio Dasher (que es nuestro microservicio Dasher), añadimos consultas GraphQL de anuncios, definimos el esquema JSON utilizado por el frontend para la representación con una nueva definición de Forward y creamos grupos de permisos específicos para los anuncios (véase la figura 8).

Por último, para la parte del frontend, utilizamos el componente EntityForm con renderizadores y componentes personalizados del sistema de diseño interno Prism para construir la interfaz de usuario en el repositorio web.

Resultados

Con la nueva herramienta web (véase la figura 9), hemos podido reducir de 8 horas a unos minutos el tiempo empleado por los operadores en crear y desplegar anuncios. Comparando la anterior herramienta Nimda con la nueva herramienta web, Nimda ofrece una experiencia de usuario subóptima, con frecuentes problemas de tiempo de espera, y no admite la carga de grandes audiencias. Sin embargo, esta herramienta permite a los operadores seleccionar países en lugar de tener que seleccionar manualmente cientos de submercados y admite la carga de grandes audiencias con Dasher ID de listas blancas superiores a 10.000.

Figura 9: La nueva herramienta web que estamos desplegando

Además, con nuestra solución, pudimos eliminar la dependencia del anuncio en Nimda y resolver el cuello de botella con el diseño del esquema mediante el uso de la caché Redis. Esto soluciona los picos de CPU, la alta latencia (> 600 ms), los tiempos de espera frecuentes y reduce la latencia de nivel superior (percentil 99) en ~30 ms.


Creación de una herramienta de análisis de registros para mejorar la experiencia del operador de DashMart

Por Kenny Chi

DashMart es un equipo de New Verticals creado para entregar artículos de tiendas de conveniencia a los clientes a través de un MFC (Micro-Fulfillment Center), un almacén propiedad de DoorDash. Uno de los principales enfoques del equipo de ingeniería de DashMart en los últimos meses es mejorar la claridad del operador de DashMart, permitiéndole ver cada acción dentro de una instalación: recepción de artículos, almacenamiento, movimiento, picking, etc. 

Problemas con la gestión del inventario a nivel de ubicación

Se podría pensar que el recorrido de un artículo a través de un DashMart es sencillo: recibir, almacenar, recoger, vender. Sin embargo, dentro de esos pasos, hay muchos puntos de fallo posibles: mover el inventario a una ubicación diferente a la especificada, inventario dañado accidentalmente, problemas de sincronización de menús, etc. 

Actualmente, los operadores de DashMart pueden ver la cantidad total disponible (OH) de un artículo en sus instalaciones. Por ejemplo, el gestor de inventario muestra que hay 24 bolsas de patatas fritas en las ubicaciones A y B. Sin embargo, al echar un vistazo a las ubicaciones, el operario se da cuenta de que sólo hay 24 en la ubicación B. Esto se debe a que se "almacenaron" 24 patatas fritas en la ubicación A y posteriormente se confirmó que había 24 unidades en la ubicación B. 

Figura 1: Datos de registro de las acciones descritas anteriormente
Figura 1: Datos de registro de las acciones descritas anteriormente

En este caso, un operario colocó accidentalmente los artículos en el contenedor equivocado al hacer el aprovisionamiento (dando 24 al contenedor A), y otro operario ajustó más tarde el contenedor B para que tuviera también 24 bolsas de patatas fritas.

Diagnóstico de discrepancias de inventario en DashMarts

Antes de este proyecto, los operarios locales y los jefes de obra tenían que buscar en los cuadros de mando en línea o pedir a un ingeniero en Slack que consultara en la base de datos el historial de un artículo para diagnosticar la discrepancia entre lo que ven en el sitio web de inventario y lo que ven realmente en el contenedor. 

Para empezar a dar a los operadores una mejor visión del historial de los artículos, decidimos crear un nuevo punto de acceso en la base de datos, que permitiera a los operadores hacer clic en un artículo para ver su historial en ese DashMart. 

Figura 2: Demostración de la página desde la perspectiva de un operador

Cómo dimos acceso a los operadores a los registros de artículos

Para exponer los datos de registro de artículos en el sitio web de front-end, necesitábamos crear un nuevo punto final de API para acceder y filtrar nuestra tabla de registro, que anteriormente era de sólo escritura. Aunque la consulta se realizaba en una tabla de gran tamaño compartida por todos los DashMarts, pudimos optimizar la consulta SQL filtrando dos de los índices almacenados en la tabla: ID de instalación e ID de artículo. 

En la página web de inventario, la interfaz de usuario permite a los usuarios filtrar aún más las actividades del artículo por tipo, usuario y fecha. Esto permitirá a los operadores investigar muchas incoherencias de cantidad de OH, lo que reducirá los INF (Inventario no encontrado) en todos los DashMarts.

Impacto

Este proyecto ofrece a los operarios visibilidad de los datos que hay detrás de cada una de las acciones en un DashMart. En lugar de ponerse en contacto con el director de la planta o con un ingeniero de DashMart para consultar manualmente la base de datos, ahora pueden saber dónde se vieron los artículos por última vez y su recorrido en la planta. Con más de 100 DashMart abiertos en todo el mundo, esto mejorará enormemente la capacidad de los operarios para ver las operaciones de inventario en un DashMart y, en última instancia, hacerse cargo de forma independiente de sus instalaciones.


About the Authors

Trabajos relacionados

Ubicación
Pune, India
Departamento
Ingeniería
Ubicación
Pune, India
Departamento
Ingeniería
Ubicación
Pune, India
Departamento
Ingeniería
Ubicación
San Francisco, CA; Sunnyvale, CA; Seattle, WA
Departamento
Ingeniería
Ubicación
San Francisco, CA
Departamento
Ingeniería