Файл на 10 ГБ загружался сорок минут, индикатор показал 94 %, и тут соединение оборвалось. Первый вопрос, который задаёт себе человек: «Неужели всё сначала?» Второй, более точный: «Сколько байт сервер уже принял по-настоящему?» Ответ на него определяет всё остальное: и пользовательский опыт, и то, сколько диска вы должны держать в запасе.

Если считать по индикатору в браузере, потеряно будет около 600 МБ. Но индикатор показывает, сколько байт клиент отдал в сеть, а не сколько сервер записал и подтвердил. Эти числа могут отличаться: часть данных могла застрять в буферах, на промежуточном прокси или в сетевом стеке. Поэтому при возобновлении правильно спросить не себя, а сервер. Именно так устроен протокол возобновляемой загрузки tus, на котором удобно строить временный обмен большими файлами.

Ниже разберём путь целиком: как работает продолжение после обрыва, почему вход пользователя не относится к протоколу передачи, как посчитать диск под одновременные загрузки и готовые файлы, чем отличается срок брошенной загрузки от срока выданного результата и как сделать так, чтобы место освобождалось по правилу, которое вы заранее записали.

Сервер помнит позицию: HEAD, offset и PATCH

В описании протокола tus идея простая. Загрузка — это ресурс на сервере. Узнать, до какой позиции он заполнен, можно запросом HEAD, а продолжить — запросом PATCH, указав эту позицию. Никакой магии: сервер хранит число принятых байт (offset), клиент при возвращении спрашивает это число и досылает остаток.

Вот как выглядит возвращение после обрыва. Условно: файл размером ровно 10 000 000 000 байт, ссылка на ресурс загрузки уже получена при создании. Клиент вернулся в сеть и спрашивает состояние:

HEAD /files/24e533e02ec3bc40c387f1a0e460e216 HTTP/1.1
Host: files.example.com
Tus-Resumable: 1.0.0

HTTP/1.1 200 OK
Upload-Offset: 9400000000
Upload-Length: 10000000000
Tus-Resumable: 1.0.0
Cache-Control: no-store

Сервер говорит: принято 9 400 000 000 байт из 10 000 000 000. Осталось 600 000 000 байт, то есть 6 % файла. Клиент продолжает ровно с этой позиции:

PATCH /files/24e533e02ec3bc40c387f1a0e460e216 HTTP/1.1
Host: files.example.com
Tus-Resumable: 1.0.0
Content-Type: application/offset+octet-stream
Upload-Offset: 9400000000
Content-Length: 600000000

[600 000 000 байт данных]

HTTP/1.1 204 No Content
Upload-Offset: 10000000000
Tus-Resumable: 1.0.0

В ответе новый offset совпадает с полным размером, значит, загрузка завершена. Если связь опять упадёт на середине этого PATCH, повторится та же последовательность: снова HEAD, снова сравнение, снова досылка только недостающего. Здесь и скрыта главная практическая ценность: цена обрыва измеряется не размером файла, а расстоянием между последним подтверждённым offset и концом.

Обратите внимание на одну деталь, которая часто путает. Клиент не должен сам решать, с какого места продолжать. Если он начнёт с позиции, которая не совпадает с offset сервера, запрос будет отвергнут, и это хорошо: так невозможно записать данные «внахлёст» или оставить дыру посередине. Источник правды один, и он на сервере.

Проверка целостности — отдельное решение

Докачка гарантирует, что вы продолжаете с нужной позиции, но не отвечает на вопрос, не исказились ли байты по дороге. Для этого в tus есть необязательное расширение checksum, которое позволяет проверять целостность отдельных PATCH-запросов. Слово «необязательное» здесь ключевое: конкретный сервер может его не поддерживать, и это надо проверить по его документации и ответам, а не предполагать. Если расширения нет, а файлы критичны, разумно добавить собственную сверку на уровне приложения: например, отправитель публикует контрольную сумму рядом со ссылкой, а получатель сравнивает её после скачивания.

