Достаточно часто возникает потребность проведения периодических вычислений и подготовки консолидированного отчета по самодостаточным данным. Т.е. по данным, которые хранятся в виде файлов. Это могут быть данные, набранные из открытых источников, различные документы и excel таблицы, выгрузки из корпоративных систем. Данные в сыром виде могут занимать как несколько мегабайт, так и несколько гигабайт. Данные могут быть обезличенными, либо содержать конфиденциальную информацию. В том случае, когда код вычислений помещается в репозиторий, а работа ведется более чем одним человеком более чем на одном компьютере, возникает проблема сохранения консистентности кода и данных. При этом необходимо еще обеспечить соблюдение разных прав доступа к коду и данным. Что делать?
Является продолжением предыдущих публикаций.
RStudio сейчас активно разрабатывают пакет pins
для решения этой проблемы. К сожалению, применяемые бэкенд решения несколько непопулярны и дороговаты для применения на просторах нашей страны. AWS, Azure, Google cloud… за каждый чих надо платить, и за хранение и за трафик. Аутентификацию AWS4 pins
пока не поддерживает, так что Yandex cloud пока тоже в стороне, хотя и он не бесплатен.
С другой стороны, команды аналитиков, работающих над конкретными задачами, как правило, невелики (не более 5-10 человек). Многие используют Google drive, One drive и пр., в платном или бесплатном формате. Почему бы не воспользоваться уже приобретенными ресурсами? Ниже предлагается один из возможных workflow.
Общий план
- Вычисления должны проводиться локально на машине, а значит на машине должна быть актуальная реплика всех необходимых для проведения вычислений данных.
- Код должен быть под системой контроля версий. Данные туда не должны никоим образом попадать (потенциальные объем и конфиденциальность). Будем хранить реплику данных либо в отдельной папке в проекте (включив ее в
.gitignore
), либо во внешней относительно проекта директории. - Хранилищем мастер данных будет выступать google drive. Права на доступ к директориям развешиваем в нем же.
Осталось дело за малым. Необходимо реализовать функционал синхронизации локальной реплики данных с облаком. Авторизация и аутентификация обеспечивается google.
Ниже код.
library(googledrive)
library(memoise)
# синхронизация кэша с google disk
drive_user()
updateGdCache(here::here("data/"), cloud_folder = "XXX___jZnIW3jdkbdxK0iazx7t63Dc")
updateGdCache <- function(local_folder, cloud_folder){
# обновляем весь кэш одним махом
cache_fname <- "gdrive_sig.Rds"
# 0. Делаем memoise на загрузку данных из гугла
getGdriveFolder <- memoise(function(gdrive_folder){
drive_ls(as_id(gdrive_folder), recursive = FALSE)
})
# 1. Проверяем наличие и загружаем облачные идентификаторы указанной папки
cloud_gdrive_sig <- purrr::possibly(getGdriveFolder, NULL)(cloud_folder)
# Если в облаке ничего нет или связи с ним нет, то и делать дальше нечего
if(is.null(cloud_gdrive_sig)) {
message("Some Google Drive issues happened. Can't update cache")
return()
}
# 2. Выцепляем директорию и имя файла из пути
fdir <- if(fs::is_dir(local_folder)) local_folder else fs::path_dir(local_folder)
# 3. Загружаем список локальных файлов на файловой системе
local_files <- fs::dir_ls(fdir, recurse = FALSE) %>%
fs::path_file()
# 4. Проверяем наличие и загружаем локальный кэш облачных идентификаторов
local_gdrive_sig <- purrr::possibly(readRDS, NULL, quiet = TRUE)(fs::path(fdir, cache_fname))
if(is.null(local_gdrive_sig)){
# Если локального кэша нет, то сверять нечего, просто загружаем все из облака
# сохраняем структуру, удаляем все данные
local_gdrive_sig <- cloud_gdrive_sig %>%
dplyr::filter(row_number() == -1)
}
# актуализируем сигнатуры локальных файлов с учетом реально существующих файлов
local_gdrive_sig <- local_gdrive_sig %>%
dplyr::filter(name %in% local_files)
# 5. Сверяем идентификаторы и времена последнего изменения, оставляем файл на обновление, если есть отличия
# Облако первично, по нему формируем список файлов для загрузки
reconcile_tbl <- cloud_gdrive_sig %>%
dplyr::rename(drive_resource_cloud = drive_resource) %>%
dplyr::left_join(local_gdrive_sig, by = c("name", "id")) %>%
tidyr::hoist(drive_resource_cloud, cloud_modified_time = "modifiedTime") %>%
tidyr::hoist(drive_resource, local_modified_time = "modifiedTime") %>%
# TODO: надо сверять время, а тут времена изменения выступают как строки
# для отсутствующих локальных файлов время модификации = NA
dplyr::mutate(not_in_sync = is.na(local_modified_time) | cloud_modified_time != local_modified_time)
# 6. Выгружаем файлы на диск
syncFile <- function(fpath, id){
res <- purrr::possibly(drive_download, otherwise = NULL)(as_id(id), path = fpath, overwrite = TRUE, verbose = TRUE)
ifelse(is.null(res), FALSE, TRUE)
}
# пакетная загрузка, для сброса сигнатур в кэш оставляем только те, что успешно загрузились
sync_gdrive_sig <- reconcile_tbl %>%
dplyr::filter(not_in_sync == TRUE) %>%
dplyr::mutate(fpath = fs::path(fdir, name)) %>%
dplyr::mutate(sync_status = purrr::map2_lgl(fpath, id, syncFile)) %>%
dplyr::select(name, id, sync_status)
# 7. Сбрасываем в локальный кэш только файлы, находящиеся в синхронизации
# Собираем все воедино
cloud_gdrive_sig %>%
# исключаем ошибочные файлы
dplyr::anti_join(dplyr::filter(sync_gdrive_sig, sync_status == FALSE), by = c("name", "id")) %>%
saveRDS(fs::path(fdir, cache_fname))
}
В качестве пути указываем идентификатор папки в google drive, можно взять его из адресной строки браузера. Идентификатор будет неизменным, даже если папка будет перемещаться в драйве.
Просто, компактно, удобно и бесплатно.
Пара замечаний
- Есть проблемы с кодировкой у
gargle
0.4.0 версии. Надо грузить dev версию. Подробнее здесь. - Есть проблемы с авторизацией на RStudio Server «Unable to authorize from RStudio Server #79 {Closed}», но идеи по обходному пути можно поглядеть здесь.
Предыдущая публикация — «Программирование и новогодняя елка, можно ли их совместить?».