[Case Locomizer] Cómo acelerar el cálculo de un mapa de calor en 20,000 veces en dos años y medio

Este artículo es una continuación de la serie Case Locomizer, ver también


Hola

FDC: TC, EMR, IDEA

¿Sabes qué es una autopsia? Esta es una historia sobre cómo llegamos a esa vida.

No estoy seguro de ti, pero me encanta leer historias sobre el proceso de desarrollo de un software altamente especializado o de bajo nivel. Los colegas pueden tener una idea interesante con la que trabajar, y siempre es curioso seguir lo que sucedió con el programa desde el prototipo hasta el producto maduro, que hace algo de magia en un área temática desconocida.

Además, si solo lanzo un enlace a un repositorio con dicho software, es poco probable que alguien pueda obtener una idea de qué es y por qué, y para qué tareas puede ser útil. Incluso si traduzco del inglés tres docenas de páginas de instrucciones para comenzar. Sin embargo, el marco de Spark no es solo otro oficio en lo angular, debe entenderse que los autores fumaron por qué fue escrito de esta manera y no de otra manera.

Este artículo es una introducción histórica a One Ring. No tiene código, y la historia es más popular que científica. Pero solo sobre el desarrollo, y sobre nada más, excepto por dos años y medio de desarrollo.

La última vez, hablé con suficiente detalle (espero que sea suficiente) sobre las dificultades de extraer datos de conjuntos de datos anónimos en el carril central, y al final me encontré con una intriga no débil. Dejemos su resolución por última vez, y hoy hablaremos sobre el largo y difícil camino hacia la perfección de nuestra herramienta principal:

  • Big data es grande
  • Nuestro caso no es estándar
  • Prototipo en C # y PostGIS
  • Primer acercamiento a Hadoop MapReduce
  • El advenimiento de CI y Spark
  • Tercera aproximación en GeoSpark
  • Analistas japoneses y migración de Azure a AWS
  • Ash Nazg Durbatuluk, Ash Nazg Gimbatul, Ash Nazg Trakatuluk, Ag Burzum Ishi Krimpatul !!
  • Optimización y geocatarsis con Uber H3
  • En todo blanco

Big data es grande


Big data no se trata de tamaño.

Puede haber decenas, o incluso cientos de millones de registros en un conjunto de datos mensual en la región del Gran Londres, pero eso no es mucho. Una sola iteración en ellos desde el principio hasta el final se basa en la velocidad de lectura lineal del disco. Si la unidad es una SSD, tomará unos segundos.

(Le recuerdo que el conjunto de datos en cuestión es un conjunto de archivos CSV con un conjunto de campos específicos para el proveedor. La agrupación de registros con las coordenadas de usuarios anónimos en un archivo se produce a lo largo de las fronteras de la región administrativa del país, prefectura o ciudad. Los archivos se generan en la fecha seleccionada, diaria o mensualmente. Más detalles se describen en la parte anterior, ejecútelos en diagonal si no hay suficiente contexto).

Nuestro proceso es de varios pasos. Las heurísticas iniciales de enriquecimiento de datos sin procesar que funcionan solo en un solo modo de iteración son rápidas, y puede escribirlas al menos en Python, al menos en C ++, incluso en PHP. Incluso en una máquina débil, el procesamiento será rápido.

Si el conjunto de datos está en algún lugar de la nube, entonces, siempre que el controlador se coloque en la misma nube, no hay ningún problema en particular para acceder, repasar y guardar el resultado al lado. Además, generalmente el archivo ya está allí, porque los proveedores de datos con gran placer subirán el archivo a su almacenamiento en la nube, lo que le dará un enlace de descarga. Solo resta desplegar la máquina virtual, y todas las bibliotecas para acceder al repositorio serán cuidadosamente puestas por el proveedor, todas las claves de acceso están registradas, solo tome la API en sus manos y úsela. Será rápido también.

Bueno, con los primeros pasos, todo está claro. Tomaron el archivo, lo revisaron varias veces, volvieron a colocar la versión procesada. Pero, ¿qué sucede si los pasos posteriores de nuestro algoritmo requieren algún conjunto de cálculos ligeramente más complejos para cada registro?

Tome algo como determinar la distancia entre un par de coordenadas. Existe un método Haversine extremadamente rápido ("haversinuses" según la versión de la sala), que proporciona una precisión aceptable a distancias cortas y permite no tomar el geoide WGS84 , cuyo cálculo funciona mucho más lentamente.

En sí mismo, resulta que tal cálculo no cuesta tanto si es único. E incluso si hay decenas de millones de ellos, esto, en principio, no tiene sentido.

Y ahora tomamos el caso de nuestro algoritmo patentado, cuando necesitamos calcular la distancia de cada señal a cada PDI de la categoría seleccionada, y descartar aquellos que están más allá de medio kilómetro (una distancia que es fácil de caminar).

Para la región del Gran Londres, aproximadamente un millón de establecimientos caen en PDI específicos en la categoría de tiendas y puntos de venta. Y como dije, en el conjunto mensual de datos docenas, cientos de millones de registros vienen por él. Y así llegamos ...

1,000,000 POIs × N, 000,000 señales = N, 000,000,000,000 distancias.


Oh, ven Trillones de cálculos de distancia y comparaciones de umbral constante.

La situación clásica con el producto cartesiano . Dos conjuntos no muy potentes individualmente dan fácilmente N × 10 12 resultados intermedios, ¡y esto es solo un mes en una región! Tal cantidad ya se está convirtiendo en calidad. No solo el tamaño del resultado intermedio ya es un problema grave, ya que no cabe en la memoria por completo, y es necesario procesarlo inmediatamente en el lugar de recepción, sino que la cantidad de cálculos necesarios para obtenerlo requiere demasiado tiempo de computadora. Y si para un registro, teniendo en cuenta todas las demoras en la transmisión a través de la red y otros costos generales, solo se gastan 100 nanosegundos, entonces millones de segundos son días y semanas de cálculos en una secuencia.

O, si necesitamos eliminar un segmento de la población general, por ejemplo, la condición "no tiene en cuenta los intereses de los usuarios que viven en un área determinada", entonces tendremos que comparar el device_id de cada registro del conjunto de datos enriquecido de toda la región con un conjunto en el que cientos de miles de registros con excluidos los residentes de device_id de esta área. Y estas son comparaciones de cadenas de muchas maneras, no tan rápido como para dos entradas. Una vez más, hay una especie de increíble número de ceros en la evaluación de una operación simple, y los tenemos para un conjunto completo de heurísticas para un proyecto promedio con una docena, o incluso más.
Big Data son datos que, debido a su tamaño, hacen que sea necesario utilizar técnicas algorítmicas especiales debido a lo inapropiado o poco práctico del procesamiento directo.

... incluso si el resultado final del cálculo se contrae en una pantalla de la tabla de Excel.

