Атомно file_get_contents + file_put_contents

У меня есть кусок кода, который усекает файл журнала CSV до указанного конечного периода времени. Первое поле в каждой записи CSV — это отметка времени.

Приведенное ниже корректно удаляет, но не усекает журнал атомарно, что приводит к возможности потерять потерянные записи журнала между file_get_contents & file_put_contents. Так как новые записи идут внизу файла, нет риска испортить журнал до этого момента.

Я рассмотрел ручное выполнение операций внутри file_get_contents & file_put_contents, но документы PHP утверждают, что эти операции делают все виды супер-забавных оптимизаций voodoo и являются рекомендуемым методом выполнения того, что я хочу (получение всего содержимого файла в виде строки и заполнение файла строкой), поэтому мне было любопытно, если Есть способ использовать эти функции, не будучи небезопасным.

$time = time();
$fp = @fopen( $file, 'r' );
if ( $fp !== false ) {
$truncate = false;
$offset   = 0;

// find the first non-expired entry
while ( ( $fields = fgetcsv( $fp ) ) !== false ) {
if ( ! is_null( $fields ) && $time > ( $fields[0] + $purge_interval ) ) {
// we've reached the recent entries -- nothing beyond here will be removed
break;
}

$offset   = @ftell( $fp );
if ( false === $offset ) {
break;
}

$truncate = true;
}

@fclose( $fp );

if ( $truncate ) {
// need the next two lines atomically performed...
$data = file_get_contents( $file, false, null, $offset );
file_put_contents( $file, $data, LOCK_EX );
}
}

1

Решение

Не существует пуленепробиваемого способа одновременного внесения изменений на месте. Процесс должен будет отбросить один из этих атрибутов, чтобы быть осуществимым.

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

Например, утилита усечения будет делать

if (flock($fp, LOCK_EX)) {
$data = file_get_contents( $file, false, null, $offset );
file_put_contents( $file, $data, LOCK_EX );
flock($fp, LOCK_UN);
}

Создатели журнала также получат блокировку перед записью в файл. Интересным моментом является то, что авторы могут предпочесть попробовать неблокирующие блокировки и, если заняты, продолжать хранить журналы в памяти, чтобы не блокировать процесс в течение неизвестного периода времени; в этом случае процесс будет повторяться периодически.

2

Другие решения

Я думаю, что файлы журналов следуют шаблону «добавляй, только пиши» по причине: трудно сделать их производительными и редактируемыми одновременно. Вот почему обычные файлы журналов атомарно вращаются в файловой системе с помощью задания cron, чтобы позволить отрезать старую часть, возможно сжать или, в конечном итоге, удалить ее, а также сохранить новые данные в новом файле.

Поэтому я бы попытался отделить создание записей журнала от их обработки, имея отдельные файлы. Создайте новый файл журнала каждый день или каждый час. Обработайте старые файлы после запуска нового файла.

0