--- topic: app-directories prefix: DIRS --- # Категории директорий приложения Всё, что приложение пишет на диск, делится на три категории по принципу создания и ценности содержимого: конфигурация, данные, кеш. Категория сразу отвечает на два вопроса, которые иначе выясняются чтением кода приложения: **кто создаёт** содержимое и **что будет, если его потерять**. Из категорий механически выводится состав бэкапа. Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и метки ПОЧЕМУ, МЕХАНИЗИРОВАНО и СНЯТО толкуются как описано в языке конвенций версии 1 — тогда и только тогда, когда написаны заглавными. ## Область действия Раскладка меняется вместе с миграцией данных, поэтому правила распространяются на **новые приложения**; существующие переезжают по мере касания, отдельной кампанией не переписываются. Разделять данные и кеш задним числом имеет смысл тогда, когда кеш заметен по объёму в бэкапе, а не ради самой схемы. ## Правила ### DIRS-1. Записываемые пути разложены по трём категориям **ДОЛЖЕН.** Каждая директория, в которую пишет приложение или деплой, относится к одной из трёх категорий: | № | Категория | Директория | Создаёт | Потеря содержимого | В бэкапе | |---|---|---|---|---|---| | DIRS-1.1 | конфигурация | `config/` | деплой | восстанавливается прогоном деплоя | нет | | DIRS-1.2 | данные | `data/` | приложение | невосполнима | да | | DIRS-1.3 | кеш | `cache/` | приложение | приложение перегенерирует | нет | Имена в таблице — умолчание для случая «одна директория на категорию». **ПОЧЕМУ.** Все дальнейшие решения — что попадает в бэкап (DIRS-4), что можно снести при нехватке места, что переживает переезд на другой диск — читаются из категории, а не выясняются по коду приложения. Без единой классификации каждое такое решение принимается заново и каждый раз чуть по-другому, а цена ошибки несимметрична: лишний кеш в снапшоте стоит места, потерянные данные не стоят ничего, потому что их больше нет. ### DIRS-2. Категория может состоять из нескольких директорий **ДОПУСКАЕТСЯ.** Директорий в категории столько, сколько нужно раскладке; принадлежность к категории задаётся не именем, а участием в списке бэкапа. **ПОЧЕМУ.** Явное разрешение снимает вопрос, не читается ли DIRS-1 как «ровно три директории». Крупные файлы отделяют от базы, чтобы двигать их между дисками независимо (`media/`, `uploads/` — та же категория «данные», что и `data/`); запрет на такое деление вынуждал бы либо держать всё на одном диске, либо выводить директорию из-под категорий вовсе. Категорию нельзя задавать именем ровно поэтому: имён в категории несколько, и выбираются они по содержимому. ### DIRS-3. Данные и кеш разделяются по тесту на пересоздание **ДОЛЖЕН.** Записываемый путь относят к данным или к кешу по содержимому: | № | Что лежит | Категория | |---|---|---| | DIRS-3.1 | база, загруженные файлы, сгенерированные артефакты | данные | | DIRS-3.2 | миниатюры, распакованные ассеты, кеш внешних ответов, перестраиваемые индексы | кеш | | DIRS-3.3 | спорный случай | `rm -rf` и запуск приложения заново: поднялось и наверстало само — кеш; не поднялось или поднялось пустым — данные | **ПОЧЕМУ.** Без внешнего теста граница проводится по ощущению «жалко потерять», а оно смещено в одну сторону: дорогой в пересборке кеш переезжает в данные и раздувает каждый снапшот. Дорого ≠ невосполнимо, и разделяет эти два свойства именно способность приложения пересоздать содержимое. Обратная ошибка — данные, названные кешем, — тестом обнаруживается в тот же момент, но дёшево: при попытке пересоздать, а не при попытке восстановить. ### DIRS-4. В бэкап идут данные, и только они **ДОЛЖЕН.** Директории категории «данные» — в списке бэкапа; конфигурация и кеш — нет. **ПОЧЕМУ.** Кеш раздувает снапшот содержимым, которое приложение восстановит само. Конфигурацию бэкапить не только незачем, но и вредно: там лежат секреты, а бэкапы уезжают в облако — источник истины для конфигурации остаётся в репозитории и хранилище секретов, а не в снапшоте. Ошибка в другую сторону дороже: директория данных, не попавшая в список, обнаруживается в единственный момент, когда исправить её уже нечем. ### DIRS-5. Список бэкапа ссылается на те же пути, что и создание директорий **ДОЛЖЕН.** Список выводится из категорий по DIRS-4 и ссылается на те же **объявления путей**, по которым директории создаются, а не набирается независимо. Объявление пути — то единственное место, где путь директории записан буквально: переменная деплоя, константа, поле конфигурации. Всё остальное на него ссылается. **ПОЧЕМУ.** Правило вывода механическое, но применяет его человек или шаблон — то есть ошибиться можно. Общая ссылка делает целый класс ошибок невозможным: переименование директории отражается в обоих местах сразу. Независимо набранный список расходится тихо — ни деплой, ни прогон бэкапа на это не жалуются, — и расхождение между тем, что бэкапится, и тем, что нужно, проявляется при восстановлении. ### DIRS-6. Способ попадания данных в бэкап зависит от того, кто отвечает за консистентность **ДОЛЖЕН.** Способ выбирается по тому, самодостаточны ли файлы на диске: | № | Данные | В бэкап | |---|---|---| | DIRS-6.1 | файлы самодостаточны на любой момент времени | копированием | | DIRS-6.2 | консистентность обеспечивает только сама СУБД | дампом: бэкапится директория дампов, сырой каталог базы — нет | **ПОЧЕМУ.** Файловый снапшот работающей СУБД не гарантирует консистентности: скопированный каталог может не восстановиться, и узнают об этом при восстановлении. Директория дампов — тоже данные, просто производные, поэтому DIRS-4 покрывает её без оговорок. Сырой каталог базы из списка при этом исключается: он удваивает объём снапшота и добавляет к надёжной копии заведомо ненадёжную. ### DIRS-7. Способ выбирается при заведении приложения **ДОЛЖЕН.** Решение «копировать или дампить» (DIRS-6) принимается, когда приложение заводят. **ПОЧЕМУ.** Неверный выбор ничем себя не проявляет, пока бэкап не понадобился: прогоны копирования проходят успешно и накапливают снапшоты, из которых база не поднимется. Отложить решение — значит принять его по факту первой неудачной попытки восстановления, то есть тогда, когда данных уже нет. ### DIRS-8. Приложение разводит записываемые пути по категориям **ДОЛЖЕН.** Конфигурация приложения задаёт отдельные пути для данных и для кеша, а не один каталог на всё. **ПОЧЕМУ.** Снаружи категория определяется только тогда, когда разным категориям соответствуют разные директории. Всё, сложенное в один каталог, заставляет составлять список бэкапа вручную, читая код приложения, — и пересматривать его при каждом обновлении, потому что новый подкаталог появляется молча. Приложение, которое не умеет разделять, тем самым дефектно; раскладка под этот дефект не подстраивается. ### DIRS-9. Приложение не пишет в директорию конфигурации **НЕ ДОЛЖЕН.** Записываемые пути приложения не указывают внутрь конфигурации. **ПОЧЕМУ.** Конфигурация восстанавливается прогоном деплоя (DIRS-1.1), поэтому всё, что приложение туда записало, следующий деплой затирает без предупреждения. Вдобавок директория конфигурации может быть подключена только на чтение — тогда запись отказывает в рантайме. Ни то ни другое не видно в момент, когда приложение настраивают.