Skip to content

0074. Хранилище принимает готовый файл вместо копии ​

Статус: accepted. Owner: Ian Panaev. Дата: 2026-10-11.

Контекст ​

Аудит производительности (2026-10-11, этап 2) нашёл лишние полные копии больших объектов на пути записи. LocalBlobStore.put пишет объект один раз: во временный файл с хешем, затем жёсткая ссылка в blobs/. Но несколько путей сначала кладут байты в свою временную область на том же томе, а потом читают их заново в put, который снова пишет и хеширует:

  • raw-файлы без контрольной суммы (ADR 0064) и публикация npm: временный файл уже хеширован, затем ещё одно чтение, запись и хеш;
  • слой OCI: куски PATCH дописываются во временный файл, при PUT он целиком копируется в хранилище; на время копии объект занимает место дважды;
  • зеркала: каждая часть хешируется во временный файл, затем копируется в часть загрузки с повторным хешем.

Решение ​

  1. Временная область отдаёт свой файл как поток с меткой StagedFile: путь, а для raw-файлов и частей зеркал — размер и SHA-256, измеренные при записи. Слой приложения меток не видит и путей не получает — для него это обычный поток.
  2. LocalBlobStore принимает такой файл как объект (или часть) второй ссылкой на те же байты, без копии:
    • только если файл лежит на том же томе, что и хранилище; иначе и при отсутствии файла — прежнее копирование;
    • размер файла и измеренные размер и SHA-256 должны совпасть с заявленными; без измерения (OCI) файл один раз хешируется на месте;
    • перед тем как объект станет видимым, файл синхронизируется (fsync), затем ссылка и синхронизация каталога — гарантия долговечности та же, что у записи потоком.
  3. Внутренние чтения (сборка multipart, проверки хеша) идут блоками 1 МиБ; ответы клиентам — прежние 64 КиБ, на них настроен ограничитель полосы.

Почему измерению можно доверять ​

Размер и хеш считает тот же код, который пишет файл, с правами 0600 в каталоге данных; никто другой этот файл не открывает. Перед переносом хранилище переименовывает временный файл в собственное имя и убирает его после ссылки, поэтому через временное имя до опубликованных байтов уже не добраться. Это важно для OCI: временная область дописывает в существующий файл (r+), и повтор запроса после сбоя между переносом и удалением загрузки иначе писал бы в опубликованный объект. Теперь такой повтор не находит файла и получает BLOB_UPLOAD_INVALID. Если перенос не удался, файл возвращается под временное имя, если его не занял новый файл. Зеркало создаёт часть заново (wx), а не усекает её.

Что не меняется ​

  • Завершение multipart по-прежнему читает все части и пишет объект: SHA-256 всего объекта нельзя получить из хешей частей. Большие объекты завершаются в worker, не в HTTP-запросе.
  • npm по-прежнему читает tarball для разбора package.json и хешей integrity; убрана только третья копия в хранилище.

Последствия ​

  • Для raw, npm и зеркал — на одно полное чтение, запись и хеш меньше; для OCI — на одну запись меньше и без двойного места на время финализации.
  • Неопубликованный объект, если его файл после переноса всё-таки изменился бы, при повторе проверяется хешем и отвергается; опубликованные объекты неизменяемы.
  • Перенос не резервирует место: он не занимает новых байтов. Проверка свободного места остаётся для записи потоком.
  • Тесты: перенос без копии (тот же inode), после переноса временное имя свободно и запись через него не меняет объект, отказ при несовпадении размера или хеша возвращает файл, прежняя запись при отсутствии файла, перенос при заполненном томе.

Arkvory · ProAnimaStudio