Puede intentar paralelizar el controlador "ingenuo" por la cantidad de procesadores virtuales disponibles en la máquina donde ejecutamos el cálculo. Puede dividir el conjunto de datos en partes y ejecutar el cálculo de diamantes de imitación en una docena de máquinas virtuales en la nube. Pero todo esto no dará un resultado cualitativamente excelente. Escalar "en ancho" proporciona rendimientos decrecientes a partir de un cierto ancho. Y ciertamente surgirá el problema de la sincronización y la partición, y administrar una flota completa de máquinas virtuales costará mucho tiempo y dinero. Mantenerlos encendidos todo el tiempo es costoso, y comenzar y detenerse bajo demanda es una labor intensiva.

Por lo tanto, para Big Data, se utilizan sistemas de software especiales del ecosistema Hadoop, que ya tiene controles de escala, así como un conjunto especial de algoritmos que permiten al mamut comer en pequeñas porciones sin riesgo de atragantarse con la cantidad astronómica de datos intermedios, y simplifica enormemente la vida de un desarrollador de Big Data. Pero no puede simplemente tomar y comenzar a usar Hadoop. Primero necesitas hacer un plan.

Especialmente si ...

Nuestro caso no es estándar


Si pregunta cómo las oficinas involucradas en análisis en grandes conjuntos de datos construyen sus procesos, resulta que se utilizan dos enfoques principales en la práctica mundial.

Enfoque número 1. Lago de datos


Para los datos que se acumulan con el tiempo y siguen siendo relevantes para siempre, se diseña un tipo especial de almacenamiento, el llamado " lago de datos ".

La arquitectura de dichos repositorios está optimizada para un acceso aleatorio rápido. Muchos de los conjuntos de datos recopilados se traducen a un formato especializado que le permite realizar rápidamente selecciones y cortes de criterios múltiples por conjuntos de columnas. A diferencia de las bases de datos relacionales y orientadas a documentos tradicionales, el almacenamiento de columnas se usa en lagos de datos. Por lo general, son finales, es decir, el formato de los contenedores con los datos es tal que después de rellenar e indexar, los datos en el mismo conjunto de datos nunca cambian nuevamente. Por ejemplo, archivos de parquet que no requieren modificación.

Después de eso, una multitud de analistas de datos, satanistas o analistas de datos , se apresuran, y en software especializado ("computadoras portátiles" como Jupyter) recopila estadísticas, indicadores, etc. en línea Estas estadísticas se descargan del lago en algún lugar hacia afuera, o simplemente se agregan en forma de los mismos archivos finales para la agregación posterior.

Enfoque número 2. Transmisión de datos


Para los datos que llegan en tiempo real y necesitan ser procesados ​​rápidamente (es decir, transmisión de datos), se diseñan buses de datos o colas de mensajes.

En una infraestructura con un bus de datos, hay generadores en un extremo y consumidores en el otro, y los flujos de datos en sí están compuestos de eventos.

Se generan generadores, y los consumidores, en tiempo real o casi real, analizan eventos, acumulando algunos resultados finales, que nuevamente pueden generar eventos que el próximo conjunto de agregadores consumirá a través del mismo bus, y así sucesivamente hasta que se obtenga el resultado final, plegado en el repositorio de resultados finales.

Es conducido por Apache Kafka y almacenamiento rápido como Aerospike.

Nuestro caso


Pero nuestro caso no encaja en estos dos enfoques.

En primer lugar, no tiene sentido que mantengamos un lago de datos, porque el conjunto de datos rara vez dura más de un año (las pistas de usuarios para 2016 en 2019 ya no son necesarias para nadie), y cada vez que los clientes necesitan una parte completamente impredecible de todos los datos acumulados. Además, debido al hecho de que para cada segmento de la población y categoría se crea su propia plantilla, todavía estamos obligados a tomar solo la pieza requerida, y fusionarlos en un lago común no tiene mucho sentido. Es más fácil mantener cada conjunto de datos mensual en su forma original: archivos CSV en su propio directorio separado. La ruta al archivo se obtiene ... / proveedor / país / región / subregión / año / mes / archivos de conjunto de datos, y un subconjunto se selecciona simplemente por la máscara de nombre de archivo, por ejemplo, ... / Tamoco / UK / Greater_London / * / 2019 / {6, 7.8} / *. Csv.

En segundo lugar, la naturaleza de los conjuntos de datos es discreta, no de transmisión. Por supuesto, uno podría, por supuesto, calcular directamente algunos indicadores directamente en el proceso de carga en el almacenamiento de la red, pero los mapas de calor terminados para la región de Moscú y la región vecina de la Región de Moscú no se correlacionan con el mapa de calor terminado de la región combinada de Moscú y Región ( debido al hecho de que muchos viven en la región y trabajan en Moscú), y todavía no sabemos de antemano qué región necesitaremos. Tal vez ni Moscú, ni Región de Moscú, sino solo alguna Ciudad 17. Es muy costoso conducir la heurística y calcular indicadores para todos los conjuntos de datos.

Como resultado, debemos seleccionar rápidamente un subconjunto de los conjuntos de datos acumulados, implementar rápidamente una granja de cómputo que sea adecuada para la energía, realizar rápidamente un proceso de cálculo único pero estandarizado, escupir el resultado y ... quizás nunca más volver a un subconjunto o una granja de este tamaño , no a la plantilla. Y no podemos mantener un clúster de rendimiento bien ajustado en nuestro propio hardware, que cubriría las necesidades de todos nuestros proyectos, desde el más pequeño hasta el más difícil, ya que son demasiado diferentes.

No creo que seamos tan únicos. En conversaciones con colegas, surge regularmente la necesidad de instrumentación de casos de estallido similares, pero aquí todos construyen el proceso a su manera. Por lo general, las soluciones para casos no estándar se unen al transportador existente desde los enfoques No. 1 o No. 2 en el lateral; nuestro proceso consiste completamente en proyectos privados, tenemos todas las tareas como "burst".

Bien ahora. Durante dos años y un centavo, pudimos encontrar un kit de herramientas para automatizar mi trabajo tanto como sea posible, y es precisamente esto lo que presentaré para uso general en la tercera parte de mi historia. Mientras tanto, hablemos sobre la evolución, y todos esos errores y problemas, corrigiendo y resolviendo que hemos llegado a un proceso sostenible por experiencia.

Prototipo en C # y PostGIS


Todo comenzó hace unos años. Dos tipos muy inteligentes llamados Alexei Polyakov y Alexei Polyakov , no se rían, en realidad son homónimos, pero de diferentes partes del mundo, un biólogo y un vendedor, decidieron aplicar el método de la disertación sobre el comportamiento colectivo de las poblaciones celulares en cultivos celulares, probado experimentalmente hasta ratones , a publicidad y marketing.

Funcionó en las personas.

Y luego surgió el proyecto Locomizer. Digo "proyecto" porque es como una startup con una LLC para celebrar contratos, pero no del todo. Los miembros de nuestro equipo están dispersos por todo el mundo, trabajan en diferentes lugares y oficinas como autónomos o subcontratistas (y no todos a tiempo completo), y utilizamos nuestros algoritmos para clientes muy diferentes con diferentes modelos de interacción a medida que recibimos o encontramos pedidos. Hay suscripciones, pero más tareas privadas de una sola vez.