Кто вы: аутентификация живёт отдельно

Протокол tus описывает, как передавать байты, но не отвечает на вопрос, кому это разрешено. По описанию протокола, аутентификация и авторизация оставлены конкретной серверной реализации. Для проекта это означает практическое решение: перед сервером загрузки нужен слой, который отличает вашего сотрудника от случайного прохожего.

Какие варианты вообще бывают, если вы строите сервис для небольшой команды или клиентского кабинета:

  • Ваш бэкенд выдаёт право на загрузку. Пользователь входит в кабинет, приложение проверяет его права и только после этого создаёт загрузку или даёт токен для неё. Сервер передачи видит уже проверенный запрос. Подробнее такую схему с серверным компонентом tus мы разбирали в статье про загрузку больших файлов с докачкой на VPS.
  • Обратный прокси проверяет вход. Запросы без действительной сессии или токена отсекаются раньше, чем дойдут до приёма данных. Важно, чтобы проверка выполнялась на каждый запрос, включая HEAD и PATCH: докачка идёт через новые запросы, и каждый из них должен нести подтверждение права.
  • Отдельный файловый сервис с учётными записями. Если людям удобнее не браузер, а SFTP или веб-клиент, можно взять сервер, где аккаунты и права — штатная часть.

Третий вариант хорошо иллюстрирует SFTPGo. Согласно документации по возможностям SFTPGo, он поддерживает отдельные виртуальные аккаунты, права на пользователя и каталог, аутентификацию по ключу или паролю. Права различают просмотр списка, загрузку, перезапись, скачивание и удаление. Для временного обмена это ценно: сотруднику можно разрешить загружать и скачивать, но не удалять и не перезаписывать, а внешнему партнёру выдать аккаунт только на один каталог без права просмотра соседних.

Тут нужна честная оговорка: SFTPGo и tus — разные вещи, и из документации не следует, что одно заменяет другое. Протокол докачки через HTTP и файловый сервис с аккаунтами можно использовать каждый по назначению. Если вашим пользователям достаточно клиента SFTP или веб-интерфейса, tus может не понадобиться вовсе. Если вам нужна докачка прямо из страницы в браузере, tus-клиент и сервер как раз для этого сделаны, а вход вы строите вокруг него.

Есть и вторая сторона аутентификации — что вы потом можете доказать. В описании журналов SFTPGo подчёркивается, что журнал фиксирует имя учётной записи, а не личность человека за экраном. Это стоит помнить при разборе инцидентов: если один аккаунт выдан на троих, в логе всё равно будет одно имя. Для временного обмена правило простое: один человек или одна задача — одна учётная запись, общие пароли не раздаём.

Арифметика диска: сколько места нужно на самом деле

Теперь самая приземлённая часть. Сервис с докачкой накапливает на диске файлы в двух состояниях, и считать их надо отдельно:

  • Незавершённые загрузки. Они занимают столько, сколько успели принять. Пока человек не вернулся, эти байты лежат без дела.
  • Готовые файлы. Загрузка закончена, ссылка выдана, получатели скачивают. Файл лежит целиком до тех пор, пока не сработает ваше правило удаления.

Грубая формула для худшего случая выглядит так:

Диск = (U × S) + (R × S × T) + запас

Где S — максимальный размер файла, который вы разрешаете, U — число одновременных незавершённых загрузок, R — сколько файлов максимального размера в день могут стать готовыми, T — сколько дней готовый файл хранится, а запас нужен для системы, журналов и просто чтобы диск не был забит до отказа.

Возьмём условный пример, чтобы увидеть порядок величин. Пусть лимит файла S = 10 ГБ, одновременно висит не больше U = 4 незавершённых загрузок, в день завершается до R = 5 файлов, срок хранения готового T = 3 дня. Тогда незавершённые занимают до 4 × 10 = 40 ГБ, готовые до 5 × 3 × 10 = 150 ГБ. Итого 190 ГБ, и с запасом в 20 % получается около 228 ГБ. Это уже упирается в предел диска средней конфигурации: даже тариф с 240 ГБ NVMe оставляет мало места под операционную систему и остальное.

