Решения на базе R, как классические «отчетные», так и в контуре операционной аналитики, очень хорошо себя зарекомендовали в enterprise окружении. Несомненно, значительную роль в этом играет компания RStudio и ее увлеченный коллектив. В коммерческих продуктах RStudio можно не думать об инфраструктурных вопросах, а просто обменять небольшую денежку на готовые решение «из коробки» и положиться на их разработчиков и поддержку. В open-source редакциях, а большинство инсталляций в российских компаниях именно такие, приходится думать про инфраструктурные вопросы самостоятельно.

Решения на R хорошо закрывают нишу «средних данных», когда данных «чуть больше» чем влезает в excel или в ненастроенную реляционку и нужны сложные алгоритмы и процессинг, но когда разворачивать «пусковой комплекс» бигдаты еще более чем рано -- наши задачи пока еще в пределах орбиты Земли. Речь идет о десятках-сотнях террабайт в полном объеме, которые легко умещаются в бэкенд на Cliсkhouse. Важный момент: все находится во внутреннем контуре, в подавляющем большинстве случаев ПОЛНОСТЬЮ отрезанном от интернета.

Является продолжением серии предыдущих публикаций, уточняет публикацию «Конструктивные элементы надежного enterprise R приложения».

Проблематика

Для продуктивного решения необходимо обеспечить воспроизводимость результатов и вычислений. Задача воспроизводимости делится на несколько различных направлений. Крупными блоками можно выделить:

  • инфраструктурная воспроизводимость. Многие вопросы закрываются комбинацией технологий docker + renv + git.

  • программная воспроизводимость. Многие вопросы закрываются технологией пакетов и автотестов.

  • статистическая «похожесть» выдаваемых результатов. Тут уже возникает специфика каждой отдельной задачи. Ниже предложены отдельные моменты, позволяющие ее обеспечить.

В чем заключается сложность?

Алгоритмы, «выкатываемые в продуктив»

  • могут быть многофазными с совокупным временем расчета несколько часов;

  • могут использовать кроме данных из основного бэкенда множество дополнительных неструктурированных и «грязных» источников данных (внешние справочники, excel файлы, технические логи и т.д.);

  • опираются на данные, которые поступают от постоянно изменяемых объектов наблюдения и эволюционируют во времени непредсказуемым образом;

  • могут активно использовать случайные выборки из данных бэкенда;

  • могут в рамках своего жизненного цикла постоянно уточняться и модифицироваться.

  • могут иметь на выходе не один показатель, а семейства таблиц в которых каждая метрика характеризуются своим распределением;

  • оценка корректности работы алгоритма может быть проведена только в конце вычислений, по расчету стат. характеристик выданных таблиц.

В таких случаях крайне затруднительно сделать тестовый набор данных (рефересный снапшот) для автотестов и постоянно его актуализировать, а для ИТ служб задача бэкапа всего инстанса БД зачастую становится либо крайне дорогой либо непосильной. Приходится использовать продуктив и как тестовый комплекс и дополнять аналитические решения дополнительным модулем статистической самодиагностики, исполняемым как в продуктивном процессе так и по требованию. А также приходится применять широкий спектр средств отладки для быстрой диагностики возникших отклонений, как в prod контуре (постфактум), так и в dev среде.

И это вполне честная ситуация с точки зрения подразделения и конкретного владельца бюджета, финансирующего вычисления. Есть аппаратные средства и список работ, необходимые для решения задач, и они профинансированы. Создание тестового окружения напрямую никак не связано с основной задачей, разве что через непрерывность бизнеса. Но это достаточно тонкая тема и каждый раз требует компромисса. В одних случаях владелец бюджета вполне может решить для себя, что он готов на снижение готовности (availability) решения на X% при одновременном сокращении расходов на $Y. В других же случаях готовность может быть дороже денег.

Контроль в продуктивном контуре

Исходные постулаты

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

  • Техническая и логическая валидация поступающих параметров как при вызове собственных функций, так и при загрузке данных из источников.

  • Валидация стат. характеристик загружаемых источников позволяет обнаружить перекосы уже на этапе ввода.

  • Необходимо выбирать компромисс между глубиной охвата и сложностью проверок и временем их проведения.

  • Маркируйте используемые в расчетах данные и по мере возможности оставляйте на диске временные дампы data.frame в критических точках с тем, чтобы при отладке можно было повторно «проиграть» непонятную ситуацию, пойманную при очередном продуктивном запуске.