Pero eso es ahora. Y hace unos años, todo era aún más caótico. Quien escribió la primera implementación de software para calcular la velocidad, generalmente no lo sé. (Si de repente conoces a estos héroes desconocidos, salúdalos). Al final de mi último artículo sobre la carrera de un programador en una ciudad en particular, escribí literalmente lo siguiente: "Vine a hablar al lugar donde trabajo ahora, y el primer ministro declaró desde el umbral que El proyecto es infernal. Nada Nuevamente, SIG, solo los cálculos se basan en MapReduce (y lo quiero en Spark), mapas en ArcGIS, y todo esto gira en nubes que nadie puede idear. En mi opinión, ¡una gran opción! ”, En ese momento ya era así, y solo puedo restaurar la primera etapa del desarrollo del proyecto en código a partir de los recuerdos de mitra_kun , que apareció en el proyecto solo un año antes.

Las heurísticas rudimentarias para procesar conjuntos de datos sin procesar se escribieron en PHP, Python y C ++, y el cálculo principal de la velocidad para el mapa de calor se realizó mediante un programa en C #.

Todo el proyecto en C #

Ella trabajó así:

  1. Primero, leemos directamente la cadena en la matriz del archivo del conjunto de datos.
  2. Ejecútalo para siempre, construye una tabla hash en polzakz.
  3. La base de POI es una tabla literal en una base de datos PostgreSQL con campos PostGIS de tipo GEOMETRY, y para calcular la distancia entre cada señal de usuario y cada POI, la función ST_DISTANCE se extrae a través de un pequeño almacenamiento , el resultado se agrega a una tabla hash con una clave para cada usuario.
  4. Luego hacemos foreach en la tabla con la acumulación del resultado del puntaje de interés para cada clave en la matriz.
  5. Una vez más, grupo, para cada categoría.
  6. Después del final del cálculo, que toma completamente de un par de horas a una semana, el resultado se agrega al archivo CSV ...
  7. ... y luego aún se procesa manualmente, se superpone en el mapa y se visualiza en ArcGIS .

Está claro que el volumen máximo procesado está limitado por la memoria disponible en la máquina, y la velocidad de las consultas individuales a la base de datos provoca una cierta alarma.

Primer acercamiento a Hadoop MapReduce


Se calculó algo sobre el prototipo local, se probó la idoneidad de los tratamientos aplicados para preparar conjuntos de datos y construir mapas de calor, y surgió la pregunta de cómo poner el trabajo en marcha. Bueno, es importante no tratar la puesta de sol manualmente, sino usar las capacidades de alguna plataforma, preferiblemente escrita por ballenas de la industria, y escalar al menos al mínimo.

Como dije, la plataforma estándar de procesamiento de big data es el ecosistema Hadoop. Un gran conjunto de bibliotecas heterogéneas, que incluyen un sistema de archivos distribuido, planificadores para tareas paralelas, abstracciones relativamente convenientes sobre reducción de mapas, motores para ejecutar consultas e incluso un montón de cosas para el análisis de datos. Y toda esta infraestructura de software está disponible en la nube de diferentes proveedores en forma de paquetes integrados, y se automatizará, pero más sobre eso más adelante.

Ok Google, busca Hadoop. Mis predecesores tomaron el prototipo y reescribieron el cálculo principal de C # a Java, reemplazando literalmente todo foreach con el correspondiente Mapper y Reductor Hadup, y tomaron todos los pasos para preparar y enriquecer conjuntos de datos en utilidades separadas en lenguajes de scripts para que se desarrollen más rápido, porque con el advenimiento de diferentes Los algoritmos de los clientes comenzaron a evolucionar activamente. Por separado, comenzamos a escribir un back-end para la interfaz de usuario web en Spring (no es la mejor solución, si no hay experiencia previa en el desarrollo de Java, sería mejor escribir en PHP), con un frente en Node.js con integración de mapas de ArcGIS.

Una pequeña parte de un proyecto Java.

Levantaron el "gran grupo" de Hadoop en cinco máquinas virtuales en Microsoft Azure para este caso. Por que Azure En primer lugar, para las startups hay un gran descuento durante los primeros años. En segundo lugar, ArcGIS Desktop para Windows para la visualización de mapas ya estaba implementado en esta nube.

El clúster de Hadoop se implementó manualmente, y no desde el servicio Azure HDInsight correspondiente, que era difícil de configurar.En cada una de las máquinas de clúster, plantearon Postgre + PostGIS (una decisión bastante dudosa, porque MR y la base están comenzando a competir por el procesador), para no recorrer distancias a un servidor separado. Creamos un pequeño script que dispersó las réplicas de la base de datos de POI en los nodos del clúster.

El proyecto seguía siendo un prototipo, solo un poco más avanzado. PostGIS todavía se usó porque apareció geofencing, y los muchachos aún no sabían cómo se podría implementar con un trabajo mínimo. Parecía que todo era terriblemente lento, y la cantidad de pasos que debían hacerse manualmente excedía una docena y media.

Fue en ese momento cuando me interesó la propuesta de un poco conocido en nuestra pequeña pero muy TI ciudad (en Izhevsk hay más de siete docenas de oficinas con personal de desarrollo, donde trabajan unos tres mil programadores), una oficina con un nombre absolutamente genérico "Tecnologías de la información rusa", que De repente, sin ninguna razón, se necesitó un Desarrollador Java Senior con amplia experiencia en implementación y automatización, y al menos escuchó del oído sobre Big Data y las nubes. Bueno, cuando escuché un poco sobre nubes y big data.