Теперь посмотрим, какие рычаги реально двигают результат:

Как меняется расчёт при разных настройках (условный пример: S = 10 ГБ, U = 4, R = 5, запас 20 %)
Срок хранения готового, TНезавершённыеГотовыеС запасом 20 %
1 день40 ГБ50 ГБ108 ГБ
2 дня40 ГБ100 ГБ168 ГБ
3 дня40 ГБ150 ГБ228 ГБ
7 дней40 ГБ350 ГБ468 ГБ

Из таблицы видно главное: незавершённые загрузки — величина почти постоянная и ограничена вами через U и S. А вот готовые файлы растут линейно со сроком хранения. Неделя вместо трёх дней превращает аккуратный сервис в потребителя почти полутерабайта. Поэтому срок хранения — самый дешёвый способ управлять диском: он стоит вам не денег, а лишь договорённости с пользователями.

Есть и скрытая надбавка, которую легко пропустить. Некоторые серверные реализации при завершении загрузки собирают итоговый файл из частей или копируют его в итоговый каталог. Если ваша реализация так делает, на время сборки файл существует дважды, и в формулу нужно добавить ещё один S на каждую параллельную сборку. Проверяется это просто: загрузите тестовый файл и посмотрите на занятое место до и после завершения, пока не удалены временные данные. Не предполагайте, измерьте на своей конфигурации.

Чем ограничить расход, а не только посчитать

Расчёт бесполезен, если ничто не удерживает систему в его рамках. Здесь пригодятся лимиты. В документации SFTPGo перечислены квоты по объёму и числу файлов, отдельные ограничения скорости загрузки и скачивания и лимиты сессий. Если ваша схема строится на нём, то квота на пользователя превращает «U × S» из надежды в правило: один аккаунт не сможет в одиночку занять весь диск. Лимит сессий защищает от ситуации, когда десять параллельных соединений от одной учётки забивают канал и процессор.

Если же вы берёте tus и собственный бэкенд, те же ограничения придётся реализовать самим: отказывать в создании новой загрузки, когда число активных превысило U, и отвергать заявленный размер больше S ещё на этапе создания, до первого байта. Ранний отказ дешевле всего: человек сразу видит причину, а диск не занят.

Исходящий трафик тоже надо посчитать

Диск — только половина арифметики. Каждый получатель скачивает файл целиком. Если пять файлов по 10 ГБ в день забирают по три человека, исходящий поток — 150 ГБ в сутки, и эти скачивания придутся на те же рабочие часы, что и загрузки. Ограничение скорости скачивания на пользователя, о котором говорит документация SFTPGo, помогает не отдавать весь канал одной передаче. Условия по трафику вашего тарифа сверяйте в предложении хостинга отдельно: из ресурсов процессора, памяти и диска их вывести нельзя.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Два срока, которые нельзя путать

Теперь о самом частом заблуждении. У временного обмена есть два независимых таймера, и у них разный смысл.

Первый — срок жизни незавершённой загрузки. В протоколе tus для этого есть необязательное расширение expiration, а заголовок Upload-Expires сообщает, до какого момента сервер готов держать недогруженный ресурс. Логика понятна: человек начал отправку и пропал, а недозагруженные 7 ГБ продолжают занимать диск. Через определённое время такой остаток должен исчезнуть, и клиент это знает заранее.

Второй — срок хранения готового файла, который вы выдали получателю. Это уже не про протокол загрузки. По описанию tus, Upload-Expires относится именно к незавершённой загрузке и не является общей политикой удаления выданных файлов. Протокол вообще не описывает ни выдачу ссылок, ни их срок. Если вы рассчитываете, что истечение в tus само уберёт скачиваемый файл через три дня, этого не произойдёт: такое правило придётся написать самим.

