Я выполняю параллельные вычисления из данных в R, с 2000 параллельными заданиями, в каждом задании использую следующий код для сохранения окончательных результатов в формате RDS:
timestamp <- format(Sys.time(), "%Y%m%d%H%M%S")
timestamp <- paste0(timestamp, sample.int(1000, 1))
filename <- paste0("differences_", timestamp, ".rds")
saveRDS(differences, file = filename)
Я понял, что даже несмотря на то, что я добавил случайные четыре цифры к отметке времени, все равно возможно, что параллельные задания конфликтуют друг с другом. Возможным решением может быть проверка, существует ли уже файл с таким именем, и если да, то создайте новое имя:
while (file.exists(filename)) {
timestamp <- format(Sys.time(), "%Y%m%d%H%M%S")
timestamp <- paste0(timestamp, sample.int(1000, 1))
filename <- paste0("differences_", timestamp, ".rds")
}
Но я не уверен, что это полностью безопасно. Есть ли способ обеспечить уникальное имя файла для каждого результата параллельных заданий? Мне нужно хранить файлы по одному и тому же пути.
@RomanLuštrik Я думаю, что мог бы сделать случайные цифры длиннее, снизив вероятность конфликта имен файлов до приемлемой степени.
Вы можете добавить UUID к имени файла, используя uuid-пакет.
Короче говоря, этот подход совершенно небезопасен (проверка существования файла перед записью — это классический сценарий состояния гонки, а парадокс дня рождения учит нас, что использование простых случайных имен файлов приведет к удивительно частым коллизиям!), а также излишне сложен: вместо этого генерируется уникальные, последовательные имена файлов в основном процессе и передавать отдельные пути в подпроцессы при их запуске.
@Wimpel Я проверил пакет, насколько я понимаю, это сложный генератор случайных символов, с ним очень сложно столкнуться, но не совсем невозможно?
@KonradRudolph Это хороший подход. Согласно моим тестам, действительно 20% результатов отсутствуют, вероятно, из-за противоречивых имен.
Я никогда не доверяю случайным символам, чтобы избежать конфликта пространств имен (например, имена файлов параллельных заданий); иметь его в качестве компонента - это нормально, если есть достаточное количество других компонентов. Я предпочитаю для этого предложение Конрада о последовательных именах файлов, но если вы заранее не знаете, сколько вы будете создавать, то альтернативой является именование файлов maildir, где, я думаю, основными компонентами являются: наносекунды эпохи, PID процесса записи и счетчик каждого процесса.
По теме: сохранение/запись требует времени, иногда прерывается или завершается сбоем, что рискует оставить неполный *.rds файл. В конечном итоге это повлияет на вас, когда вы будете выполнять множество задач, особенно в средах HPC. Я использую что-то вроде saveRDS(..., file = "foo.rds.tmp"), за которым следует file.rename("foo.rds.tmp","foo.rds"), чтобы эмулировать атомарную запись файла foo.rds — таким образом я знаю, что *.rds завершен, если я его вижу.





Я предлагаю это как немного более сложный вариант предложения @KonradRudolph (имена файлов предварительной последовательности для каждой параллельной задачи), который намного проще и может быть предпочтительнее в большинстве ситуаций.
Это предложение ориентировано на общие файловые системы, в которых имена файлов не могут быть легко определены заранее. Проблемы с общими файловыми системами начинаются с того, что использование существующих файлов для обеспечения уникальности теоретически является гарантированным состоянием гонки, несколько воспроизводимо плохим в локальной файловой системе, а в NFS и других общих файловых системах задержка может вызвать серьезные проблемы. Поэтому нам нужно создать имя файла «в реальном времени», которое имеет очень низкую вероятность коллизий. (В моем примере я пишу в файловую систему Lustre, где задержка создания файла может составлять более 5-10 секунд на всех узлах HPC.)
Вот функция, которая создаст такое имя файла:
#' Create a "guaranteed-unique" filename
#'
#' @param path character, the directory in which the new file will be
#' created
#' @param fileext optional character, appended to the new filename
#' @param create logical, whether to "touch" the file
#' @return character, the filename, optionally created
#' @export
unique_filename <- local({
.host <- gsub("[/:]", "_", Sys.info()["nodename"])
.count <- 0L
function(path = character(0), fileext = "", create = FALSE) {
now <- as.numeric(Sys.time())
# if we put this up with `.host`, I suspect the `future` is
# transferring the old PID to new processes
.pid <- Sys.getpid()
# we look within all subdirs to make sure we won't have a "future"
# collision (though highly unlikely) and then discard the
# subdirectory component
filename <- sprintf(
"%0.06f.P%i.Q%i.%s%s",
now, .pid, .count, .host, fileext)
.count <<- .count + 1L
out <- if (length(path) && nzchar(path)) file.path(path, filename) else filename
if (create) fs::file_touch(out)
out
}
})
Он в значительной степени основан на соглашении об именах файлов, используемом в почтовом хранилище Maildir, где нужно было максимально экономично избегать конфликтов файлов на основе NFS. Я не использую все предложенные компоненты, поэтому данная реализация несколько ослаблена. При тысячах одновременных операций записи я не видел никаких коллизий.
Оно использует:
Одним из побочных эффектов имени файла, начинающегося с компонента «время», является то, что файлы естественным образом сортируются в хронологическом порядке, если это привлекательно. Использование случайных имен файлов (uuid или других) не позволяет сделать это так просто.
Раньше мой подход заключался в том, чтобы проверить, существует ли уже имя файла (и восстановить имя, если оно существует). При большом количестве случайных символов, временной метке и предположении, что запись файла относительно короткая, конфликт имен маловероятен. Если вам нужен надежный способ генерации имен файлов, вы можете заранее создать базу данных суффиксов и последовательно их исчерпать. Сложность настройки всей инфраструктуры для этого не может быть оправдана, поскольку конфликт имен файлов маловероятен.