Existe una clase de tareas tan popular en la que es necesario realizar un análisis suficientemente profundo del volumen total de cadenas de trabajo registradas por cualquier sistema de información (SI). Como IP, puede haber un flujo de documentos, una mesa de servicio, un rastreador de errores, un diario electrónico, contabilidad de almacén, etc. Los matices se manifiestan en modelos de datos, API, volúmenes de datos y otros aspectos, pero los principios para resolver tales problemas son aproximadamente los mismos. Y el rastrillo que puedes pisar también es muy similar.
Para resolver esta clase de problemas, R encaja perfectamente. Pero, para no encogerse de hombros decepcionantemente, R puede ser bueno, pero oh, muy lento, es importante prestar atención al rendimiento de los métodos de procesamiento de datos seleccionados.
Es una continuación de publicaciones anteriores .
Por lo general, un enfoque superficial de "frente" no es el más efectivo. El 99% de las tareas asociadas con el análisis y procesamiento de datos comienzan con su importación. En este breve ensayo, consideraremos los problemas que surgen en la etapa básica de importar datos con datos en formato json
, utilizando el ejemplo de una tarea típica de análisis "profundo" de los datos de instalación de Jira. json
admite un modelo de objeto complejo, a diferencia de csv
, por lo que analizarlo en el caso de estructuras complejas puede ser muy difícil y largo.
Declaración del problema.
Dado:
- jira se implementa y utiliza en el proceso de desarrollo de software como un sistema de gestión de tareas y rastreador de errores.
- No hay acceso directo a la base de datos jira, la interacción es a través de la API REST (aislamiento galvánico).
- Los archivos json que se tomarán tienen una estructura de árbol muy compleja con tuplas anidadas necesarias para cargar todo el historial de acciones. El cálculo de las métricas requiere un número relativamente pequeño de parámetros dispersos en diferentes niveles de la jerarquía.
Un ejemplo de un jira json regular en la figura.