Разница ощутимо влияет на формулу диска. Первый срок управляет слагаемым U × S: чем короче жизнь брошенных загрузок, тем быстрее освобождается место, которое никому не пригодилось. Второй срок T управляет слагаемым R × S × T. Настраивать их можно независимо. Разумная комбинация для небольшой команды: брошенная загрузка живёт сутки, готовый файл — два-три дня после завершения. Первое число достаточно, чтобы человек вернулся после ночи и продолжил, второе — чтобы получатель успел скачать.

Заранее скажите пользователям и то, чего правило удаления не умеет. Оно убирает файл с вашего сервера, но не отзывает копии, которые получатель уже скачал, и не стирает содержимое из резервных копий диска или из кэшей, если вы их делаете. Обещание «файл исчезнет» корректно формулировать так: «ссылка перестанет работать, а файл будет удалён с сервера в такой-то срок».

Как удалять готовые файлы по понятному правилу

Правило удаления должно отвечать на три вопроса: от какого момента отсчитывать срок, что удалять и как узнавать об ошибках.

Момент отсчёта обычно выбирают одним из двух способов. Первый — время завершения загрузки: проще всего реализовать, и срок для отправителя предсказуем. Второй — время последнего скачивания: щедрее к получателям, но тогда файл, который кто-то скачивает раз в день, может жить вечно, и ваша формула T теряет верхнюю границу. Для предсказуемого расхода диска лучше первый вариант, а продление делать осознанным действием, которое отправитель выполняет сам.

Технически самый простой исполнитель — периодическая задача на сервере. Например, раз в час она просматривает каталог готовых файлов и удаляет те, что старше заданного возраста. Сначала запустите её в режиме «только показать»:

find /srv/exchange/ready -type f -mmin +2880 -print

Число 2880 здесь — сорок восемь часов в минутах, то есть двое суток. Когда список вас устроил и вы убедились, что время изменения файла действительно отражает момент завершения загрузки, а не, скажем, момент исходной даты на компьютере отправителя, замените -print на -delete. Последняя оговорка существенна: некоторые клиенты и серверы переносят исходную дату файла, и тогда свежезагруженный файл может выглядеть «старым» и исчезнуть слишком рано. Проверьте это на тестовом файле до включения удаления, а при сомнениях храните дату завершения в отдельной записи.

Если вы используете SFTPGo, у него есть Event Manager и события передач, о которых говорится в той же документации по возможностям. Это означает, что реакция на загрузку, скачивание или расписание может быть настроена внутри самого продукта. Но сама по себе документированная возможность не означает, что уведомления и очистка у вас уже работают: правило нужно создать и проверить на тестовом аккаунте. Какие именно действия доступны в вашей версии, смотрите в документации установленной версии, не полагаясь на память.

Отдельное внимание — последствиям удаления для тех, кто в этот момент скачивает файл. Если очистка срабатывает по часам, а получатель качает 10 ГБ по медленной связи, файл может исчезнуть посреди передачи. Простое практическое решение: удалять не с точностью до минуты, а с «льготным» запасом, и не трогать файлы, у которых есть активное соединение. Это та самая мелочь, о которой пользователь не думает, пока не столкнётся.

Что видно в журналах, когда всё работает и когда нет

Наблюдение за передачами не требует сложной системы. В журналах SFTPGo события выходят потоком JSON, а поле sender указывает вид события. Записи о передачах содержат тип (загрузка или скачивание), имя пользователя, путь к файлу, число байт, время, идентификатор соединения, протокол и сообщение об ошибке, если она была.

