- PVSM.RU - https://www.pvsm.ru -
Есть такой популярный класс задач, в которых требуется проводить достаточно глубокий анализ всего объема цепочек работ, регистрируемых какой-либо информационной системой (ИС). В качестве ИС может быть документооборот, сервис деск, багтрекер, электронный журнал, складской учет и пр. Нюансы проявляются в моделях данных, API, объемах данных и иных аспектах, но принципы решения таких задач примерно одинаковы. И грабли, на которые можно наступить, тоже во многом похожи.
Для решения подобного класса задач R подходит как нельзя лучше. Но, чтобы не разводить разочарованно руками, что R может и хорош, но о-о-очень медленный, важно обращать внимание на производительность выбираемых методов обработки данных.
Является продолжением предыдущих публикаций [1].
Обычно, поверхностный подход «в лоб» не является самым эффективным. 99% задач, связанных с анализом и обработкой данных начинаются с их импорта. В этом кратком очерке рассмотрим проблемы, возникающие на базовом этапе импорта данных, на примере типовой задачи «глубокого» анализа данных инсталляции Jira.
Дано:
Пример штатного jira json на рисунке.

Требуется:
Теоретически в R есть несколько различных пакетов по загрузке json и преобразованию их в data.frame. Наиболее удобным выглядит пакет jsonlite. Однако, прямое преобразование иерархии json в data.frame затруднительно в силу многоуровневого вложения и сильной параметризированности структуры записей. Выцепление конкретных параметров, связанных, например, с историей действий, может потребовать различных доп. проверок и циклов. Т.е. задачу можно решить, но для json файла размером в 32 задачи (включает все артефакты и всю историю по задачам) такой нелинейный разбор средствами jsonlite и tidyverse занимает ~10 секунд на ноутбуке средней производительности.
Сами по себе 10 секунд — это немного. Но ровно до момента, пока этих файлов не становится слишком много. Оценка на сэмпле разбора и загрузки подобным «прямым» методом ~4000 файлов (~4 Гб) дала 8-9 часов работы.
Такое большое количество файлов появилось неспроста. Во-первых, jira имеет временные ограничения на REST сессию, вытащить все балком невозможно. Во-вторых, будучи встроенным в продуктивный контур, ожидается ежедневная выгрузка данных по обновленным задачам. В-третих, и это будет упомянуто дальше, задача очень хороша для линейного масштабирования и думать о параллелизации надо с самого первого шага.
Даже 10-15 итераций на этапе анализа данных, выявления необходимого минимального набора параметров, обнаружения исключительных или ошибочных ситуаций и выработки алгоритмов постпроцессинга дают затраты в размере 2-3 недели (только счетное время).
Естественно, что подобная «производительность» не подходит для операционной аналитики, встроенной в продуктивный контур, и очень неффективно на этапе первичного анализа данных и разработки прототипа.
Пропуская все промежуточные детали, сразу перехожу к ответу. Вспоминаем Дональда Кнута, засучиваем рукава и начинаем заниматься микробенчмаркингом всех ключевых операций безжалостно срезая все, что только можно.
Результирующее решение сводится к следующим 10 строчками (это сутевой скелет, без последующего нефункционального обвеса):
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"))})
Что здесь интересного?
readtext.jq [2] позволяет перевести все выцепление нужных атрибутов на функциональный язык, опустить его на CPP уровень и минимизировать ручные манипуляции над вложенными списками или списками в data.frame.bench [3] для микробенчамарков. Он позволяет изучать не только время исполнения операций, но и манипуляции с памятью. Не секрет, что на копировании данных в памяти можно терять очень много.tidyverse и переводить трудоемкие части на data.table, в частности здесь идет слияние таблиц средствами именно data.table. А также все преобразования на этапе постпроцессинга (которые включены в цикл посредством функции ff также сделаны средствами data.table с подходом изменения данных по ссылке, либо пакетами, построенными с применением Rcpp, например, пакет anytime для работы с датами и временем. fst. В частности, всего доли секунды уходят на сохранение всей аналитики jira истории за 4 года, а данные сохраняются именно как типы данных R, что хорошо для последующего их переиспользования.В ходе решения был рассмотрен подход с применением пакета rjson. Вариант jsonlite::fromJSON примерно в 2 раза медленнее, чем rjson = rjson::fromJSON(json_vec), но пришлось оставить именно его, потому как в даных бывают NULL значения, а на этапе преобразования NULL в NA в списках, выдаваемых rjson мы теряем преимущество, а код утяжеляется.
foreach практически не утяжелило код (+ 5 строчек) но снизило время исполнения до 5 минут.Итого, оставаясь в рамках платформы R, простым рефакторингом кода удалось добитьcя уменьшения времени исполнения с ~9 часов до ~9 секунд.
Решения на R могут быть вполне быстрыми. Если у вас что-то не получается, попробуйте взглянуть на это под другим углом и с применением свежих методик.
Предыдущая публикация — «Аналитический паRашют для менеджера» [4].
Автор: i_shutov
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/data-mining/299229
Ссылки в тексте:
[1] предыдущих публикаций: https://habrahabr.ru/users/i_shutov/posts/
[2] jq: https://stedolan.github.io/jq/
[3] bench: https://github.com/r-lib/bench
[4] «Аналитический паRашют для менеджера»: https://habr.com/post/416673/
[5] Источник: https://habr.com/post/429938/?utm_campaign=429938
Нажмите здесь для печати.