En cuanto a todo lo demás, tengo más que suficiente experiencia :( Por lo tanto, lo primero que dije cuando vi el código y el estado de los procesos fue en las mejores tradiciones de Artemy Lebedev, en voz alta y mucho. No lo repetiré.

Bueno, si el código y los procesos son de una calidad comprensible, definitivamente tienen un lugar para la optimización. Para empezar, al menos puede enviar solicitudes a PostGIS de una en una, pero en lotes, alrededor de 5000 puntos a la vez. Las bases de datos están, por regla general, bien optimizadas para la resolución de productos cartesianos. Se dice que, hecho, el almacenamiento con la llamada ST_DISTANCE se reescribió de tal manera que devuelva inmediatamente una gran matriz para un paquete de puntos, y desde cero el cálculo se aceleró de inmediato 40 veces, porque ahora no era necesario establecer una conexión a la base de datos con tanta frecuencia, y tantos índices en geometría en la tabla con POI comenzó a trabajar con gran sentido.

Es cierto que un desagradable error esotérico se introdujo directamente en el cálculo, debido al hecho de que el prototipo no se transfirió completamente de C # a Java. Los chicos no entendieron el punto de una variable importante, y el TK formal en el prototipo no los alcanzó en absoluto, perdió en algún lugar del camino. Luego restauramos todos los algoritmos a partir de descripciones fragmentarias, pero esto ya fue mucho más tarde. Sin embargo, este error en su conjunto no estropeó el resultado del cálculo, simplemente redujo el contraste del mapa de calor.

Pero no obtendrá mucho rendimiento de MapReduce, porque el asignador lee los datos de HDFS y los escribe de nuevo, y el siguiente reductor en la cadena hace lo mismo, y así sucesivamente hasta que se completen todos los pasos. También es muy inconveniente administrar un proceso de varios pasos, especialmente si el algoritmo tiene ramas debido a la configuración. Todo el algoritmo es un código rígido, y si desea reordenar los pasos de alguna manera, debe moverlos a módulos separados con su propio lanzador y envolver algún tipo de lógica en el exterior.

Bueno, sacar PostGIS del cálculo, incluso si duplica la base de datos en cada nodo del clúster, sigue siendo una idea muy dolorosa.

El advenimiento de CI y Spark


- ¡Automatízalo! - mi segundo gran tema de interés rofessionalny después enterprayznogo n rogrammirovaniya en un sapo ... Y no. En segundo lugar - es n itstsa, n asta y n udingi, entonces que haya una tercera - es n parada n procesos y su automatización. (Yo, como chef p ovar, me encanta que todo esté en p . Hashtag # p cookies).

El trabajo manual conlleva demasiados peligros. Las personas no son confiables y a menudo cometen errores, incluso si hacen lo mismo, por lo que es mucho más eficiente pasar un tiempo formalizando el flujo general del proyecto y escribiendo un script que no fallará al llamar a la utilidad para copiar el conjunto de datos del almacenamiento a largo plazo al en línea, y no mezclará el orden de los pasos, que seguir caminando el rastrillo.

La caminata con rake fue el problema más serio que primero tenía que resolverse. Primero, lo implementé en un pequeño equipo virtual separadoy configuró el ensamblaje con la ejecución de todas las pruebas para que el artefacto verificado esté siempre a mano y no sea necesario lanzarlo al clúster manualmente. El segundo paso fue escribir un contenedor para comenzar una tarea de MR con el conjunto de datos y el conjunto de parámetros especificados en el clúster directamente desde el mismo TC, con la misma copia automática de los conjuntos de datos originales en el clúster y los resultados del cálculo en el almacén de resultados.

Y el tercer paso, que tomó mucho tiempo por costumbre, fue automatizar la implementación del clúster, ajustar sus parámetros e iniciar el cálculo en un conjunto de datos integrado en Azure Blob Storage. De repente, hubo proyectos para los que comenzó a perderse un grupo estático de cinco máquinas virtuales y / o cuyos conjuntos de datos no se deben mezclar con un volcado de archivos antiguos en HDFS.

Azure HDInsight es en realidadHortonworks HDP (descanse en paz para él), y algunos de sus ajustes se realizan en la API, y algunos solo se pueden registrar a través de Ambari. La implementación de un clúster en función de la carga de la nube puede llevar hasta una hora, y el ciclo de ajuste, es decir, verificar el efecto de cualquier conjunto de configuraciones en el rendimiento de nuestro código, puede llevar todo un día. La versión local de HDP Sandbox en la máquina virtual consume 11 gigas de RAM, y es monstruosamente exigente en el subsistema de disco, por lo que incluso la depuración local es extremadamente desagradable, y su configuración es ligeramente diferente de la versión en la nube. Dediqué mucho tiempo a los experimentos, pero al menos descubrí cómo funciona todo y qué hacer si el cálculo de repente se queda en el medio con el siguiente OOM, porque también es bastante desagradable analizar los registros manualmente.

Mientras trabajaba con HDP, otro programador comenzó a unificar las etapas dispares de preparar conjuntos de datos en Apache Spark. Spark resolvió el problema de escribir / leer constantemente datos intermedios que ocurren entre los pasos de un cálculo, y en general, se diseñó teniendo en cuenta todos los lugares malos de la RM, y puede hacerlo muchas veces más de forma inmediata. Y el perezoso RDD de Spark es algo muy útil.

Al mismo tiempo, escribí una secuencia de comandos de Azure Templates en PowerShell para configurar el nodo perimetral para PostGIS, una instancia separada de nariz gruesa en el clúster, con un montón de núcleos y memoria para acelerar las solicitudes, así como una serie de pasos preliminares para preparar conjuntos de datos, que primero se colocaron en su disco local, y luego cargado en HDFS en el clúster.

Por lo tanto, el enlace del script, que inicialmente pensó que funcionaría tanto de manera interactiva como por lotes en TC como una compilación separada, aprendió gradualmente a ejecutar una combinación arbitraria de pasos en MR, Spark y otros paquetes de software que no utilizamos desde el paquete HDInsight, pero todavía con parametrización rudimentaria. Sin embargo, transferir los parámetros de compilación a un repositorio vecino con un conjunto de archivos .ini (para cada componente de la plataforma y para cada paso del proceso), y mantener las plantillas de proceso en las ramas de este repositorio resultó ser una práctica tan conveniente que todavía lo usamos.

Ya progreso. Con la automatización de una rutina manual, el tiempo de preparación para el cálculo se redujo cuatro veces, sin mencionar los errores humanos, que se redujeron mucho. Pero aún no es el momento del cálculo en sí.

Tercera aproximación en GeoSpark


Tardó unos seis meses. En este momento, un conjunto de heurísticas depuradas y probadas se había acumulado gradualmente, ya con aplicaciones separadas en Spark, y no con scripts, y se desarrollaron algunas plantillas de proceso típicas. Ahora era necesario optimizarlos.

El segundo programador, que no tenía experiencia previa ni en un equipo ni en una empresa, actuó con sus módulos de manera bastante directa: después de completar la transferencia de una heurística a Spark, simplemente copió todo el proyecto y comenzó a reemplazar el antiguo algoritmo con el nuevo. Como resultado, cuando había ocho de esos módulos paralelos, cada uno con un conjunto de parámetros similar pero ligeramente diferente, un poco de excelente semántica de llamadas, y también una gran cantidad de código de servicio duplicado, comenzaron a plantear otro problema. Cuanto más código, más tiempo se dedica a su soporte, especialmente si no deja de evolucionar todo este tiempo. Y debido a la constante copiar y pegar, los parámetros no utilizados y otra basura comenzaron a acumularse en ellos.

Una vez que terminé con el problema de la automatización y me ocupé de la configuración de los clústeres, ahora ya podía retomar los módulos de preparación de datos y la heurística. Para empezar, tomé todo el código que se repite en un proyecto separado de Commons, enchufado como un submódulo git , y en los módulos de cálculo se convirtió en varias veces menos un desastre. Reuní una plantilla para una heurística típica, y ya surgió un nuevo proyecto, sin la necesidad de reemplazar piezas de código y sin suciedad innecesaria en la historia de los commits. El desarrollo comenzó a ser más rápido.

El siguiente gran problema a ser derrotado provino de la lógica de calcular las señales de producto cartesiano × POI.

Solo el procesamiento por lotes lo transfiere a la base de datos, pero no reduce el número de operaciones, incluso si la base de datos utiliza efectivamente índices y optimización de consultas. Sería lógico no considerar la distancia para esos pares donde obviamente excede el umbral que necesitamos. Pero, ¿cómo descartar pares con una distancia mayor que el umbral sin calcular esta distancia?

Respuesta: particione las señales y los PDI en una cuadrícula geométrica.

Además, el mapa de calor ya consiste en una cuadrícula de polígonos. Y si selecciona el tamaño de celda de esta cuadrícula de la manera correcta, entonces para cada PDI del polígono seleccionado es bastante posible limitarnos a calcular las distancias a las señales que caen en el mismo polígono, sus celdas vecinas, y eso es todo. El resto puede descartarse; ciertamente quedarán fuera de los límites de relevancia.

Spark ya tiene una herramienta preparada para trabajar con redes: GeoSpark . El segundo programador comenzó a usarlo y apareció la operación preliminar "arrastrar el conjunto de datos a la red". Pero no mejoró mucho, un problema grave fue reemplazado por otro problema grave.

Ahora bien, este era el problema de la "cola larga": usuarios, en los que el número de señales es de millones. No hay tantos, pero si se acumulan en el centro de la ciudad, donde el POI es alto, y se acumulan allí, por suerte, entonces no importa cómo particiones en geometría (al menos Voronoi , al menos quadtree ), todavía habrá polígonos donde el número de comparaciones excede una cantidad razonable. Pero también debe verificar los polígonos vecinos donde la densidad es tan alta.

Y si el 99% de las particiones con polígonos de baja saturación funcionan rápidamente, entonces el 1% de las estaciones de trabajo de Spark con células de alta densidad continúan colgando a la victoria, comen memoria como si estuvieran inconscientes y estropean todas las frambuesas. Spark está tratando de tener todo en mente, y si hay una fuerte variación en el tamaño de las particiones en RDD, entonces todo el ajuste para el consumo de memoria vuela por el desagüe, porque tiene que hacerse para el más grande.

Resultó que el 99% del cálculo se aceleró con particiones geométricas cientos de veces, y el 1% de la cola larga redujo la optimización completa a casi nada.

En general, la transición a GeoSpark produjo una ganancia de cinco veces, pero solo en el tamaño de los ejecutores que eran muy ineficientes en la memoria y, en consecuencia, los clústeres con máquinas virtuales costosas. En resumen, la partición geométrica para geodatos de alta densidad resultó ser un callejón sin salida.

Y luego hubo felicidad en la persona del escritorio analítico de una de las mayores telecomunicaciones japonesas. Una pequeña empresa subsidiaria basada en datos de geolocalización recopilados por la empresa principal.

Analistas japoneses y migración de Azure a AWS


Los japoneses tienen una mentalidad interesante. Ellos mismos no tienen prisa, pero si solo se les da un gaijin para morderse el dedo, ambas manos se cortan. ¡Nunca le des a los japoneses fechas específicas! Y si llama, tome al menos tres veces el suministro. Será monstruosamente largo y difícil coordinar los términos de referencia, y no solo la famosa meticulosidad japonesa interferirá, sino también la diferencia de pensamiento. Puede que simplemente no quede tiempo para implementar la versión final de los TOR.

El proyecto para integrarse con la "hija" de las telecomunicaciones japonesas casi mata a nuestro proyecto. Las perspectivas brillaban para convertirse en un proveedor exclusivo de datos para el loco mercado publicitario japonés, y el negocio es un poco ... eh, puedo hacerlo sin comentarios.

En primer lugar, no Azure. Solo AWS, solo hardcore.

En segundo lugar, el frente debía modificarse para satisfacer sus necesidades, que cambiaban constantemente a lo largo del proyecto. Los especialistas en marketing de esta oficina constantemente querían algo que ellos mismos no sabían exactamente y que realmente no podían articular, y tuvo que rehacerse diez veces por etapa, cambiando la lógica de cálculo para los próximos nuevos indicadores sobre la marcha.

Pido disculpas por la calidad, una captura de pantalla del informe de error, no quedan más

En algún momento, me asusté un poco e hice un conjunto de "operaciones elementales", unas 15 acciones primitivas en RDD con llamadas a métodos básicos como uniones, mapeo, anotaciones de valores predeterminados, suma de valores de columna, y otras operaciones pequeñas de este tipo, para que rápidamente cambia la lógica de la cadena de cálculo, como si fuera un conjunto de sentencias SQL.

(Regular Spark SQL no es aplicable en nuestro caso debido al hecho de que no hay una escritura estricta o un conjunto estricto de campos. En el conjunto de datos, en cualquier momento puede agregar tantos campos adicionales como desee, y cambia durante el flujo del proceso Es muy difícil prescribir metadatos en condiciones que cambian constantemente).

La tarea de alto nivel era esta: elegir una región arbitraria de Japón y construir un mapa de calor durante un período de tiempo arbitrario utilizando un conjunto arbitrario de categorías con un montón de indicadores para el vertedero. ¿Qué tipo de indicadores, cómo contarlos? El cliente mismo no entendió realmente esto.

El conjunto de datos de prueba (es decir, pequeño) con señales de usuario para 2016-2017, en el que tuvimos que resolver la tecnología, son 5 terabytes de datos, 14,000,000,000 de registros. Solo en Tokio, hay varios millones de puntos de interés, y en la red en la región de Hokkaido, 1,600,000 células.

Y las tarjetas para las dos mil categorías para cada una de las 47 perfecciones japonesas deben considerarse "sobre la marcha", ya que deben venderse como un servicio en la nube.

Una gran tarea para romper el cerebro. En algún lugar tres o cuatro órdenes de magnitud más altas que nuestras capacidades en términos de "velocidad de cálculo" y "volumen de datos".

Después de entristecernos, decidimos hacer un cálculo previo para cada región (gracias a los dioses sintoístas, los japoneses no necesitaron unir las regiones) y un mes para que el mapa de calor se construyera de acuerdo con los puntajes previamente preparados. No lo deje en tiempo real, sino unos minutos o decenas (para el centro de Tokio) minutos. El cálculo previo tomó varios meses con grupos de 25 de las máquinas virtuales más potentes disponibles en la región de Tokio AWS.

Pero para ejecutar en AWS, primero tenía que reescribir la automatización bajo la API de AWS. Y diferentes nubes, aunque ofrecen servicios similares desde el exterior, son internamente completamente diferentes. Es bueno que en este momento PowerShell ya haya alcanzado la versión 6 del candidato de lanzamiento, y los scripts de enlace de Azur para implementar el clúster y ejecutar el cálculo podrían ser portados y ejecutados audazmente en Linux TeamCity (porque implementar servidores en Windows en AWS es una idea ) Más precisamente, no porte, pero abra una secuencia de comandos existente en un monitor y escriba una implementación paralela para otra nube en el segundo.

Además, AWS es mucho más antiguo y, por lo tanto, más arcaico que Azure, es arquitectónico y hay mucho más trabajo manual para configurar el nivel inferior de infraestructura. Y la subasta local para la venta de recursos informáticos agrega un dolor de cabeza cuando es posible que no tenga los automóviles del tamaño correcto al precio deseado, y el cliente no asigne un presupuesto para el cálculo completo del precio.

Pero el ecosistema Hadoop en sí mismo en la encarnación amazónica, EMR, está más cerca de la vainilla, y trabajar con él es más fácil que con HDInsight. Bueno, al menos con algo resultó ser más fácil.

Pero no con S3. Aquí los problemas salieron de donde no esperaban. S3 tiene límites indocumentados. Por ejemplo, en un cubo no puede haber más de ~ 11,000,000 de objetos, porque en algún lugar de las profundidades de la API hacen la clasificación de claves en orden lexicográfico para cada (¡cada!) Solicitud, y el búfer asignado para ello simplemente no permite la clasificación más líneas, especialmente si son largas. Para acelerar el cálculo, no fusionamos particiones al final, y en algún momento nos topamos con este límite, después de lo cual el proceso simplemente se detuvo.

Según la mente, la fusión debe hacerse, e incluso hay una herramienta: la utilidad s3-dist-cp, pero su uso es un dolor de cabeza por separado. Los depredadores para extraterrestres escribieron la utilidad con seguridad, se comporta de manera tan intuitiva. Y tiene un defecto fatal: bajo el archivo combinado, necesita tanto espacio en HDFS como todos los originales. Y fusionar decenas de miles de archivos de partición de cientos de bytes a decenas de megabytes de tamaño, distribuidos en un grupo de 25 máquinas, durará mucho tiempo.

Sin embargo, ya con un millón de objetos en el depósito, S3 comienza a trotar silenciosamente las solicitudes. Y en condiciones de consistencia eventual, esto generalmente es un desastre: Spark, sin esperar al siguiente escritorio el número acordado de veces, puede caer. Hay una solución: usar el complemento de Amazon propiedad de EMRFS, pero funciona por encima de DynamoDB, y esto es algo muy costoso. Y con sus propios límites en el número de solicitudes por segundo.

En resumen, en condiciones de falta total de tiempo, decidimos volver al esquema estático: implementar un clúster permanente en instancias de un tamaño bastante pequeño (aunque costoso, pero más barato que DynamoDB), fusionar todos los terabytes de los conjuntos de datos originales y calculados en HDFS en él, y leer las tarjetas localmente.

Pero el siguiente giro de la trama fue la demanda japonesa de cambiar de la cuadrícula hexagonal generada a Japan Mesh , el método estándar para ellos de la división geográfica con celdas rectangulares que dependen solo de las coordenadas del punto. Una cosa muy buena, ya que le permite abandonar el paso computacionalmente pesado de "tirar de señales a la red".

La desventaja es que la malla de malla de Japón solo es aplicable a Japón y a los territorios insulares que históricamente afirma ser, pero no al resto del mundo. Pero al menos para los japoneses fue posible abandonar el lento GeoSpark y dividir las señales de manera uniforme sin referencia a la geometría externa. Y con la partida de la "cola larga", el cálculo inmediatamente se aceleró nuevamente una vez más a las 10.

Es desafortunado que esto sucedió después de que todos descubrimos los hexágonos, gastando mucho dinero y tiempo en vano. Un grupo con terabytes de conjuntos de datos preparados tuvo que ser simplemente desechado.

Y en cualquier caso, en algún lugar en el medio del trabajo, los japoneses aún pidieron transferir toda la infraestructura de una cuenta de AWS a otra. Y como si no le importara todo el trabajo realizado en la configuración. Bueno, logré hacer un script para la plantilla CloudFormation en el momento de la transición, por lo que la migración fue más o menos fluida.

Como la cereza final del pastel, los japoneses finalmente decidieron que el frente no se rindió a ellos, y sacarían los cálculos manualmente a pedido de sus clientes, así que gracias a nosotros por los algoritmos (por primera vez los documentamos en detalle y encontramos algunos errores), y por ahora. Bueno ... buena suerte y hasta luego.

Brrr Recuerdo este proyecto con horror y un estremecimiento.

Ash Nazg Durbatuluk, Ash Nazg Gimbatul, Ash Nazg Trakatuluk, Ag Burzum Ishi Krimpatul !!


Pero desde el punto de vista positivo, además de documentar todos los algoritmos, también hubo mejoras generales.

Aprendimos un estudiante en Java Junior, y realizó un estudio de un montón de bibliotecas geográficas, como resultado de lo cual finalmente logró elegir la correcta y descartarla del entorno PostGIS.

Los intentos anteriores no tuvieron éxito debido a la poca precisión. En el radio de tres kilómetros, los Haversins nos dan un error ya notable, y la mayoría de las bibliotecas que intentamos tomar desde el principio eran pésimas en las latitudes al norte de San Petersburgo, como resultado de las cuales aparecieron agujeros o doble superposición en la cuadrícula. Y los finlandeses somos clientes frecuentes, por lo que es fundamental que todo funcione correctamente en sus latitudes.

Hasta que descubrimos que necesitábamos una lib con un geoide normal (preferiblemente el mismo que en PostGIS, WGS84), los resultados no coincidían con los resultados esperados. Pero después de cambiar a GeographicLib, se eliminó el cuello de botella en forma de conexiones Postgre, y la etapa final de cálculo de la velocidad se aceleró 40 veces. Golovnyak se fue con la configuración adicional de una instancia RDS separada debajo de la base y cargó un volcado con POI, que se trasladó a los conjuntos de datos habituales en S3. ¡Unificación!

Al mismo tiempo, el mismo estudiante desenterró y corrigió el error que causaba que las tarjetas parecieran más pálidas de lo que realmente eran. Bueno, cuando hay una tarea sin límites de tiempo, envidio a los estudiantes.

Otro punto importante. Una vez, por enésima vez, mirando secuencias de comandos vinculantes que llaman un módulo Spark tras otro, pensé, pero ¿con qué tipo de demonio los cortocircuitamos?

¿Por qué guardar resultados intermedios cada vez en S3 o HDFS, si el RDD final del módulo anterior puede simplemente ser redirigido a la entrada del siguiente en la cadena? Apenas dicho que hecho, MetaRunner fue escrito en un par de horas. La presencia de bienes comunes ayudó mucho en esto, los módulos estaban bastante estandarizados para entonces, especialmente porque los parámetros de cada uno de los módulos ya estaban en las mismas tareas.ini, con prefijos de teclas correspondientes a sus nombres.
Su atención se presenta con un diagrama de bloques de un mapa (el paso final antes de emitir al frente, pero no la versión final), escrito en operaciones elementales:

Diagrama de flujo del proceso de preparación del mapa de calor

Si se deshace de 24 llamadas intermedias a HDFS, específicamente este cálculo se acelera unas 50 veces.

Pero, ¿qué sucede si agrega soporte variable a la plantilla de proceso para que no tenga que regenerar task.ini cada vez que cambie los parámetros en la Tienda de propiedades?

- Ash Nazg! Grité Los colegas se miraron perplejos. Un tipo tiene un techo debido a estos japoneses, pero bueno, sucede.
"Ash nazg ... burzum-ishi krimpatul", gruñí gruñendo (no funcionó muy bien), y fui al primer ministro para discutir la fusión de los 15 (el número de heurísticas y servicios auxiliares estaba creciendo gradualmente) de los módulos de cálculo en un solo repositorio.

Si cortocircuitamos los módulos entre sí, entonces no trabaje más con el revestimiento de todos los JAR individuales en el classpath de la chispa, y deje que todo el paquete de la lógica Locomizer patentada (y nuestras operaciones auxiliares) se ensamblen en un JAR gordo. Al mismo tiempo y localmente, ahora será posible ejecutarlo sin un clúster. Y lo que es importante, la lógica para analizar tareas.ini se puede transferir desde enlaces de PowerShell a código Java, donde la sustitución de variables es mucho más simple.

Colegas que relinchan sobre la propuesta de llamar al proyecto "El anillo de la omnipotencia", un anillo, pero un poco de patetismo saludable nunca dolerá.

Habiendo aprovechado el momento de la próxima ronda de coordinación interminable de TK en el frente, reuní todos los módulos en un montón. Maven es una herramienta avanzada para resolver dependencias en un proyecto de múltiples módulos, por lo que resultó eliminar los últimos fragmentos de código duplicado, unificar versiones de todas las bibliotecas y hacer opciones de compilación para entornos locales y en la nube. Además, cada módulo permanece en su propio subproyecto, y su autor puede trabajar en él de manera bastante independiente, sin interferir con el resto.

Por cierto, considero que este enfoque con la cristalización de abstracciones y la construcción de algún tipo de arquitectura a partir de un conjunto existente de entidades homogéneas es más que un intento de diseñar un nivel abstracto por adelantado e implementarlo en tareas particulares. Sin las prácticas establecidas y los patrones de uso, es inútil diseñar una arquitectura: no se pueden prever todos los casos de antemano, y las opciones para el comportamiento de los usuarios del sistema pueden diferir radicalmente de las ideas del diseñador.

Con la lógica unificada de procesamiento de los parámetros, fue posible hacer un modelo de objeto unificado distinto para la configuración del módulo, y hacer verificaciones normales de la validez y consistencia de las configuraciones de los módulos entre sí dentro del mismo proceso.Esto es especialmente importante con los conjuntos de datos en formato CSV: el control del número y el orden de los campos en cada registro RDD, así como la corrección de la transferencia del conjunto de datos en sí desde la salida de un módulo a la entrada de varios posteriores, se basan completamente en el lado de la llamada. Y si hay un punto de control, entonces ya se puede hacer bien.
¿Por qué no vamos más alto y trabajamos con RDD y no con marcos de datos? Por la misma razón que no usamos Spark SQL. Pero además, la implementación en Spark es la etapa final y final del código, que comienza con un documento técnico, se depura completamente en Python y solo entonces se optimiza en unos pocos pasos para la versión más productiva. Y cuanto más cerca de las primitivas de la biblioteca base, generalmente se ejecuta más rápido el código.

... si las manos del desarrollador crecen fuera de sus hombros y su cabeza está brillante. Teóricamente

Resulta que, en nuestras condiciones, es mucho más fácil conducir la línea del CSV original en forma de un texto nativo de Hadoup compacto (bajo el capó es solo una matriz de bytes), y describir solo aquellas columnas que la operación actual conoce, y solo para ello. Además, de acuerdo con los resultados de los experimentos, los marcos de datos proporcionan una sobrecarga de consumo de memoria mayor que la necesidad de analizar CSV en la entrada de cada operación y comprimir de nuevo a Texto en la salida. Bueno y, sin embargo, es importante para nosotros poder particionar manualmente los RDD intermedios después de cada paso, porque los nuevos conjuntos de datos del almacenamiento se pueden mezclar con ellos (esto es claramente visible en el diagrama), por lo que aún debe bajar un nivel, sin importar cómo le gustaría permanecer en el nivel Libro blanco de lógica.

Pero en el código de "bajo nivel" en Java, también hay ventajas. Por ejemplo, si describe los parámetros de operación (así como los RDD esperados y generados) en los metadatos, puede generar automáticamente documentación y un ejemplo de configuración para ellos, y no escribirlos más manualmente. Y los muelles siempre serán relevantes, después de cada construcción.

El archivo de configuración task.ini en sí, de un conjunto heterogéneo de parámetros para cada módulo, se convirtió inmediatamente en un programa en una especie de lenguaje de programación declarativo. No muy bello, pero internamente lógico y relativamente legible para los humanos. Terminarlo a DSL real con su propia sintaxis no es un problema, pero no lo hice como innecesario. Pero un poco más tarde, sin embargo, agregó una vista a JSON para el futuro frente con un editor visual.

Un proceso en corto circuito, en promedio, recibió otras tres o cinco veces más rápido que una cadena de llamadas individuales a trabajos de Spark.

No cien veces, porque ahora, en el marco del mismo trabajo de Spark, se podrían mezclar pasos de tareas de diferente complejidad computacional y saturación de datos. Como resultado, el ajuste fino de los parámetros del clúster para cada una de las partes de un proceso de varios pasos ha perdido cualquier significado práctico. Pero gradualmente, y para tal opción, se encontraron algunos patrones generales que permitieron seleccionar ajustes preestablecidos de tamaños de clúster, basados ​​solo en el tamaño del conjunto de datos inicial y el número total de pasos en la plantilla del proceso de procesamiento.

Para resumir esta etapa, al final de nuestro trabajo con los japoneses, ya teníamos herramientas bastante desarrolladas:

  • , ,
  • , , DSL ,
  • , — ,
  • AWS, .

Pero lo que no funcionó fue el frente. La antigua interfaz de usuario web de Locomizer está irremediablemente desactualizada, nunca logramos terminar el nuevo japonés a un estado sano antes de que lo abandonaran por completo. Sí, y el código de back-end para esta IU, escrito con mi pie izquierdo en una oscura noche de octubre, no pude peinar hasta el final simplemente por el gran volumen.

Optimización y geocatarsis con Uber H3


Después de exhalar, volvimos a proyectos privados. El estado de ánimo después de que los japoneses fue, francamente, todo el equipo era muy regular.

Pero finalmente me libré de la necesidad de mantener un respaldo en el frente con su Bogomersssky, holm, holm, spring. (Esta es mi opinión personal. EE, no me gusta solo un poco menos, porque tiene menos autogía y valores predeterminados implícitos; por lo tanto, no importa qué terrible empresa es escribir REST).

Hubo un tiempo para mirar dentro de cada módulo con una adicción.
— , . , , , . - . — . , — .
No es que hubiera estado mirando mi código de compañeros sin prestar atención. Simplemente todos se dedican a la tarea que se le ha encomendado, y mientras él la lleva a cabo con el resultado deseado, no interfiera con el desarrollador que hace su trabajo. Si el algoritmo funciona correctamente, y esto se confirma mediante pruebas, entonces todo está bien. De acuerdo con la velocidad del trabajo, es aceptable, o de lo contrario, la decisión la toma el primer ministro.

No intervengo hasta que reconozco un alto riesgo de apoyo adicional en la decisión tomada por el desarrollador al implementar una nueva tarea. Y los módulos viejos y feos, escritos bajo el zar Gorokh por alguien que había abandonado el proyecto durante mucho tiempo, pero necesarios para los negocios, se mantendrán en un nivel viable, tal como sea, y no importa cuánto huele a ellos. Suena cínico, pero soy un pragmático, no un idealista, el resultado del trabajo es más importante para mí que la belleza del código.

Pero a veces es necesario aclarar la deuda técnica para que él no entierre el proyecto bajo su propio peso.

Spark es una biblioteca de muy alto nivel. Le permite realizar operaciones en RDD de varias maneras, lo que da el mismo resultado, y cada método puede tener varias opciones excelentes. Debe leer detenidamente la descripción de cada uno de ellos y, en caso de duda, acceder a la fuente para comprender cuál es el óptimo en cuyo caso. El resultado será el mismo, pero la diferencia en la velocidad de su cálculo puede ser varias veces, y si la lógica de alguna heurística despliega cien líneas de código en Spark, entonces debe ser especialmente cuidadoso para usar las formas más apropiadas de transformar los datos.

Lenguajes de alto nivel: son tales que te hacen pensar de manera abstracta.

Pero al mismo tiempo, el desarrollador debe ser consciente del bajo nivel, sin importar cuánto se dispare en altas abstracciones. Por ejemplo, que cualquier lambda pasada al método .map (), dentro del cual se asigna la memoria para algún objeto en negrita, se llama nuevamente para cada registro y reasigna el mismo objeto, y a ninguna de las JVM existentes le gustan las asignaciones repetidas en negrita.

Y si piensa en el soporte de código, sería bueno tener partes del algoritmo que estén conectadas por lógica interna, pero al mismo tiempo completamente aisladas con algunos valores de parámetros, aislar del resto del código, especialmente si estas partes están al principio o al final del algoritmo. Por lo general, se pueden sacar en una operación separada, al mismo tiempo, las pruebas con cobertura completa de todos los casos se acortarán.

Solía ​​ser prematuro hacer la optimización, pero ahora es el momento en que es el momento, y durante un par de meses me sumergí de lleno en una fascinante inmersión en las entrañas de los módulos computacionales con código de perfil escrito durante dos años por mis colegas.

Cuando me zambullí allí, One Ring tenía 29 operaciones (algunos módulos contienen más de uno). Cuando surgió, 43, y cada uno más rápido que el original, desde un pequeño porcentaje hasta decenas de veces. Pero, lo que es más valioso, aquellas operaciones que anteriormente estaban bloqueadas con datos en particiones de 10,000 elementos, ahora se mastican fácilmente en piezas en un millón de registros. En algunos lugares, tuve que sacrificar la flexibilidad y la legibilidad del código, en algunos lugares costó un simple reemplazo de .map () con .mapPartition (), pero el código dejó de fallar.

Solo había un cuello de botella: geofencing en una región arbitraria. Seguía siendo una extraña solución híbrida con una malla externa. Era posible usar Japan Mesh para Japón, pero para el resto del mundo era necesario buscar una variante adecuada de una cuadrícula dinámica, que depende solo de las coordenadas del punto y es conveniente de usar.

Tal opción fue encontrada: Uber H3 .

Según tengo entendido, el árbol hexagonal está encriptado en el nombre H3, y esta es una cuadrícula geográfica con excelentes características. Es estable en todo el rango de coordenadas, monstruosamente rápido (se llama el código nativo), proporciona celdas de tamaño uniforme sin espacios en toda la tierra, y le permite hacer un montón de opciones diferentes para cubrir polígonos, puntos y trazados. Además, una celda de cuadrícula hexagonal tiene un número mínimo de vecinos, y el siguiente nivel cubre las siete celdas de la anterior estrictamente por encima del centro de la celda subyacente, lo cual es conveniente al construir mapas de agregación.

Con la transición a H3, parece que el rompecabezas se ha desarrollado completamente.

Si comparamos con lo que era al principio, hace 2.5 años, luego de las semanas que se gastaron en un desafortunado mapa de calor en un conjunto de datos a un par de millones de señales, llegamos a los minutos que se gastan en docenas de tarjetas con conjuntos de datos, cuyo tamaño el analista de datos no presta mucha atención (debe refunfuñar cuando establece el valor predeterminado demasiado alto para el tamaño del clúster si escribir el resultado en S3 lleva más tiempo que el cálculo en sí). Y ya no mira a TC por sí mismo, simplemente obstruye la matriz de parámetros en algún lugar de su casa y extrae el número requerido de compilaciones necesarias con la pitón.

Agregue una nueva operación: solo necesita implementar correctamente la clase Operation (también puede usar Scala si lo desea), encerrarla con metadatos, incluirla en su configuración y luego One Ring descubrirá si está llamando a la nueva heurística o procesando la cadena correctamente.

Bueno, todo funciona tanto localmente como en AWS. También estará en alguna otra nube si es compatible con S3, y Spark puede ser arrastrado a través de Livy allí , y eliminamos todas las demás dependencias externas.

En todo blanco


- ¿Gandalf?

Pero todavía no tenemos un frente para lanzar procesos flexibles. Y las plantillas de tales procesos deben escribirse a la antigua usanza en VSCode, pero quería ser un mouse en un editor similar a Visio. Algo así: incluso hice un pequeño servicio REST como parte de One Ring, que tiene todo lo que necesitas para escribir un editor de este tipo, pero la última vez que trabajé en el frente fue hace unos 10 años, y no en el curso de las tendencias actuales. No es para JSF que lo clavo, ni siquiera será retro, sino que ya será una especie de necro. Sería bueno convertirlo en un SPA estático en algo moderno. Solo que no tengo idea de qué. Mi egoísta interés personal en revelar el código One Ring

Interfaz Mocap para editar un proceso.



(Terminaré el repositorio con contenido, pero puedes verlo ahora), espero, está claro. Y si hay alguien lo suficientemente valiente como para abordar esta tarea , escribiré una tarea técnica sensata con especificaciones.
Pero, en general, nosotros, el equipo de ingenieros de datos, no queremos mantener la herramienta terminada en nuestro armario. Estamos seguros: será útil no solo para nosotros. Y no solo para las necesidades de SIG, sino en general cualquier procesamiento de conjuntos de datos con pasos de procesamiento parametrizables.
En el artículo final (o en un par de artículos, nuevamente, algo lleva demasiado tiempo) te diré cómo construir, ejecutar, expandir y usar One Ring para tus tareas de investigación.

* El código fuente de One Ring OSS no incluye algoritmos heurísticos patentados de Locomizer. Pero su repositorio contendrá interfaces y descripciones, según las cuales las implementaciones gratuitas de estas heurísticas se pueden recrear utilizando el método de sala limpia, es decir, sin indicaciones de mi parte por el código.

Agradecimientos


... a sus colegas Gregory pomadchin por sus comentarios sustantivos sobre el tema, y sshikov por una evaluación independiente de la legibilidad del texto, y también a Anton dartov Zadorozhny por sus comentarios inesperados sobre el artículo anterior de la serie.

Source: https://habr.com/ru/post/485988/


All Articles