Se requiere:
- Con base en los datos de jira, es necesario encontrar cuellos de botella y puntos de un posible aumento en la eficiencia de los procesos de desarrollo y mejorar la calidad del producto resultante con base en un análisis de todas las acciones registradas.
Solución
Teóricamente, hay varios paquetes diferentes en R para cargar json y convertirlos a data.frame
. El paquete más conveniente es jsonlite
. Sin embargo, la conversión directa de la jerarquía json a data.frame
difícil debido al anidamiento multinivel y la fuerte parametrización de la estructura del registro. El agarre de parámetros específicos relacionados, por ejemplo, con el historial de acciones, puede requerir varias ext. cheques y bucles. Es decir la tarea se puede resolver, pero para un archivo json de 32 tareas (incluye todos los artefactos y el historial completo de las tareas), dicho análisis no lineal usando jsonlite y tidyverse tarda ~ 10 segundos en una computadora portátil de rendimiento promedio.
10 segundos solo no es mucho. Pero exactamente hasta el momento en que no hay muchos de estos archivos. La evaluación de una muestra de análisis y carga utilizando un método "directo" similar ~ 4000 archivos (~ 4 GB) dio 8-9 horas de trabajo.
Tal cantidad de archivos aparecieron por una razón. En primer lugar, jira tiene límites de tiempo para una sesión REST, es imposible sacar todo con una viga. En segundo lugar, al integrarse en el circuito productivo, se espera la carga diaria de datos sobre tareas actualizadas. En tercer lugar, y esto se mencionará a continuación, la tarea es muy buena para el escalamiento lineal y debe pensar en la paralelización desde el primer paso.
Incluso 10-15 iteraciones en la etapa de análisis de datos, identificando el conjunto mínimo de parámetros requerido, detectando situaciones excepcionales o erróneas, y desarrollando algoritmos de posprocesamiento dan costos en la cantidad de 2-3 semanas (solo contando el tiempo).
Naturalmente, dicho "rendimiento" no es adecuado para el análisis operativo, que está integrado en el circuito productivo, y es muy ineficaz en la etapa de análisis de datos inicial y desarrollo de prototipos.
Saltando todos los detalles intermedios, inmediatamente me dirijo a la respuesta. Recordamos a Donald Knuth, nos remangamos y comenzamos a microbancar todas las operaciones clave, cortando sin piedad todo lo que es posible.
La solución resultante se reduce a las siguientes 10 líneas (este es un esqueleto falso, sin un kit de cuerpo no funcional posterior):
library(tidyverse) library(jsonlite) library(readtext) fnames <- fs::dir_ls(here::here("input_data"), glob = "*.txt") ff <- function(fname){ json_vec <- readtext(fname, text_field = "texts", encoding = "UTF-8") %>% .$text %>% jqr::jq('[. | {issues: .issues}[] | .[]', '{id: .id, key: .key, created: .fields.created, type: .fields.issuetype.name, summary: .fields.summary, descr: .fields.description}]') jsonlite::fromJSON(json_vec, flatten = TRUE) } tictoc::tic("Loading with jqr-jsonlite single-threaded technique") issues_df <- fnames %>% purrr::map(ff) %>% data.table::rbindlist(use.names = FALSE) tictoc::toc() system.time({fst::write_fst(issues_df, here::here("data", "issues.fst"))})
¿Qué es interesante aquí?
- Para acelerar el proceso de carga, es bueno usar paquetes especializados y perfilados, como
readtext
. - El uso del analizador de transmisión
jq
permite traducir todo el enganche de los atributos necesarios a un lenguaje funcional, reducirlo al nivel de CPP y minimizar la manipulación manual de listas anidadas o listas en data.frame
. - Ha aparecido un paquete de
bench
muy prometedor para microbenchmarks. Le permite estudiar no solo el tiempo de ejecución de las operaciones, sino también la manipulación de la memoria. No es ningún secreto que puede perder mucho al copiar datos en la memoria. - Para grandes cantidades de datos y procesamiento simple, a menudo es necesario en la decisión final abandonar
tidyverse
y traducir las partes que consumen mucho tiempo a data.table
, en particular, las tablas se fusionan aquí usando data.table
. Y también todas las transformaciones en la etapa de postprocesamiento (que se incluyen en el ciclo a través de la función ff
también se realizan usando herramientas data.table
con el enfoque de cambiar datos por referencia, o paquetes construidos usando Rcpp
, por ejemplo, anytime
paquete de tiempo para trabajar con fechas y horas. - El primer paquete es muy bueno para descargar datos en un archivo y luego leerlo. En particular, lleva solo una fracción de segundo guardar todos los análisis del historial de jira durante 4 años, y los datos se guardan exactamente como los tipos de datos R, lo que es bueno para su posterior reutilización.
Durante la solución, se consideró un enfoque utilizando el paquete rjson
. La jsonlite::fromJSON
aproximadamente 2 veces más lenta que rjson = rjson::fromJSON(json_vec)
, pero fue necesario dejarla, porque hay valores NULL en los datos, y en la etapa de convertir NULL
a NA
en las listas rjson
por rjson
perdemos ventaja, y el código se vuelve más pesado.
Conclusión
- Tal refactorización condujo a un cambio en el tiempo de procesamiento de todos los archivos json en modo de subproceso único en la misma computadora portátil de 8 a 9 horas a 10 minutos.
- Agregar la paralelización de la tarea usando
foreach
prácticamente no cargó el código (+ 5 líneas) pero redujo el tiempo de ejecución a 5 minutos. - La transferencia de la solución a un servidor Linux débil (solo 4 núcleos), pero la ejecución en un SSD en modo multiproceso redujo el tiempo de ejecución a 40 segundos.
- La publicación en un circuito productivo (20 núcleos, 3 GHz, SSD) ha reducido el tiempo de ejecución a 6-8 segundos, lo cual es más que aceptable para las tareas de análisis operativo.
En total, permaneciendo dentro del marco de la plataforma R, una simple refactorización de código logró reducir el tiempo de ejecución de ~ 9 horas a ~ 9 segundos.
Las decisiones sobre R pueden ser bastante rápidas. Si algo no funciona para usted, intente verlo desde un ángulo diferente y utilice técnicas nuevas.
Publicación anterior: "Paracaídas analítico para gerente" .