Из этих полей складываются несколько полезных вопросов к собственному сервису:

  • Совпадает ли size_bytes с ожидаемым? Если загрузка закончилась с ошибкой, а размер меньше заявленного, перед вами обрыв, и клиент должен вернуться и дослать остаток.
  • Есть ли повторяющиеся ошибки у одной учётной записи? Это может быть плохая связь у человека, слишком жёсткий лимит скорости или неверная настройка прокси.
  • Кто и сколько скачал? Если файл должен был уйти одному получателю, а скачиваний пятнадцать, пора выяснить, куда ушла ссылка. Помните, что журнал показывает аккаунт и соединение, но не личность.
  • Не растёт ли дисковое пространство быстрее расчёта? Для этого журнала мало: нужен простой замер свободного места, который сравнивается с вашей формулой.

Если вы строите сервис на tus и собственном бэкенде, ту же роль выполняют журналы вашего приложения и прокси. Минимум, который стоит записывать: идентификатор загрузки, заявленный размер, текущий offset, время создания и завершения, имя учётной записи. По этим данным легко увидеть и брошенные загрузки, и среднее время передачи, и реальные значения U и R, которые вы поначалу задали наугад.

Дальше можно сверить прогноз с практикой. Через неделю работы возьмите самые большие значения из журнала и подставьте в формулу вместо условных. Если реальное R втрое меньше заложенного, можно увеличить срок хранения или уменьшить диск. Если больше, лучше узнать об этом по графику, чем по сообщению «нет места на устройстве».

Какой VPS подойдёт под такой сервис

Для временного обмена важнее всего диск, а затем канал; процессору в основном нужно обслуживать соединения и, если включена, проверку целостности. На VPS MAATRIX используются NVMe-диски и процессоры AMD EPYC, предоставляется root-доступ, а в публичном предложении перечислены локации UK, US, RU и FR. Для вашего сервиса это означает возможность поставить любой нужный компонент и выбрать расположение ближе к тем, кто будет загружать и скачивать файлы. Оплата доступна картой РФ и через СБП, а VPS создаётся автоматически через пару минут после оплаты.

Линейка тарифов задаёт примерные масштабы диска: Ba — 1 vCPU, 2 ГБ RAM, 40 ГБ NVMe; Uab — 2 vCPU, 4 ГБ, 80 ГБ; Heka — 4 vCPU, 8 ГБ, 160 ГБ; Maat — 8 vCPU, 16 ГБ, 240 ГБ; Djed — 16 vCPU, 32 ГБ, 320 ГБ. Сопоставьте их с расчётом выше. Для условного примера с S = 10 ГБ и хранением в два дня нужно около 168 ГБ, и это уже ближе к Maat, чем к Heka. А если ограничить файл 5 ГБ или хранить сутки, хватит значительно меньшего. Но это ресурсы, а не обещание ёмкости: сколько одновременных передач вытянет конкретная конфигурация, показывает только замер на ваших файлах и вашем канале.

Если вам нужен не отдельный механизм обмена, а полноценное файловое хранилище с общим доступом, в публичном каталоге предложений значатся и готовые варианты, например Nextcloud с установкой. Точное наличие образа и операционной системы проверяйте в форме заказа, так как оно может зависеть от выбранной локации.

Один практический совет по размещению. Держите каталог незавершённых и каталог готовых файлов на одном разделе, но проверьте, что система не остановится при заполнении. Лучше задать порог оповещения заранее, скажем, при 80 % занятого места, и отвергать новые загрузки, когда он превышен, чем дожидаться ошибки записи посреди чужой передачи.

Когда всё сложилось: возвращаясь к оборванной загрузке

Вернёмся к файлу на 10 ГБ, который оборвался на 94 %. В правильно собранном сервисе на этом месте нет драмы. Человек открывает страницу, клиент отправляет HEAD, сервер отвечает, что принято 9 400 000 000 байт, и PATCH досылает недостающие 600 МБ. Брошенный остаток, если человек так и не вернулся, живёт ровно столько, сколько сообщает срок незавершённой загрузки, и исчезает сам, а число одновременных незавершённых ограничено заранее посчитанным U × S.

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