Логирование

Существуют несколько популярных пакетов для логирования, каждый может выбрать на свой вкус:

Также есть подходы к логированию warning и message, все очень хорошо расписано в документации на указанные пакеты. Стоит отметить, что в многопоточном исполнении логфайлы могут являться единственным окошком к сути происходящего в другом потоке.

С точки зрения формирования дампов, штатный подход с использованием .Rds файлов для данных среднего размера (1-1000 Гб Ram) никуда не годится. Существует 3 хорошие многопоточные альтернативы:

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

Валидация

Комбинируйте в зависимости от задачи и вкуса:

Есть и другие пакеты, если этого будет недостаточно. Любители альтернативных решений могут почитать репозиторий Win-Vector.

Трекинг пайплайнов

Очень часто вычисления проводятся через pipe (%>%). Все промежуточные результаты скрыты. Если что-то идет не так (а особенно часто «рвет» на слиянии со справочниками по «уникальному ключу», который ни разу не уникальный), то по выходу очень тяжело понять проблемный шаг. В таких случаях помогают пакеты, фиксирующие характеристики объектов, передаваемых посредством . с шага шаг.

Вот примеры полезных пакетов для трекинга:

  • tidylog. Тут важно, что tidylog перехватывает глаголы tidyverse, поэтому конструкции dpylr::mutate останутся без трекинга.

  • lumberjack. Сохраняем изменения

Отладка

Есть масса хороших публикаций насчет отладки, например:

Какие сценарии на практике оказываются крайне востребованными (shiny здесь не затрагиваем)?

  • browser(). Никаких точек останова в IDE. Хардкорное прерывание в любом месте и в любом сценарии исполнения. Пишем if(условие) browser() -- получаем условную точку останова. Бонусом -- доп. трюк ниже.

  • debug()/undebug()/debugonce(). Для отладки функций, в т.ч., прилинкованных из пакетов.

  • traceback(). Докапываемся до причины в цепочке ассертов.

  • options(datatable.verbose = TRUE). Что творится у основной рабочей лошадки data.table под капотом (план запроса, перформанс, ошибки).

  • utils::getFromNamespace и пр. Хирургический скальпель для модификации функций из пакетов.

  • Пакеты waldo и diffobj. Прецизионное сравнение небольших объектов.

  • pryr::object_size(). Честное «взвешивание» объектов.

  • Пакет reprex. Запрашиваем помощь друга.

  • Пакет gginnards. Отладка графиков ggplot.

Трюк по использованию browser(), отлаживаем внутренние циклы data.table.

library(data.table)
library(magrittr)

dt <- as.data.table(mtcars) %>%
  .[, {m <- head(.SD, 2); print(ls()); browser(); m}, by = gear]
#>  [1] "-.POSIXt"  "am"        "carb"      "Cfastmean" "cyl"       "disp"     
#>  [7] "drat"      "gear"      "hp"        "m"         "mpg"       "print"    
#> [13] "qsec"      "strptime"  "vs"        "wt"       
#> Called from: `[.data.table`(., , {
#>     m <- head(.SD, 2)
#>     print(ls())
#>     browser()
#>     m
#> }, by = gear)

Профилировка

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

Заключение

  1. Инструменты и методы приведены.
    Но что помогает более всего? Постоянно улучшать методы разработки и написания кода. Компактный, лаконичный, понятный и эффективный код будет содержать куда меньше ошибок.

  2. Для отдельного класса задач может оказаться целесообразно использовать make инструменты. drake/targets

  3. В практических задачах могут быть всякие сюрпризы, не всегда магия бигдаты помогает, читаем иронический детектив «Using AWK and R to parse 25tb»

  4. Огромное количество ненужной работы можно срезать еще на входе, обратившись к правилам приближенных вычислений. Шум на входе определяет точность на выходе. Вспоминаем «Справочник по элементарной математике», Выгодский М.Я., Глава II (Арифметика), параграф 33.

Предыдущая публикация -- «Как в enterprise приручить при помощи R технологии process mining?»