---
title: "Laravel Read-Through Filesystem: ледаче перенесення сховища"
url: https://laravelukraine.com/blog/laravel-read-through-filesystem-ledace-perenesennia-sxovishha
date: 2026-08-20
source: https://laravel-news.com/laravel-read-through-filesystem?utm_medium=feed&utm_source=feedpress.me&utm_campaign=Feed%3A+laravelnews
---

# Laravel Read-Through Filesystem: ледаче перенесення сховища

Перенесення кількох мільйонів файлів з одного бакета в інший - це та частина міграції сховища, на яку ніхто не закладає бюджет. Масове `aws s3 sync` між провайдерами займає дні, коштує реальних грошей на передачу даних і копіює кожен застарілий аватар та осиротілий експорт разом із файлами, які люди дійсно відкривають. Альтернатива - код застосунку, який вручну перевіряє два диски, розсіює `Storage::disk('old')` фолбеки по всіх шляхах читання і ніколи не видаляється повністю.

Laravel 13.26 постачається з драйвером файлової системи `read-through`, який виконує танець з двома дисками всередині самого диска. Ви вказуєте йому основний та запасний диск, і читання спочатку перевіряють основний, потім повертаються до старого диска, коли файл ще не там, і копіюють файл на основний під час проходження. Гарячі файли мігрують самі при першому зверненні до них, а холодні залишаються там, де вони є, поки ви не вирішите, що з ними робити. Драйвер потрапив у фреймворк через [#61140](https://github.com/laravel/framework/pull/61140) від [@taylorotwell](https://github.com/taylorotwell).

## Налаштування диска

Диск read-through складається з двох інших дисків у `config/filesystems.php`. Посилайтеся на них за назвою:

```php
'disks' => [
    'r2' => [
        'driver' => 's3',
        // облікові дані Cloudflare R2...
    ],
    'legacy-s3' => [
        'driver' => 's3',
        // бакет, який ви покидаєте...
    ],
    'assets' => [
        'driver' => 'read-through',
        'primary' => 'r2',
        'fallback' => 'legacy-s3',
    ],
],
```

Код застосунку використовує `Storage::disk('assets')` як будь-який інший диск, і в цьому суть: контролери та завдання не знають, що існує два бакети. Обидва `primary` та `fallback` також приймають масив конфігурації замість назви диска, коли ви не хочете реєструвати базові диски окремо. Менеджер валідує пару під час резолюції, тому відсутня сторона, той самий диск з обох сторін або диск, який посилається на себе, викидає `InvalidArgumentException` замість того, щоб зазнати невдачі при першому використанні.

## Що куди йде

Правила маршрутизації варто засвоїти перед тим, як розміщувати це перед продакшн трафіком:

- **Читання** (`get()`, `readStream()`) перевіряють основний диск, потім запасний. Попадання в запасний копіює файл на основний, а потім повертає вміст. Потокові читання буферизуються через потік `php://temp`, а не завантажують файл у пам'ять
- **Запис, видалення, переміщення та копіювання** спрямовані тільки на основний диск
- **Списки директорій** надходять тільки з основного диска, тому `files()` показує лише те, що було просунуто або записано після переключення
- **Перевірки існування та метадані** (`exists()`, `size()`, `mimeType()`, `lastModified()`, `url()`, `temporaryUrl()`) звертаються до того диска, який містить файл, без запуску копіювання

Два з цих правил є гострими краями. Списки відображають тільки основний диск, тому все, що ітерує директорію для пошуку файлів, не побачить непросунутий контент на запасному диску. А видалення торкається тільки основного диска, тому видалення файлу, який все ще існує на запасному, воскрешає його при наступному читанні. Під час міграції це зазвичай нормально, запасний диск все одно зникає, але `delete()`, за яким слідує `exists()`, що повертає `true`, здивує вперше, коли ви на це натрапите.

Просування навмисно зроблено за принципом "найкращих зусиль". Якщо копіювання на основний диск не вдається, читання все одно успішне з запасного, і виняток проковтується, виходячи з теорії, що повний основний диск не повинен відключати ваші завантаження. Змініть це за допомогою `'throw_on_promotion_failure' => true`, коли ви хочете знати про це негайно.

## Читання без копіювання

Іноді вам потрібне шарування без міграції. [#61155](https://github.com/laravel/framework/pull/61155) від [@jimbojsb](https://github.com/jimbojsb) додав опцію `copy` саме для цього:

```php
'assets' => [
    'driver' => 'read-through',
    'primary' => 'local-assets',
    'fallback' => 'production-s3',
    'copy' => false,
],
```

З `copy => false` попадання в запасний обслуговуються безпосередньо, і нічого не просувається. Випадок використання, викладений у PR, - це середовище розробки, заповнене знімком продакшн бази даних: рядки посилаються на файли, які існують тільки в продакшн бакеті, і це налаштування дозволяє цим файлам рендеритися локально без повільного дзеркалювання бакета на ваш ноутбук. Це також підходить для обережної першої фази реальної міграції, коли ви переключаєте читання на новий макет і спостерігаєте за частотою помилок перед тим, як дозволити просуванню почати запис у новий бакет.

## План міграції

Підсумовуючи, переміщення зі старого S3 бакета на R2 виглядає так:

1. Створіть новий бакет і додайте його конфігурацію диска поряд зі старим
2. Перенаправте назву диска, яку ваш застосунок вже використовує (`assets` вище), на пару read-through: новий бакет як primary, старий бакет як fallback. Нові завантаження тепер потрапляють у R2, і кожен запитуваний файл просувається сам при першому читанні
3. Після того як шаблони доступу, які вас цікавлять, змінилися, найбільш читаний контент живе в основному диску. Заповніть довгий хвіст одноразовою синхронізацією або [пакетною фоновою операцією](https://laravel-news.com/laravel-chores), яка тепер має переміщувати лише те, про що ніхто не питав
4. Замініть конфігурацію диска read-through на звичайний диск, що вказує на новий бакет, і виведіть старий з експлуатації

Між кроками 2 і 4 немає деплою, який перемикає весь трафік одразу, і відкат - це зміна конфігурації, оскільки старий бакет ніколи не переставав бути повним.

## Додаткове читання

- [Примітки до випуску Laravel 13.26](https://laravel-news.com/laravel-13-26-0), де драйвер вийшов разом із debounced listeners та `Queue::forward()`
- [Використання AWS S3 для Laravel Storage](https://laravel-news.com/using-aws-s3-for-laravel-storage), для налаштування S3 диска, на якому будуються наведені вище приклади
