DevOps

Временные файлы в Yandex Cloud S3 с автоудалением

Как создавать временные файлы в Yandex Cloud S3 с автоматическим удалением через lifecycle. Presigned URL для прямой загрузки в NestJS, настройка правил бакета, примеры кода и лучшие практики для избежания засорения хранилища.

Как создавать временные файлы в Yandex Cloud S3 с автоматическим удалением через определённое время?

Задача:
Создание экскурсий с фотографиями, хранящимися в Yandex Cloud S3. Чтобы избежать перегрузки бэкенда по памяти и CPU (DoS), фронтенд получает presigned URL для прямой загрузки файлов в S3.

Проблема:
Файлы загружаются и остаются в хранилище навсегда, что приводит к засорению. Необходимо реализовать механизм, чтобы файлы автоматически удалялись, если их не переместили в постоянное хранилище (например, другую папку).

Текущая реализация:

typescript
import { S3Client } from "@aws-sdk/client-s3";
import { Injectable } from "@nestjs/common";

@Injectable()
export class S3ClientService {
 public client: S3Client;

 constructor() {
 this.client = new S3Client({
 region: "ru-central1",
 endpoint: "https://storage.yandexcloud.net",
 credentials: {
 accessKeyId: process.env.YC_S3_KEY_ID!,
 secretAccessKey: process.env.YC_S3_SECRET!,
 },
 });
 }
}
typescript
import { Injectable } from "@nestjs/common";
import { S3ClientService } from "./s3-client.service";
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

@Injectable()
export class S3Service {
 constructor(
 private readonly s3: S3ClientService
 ) { }

 async GetUploadUrl(key: string, contentType?: string) {
 const command = new PutObjectCommand({
 Bucket: process.env.YC_S3_BUCKET,
 Key: key,
 ContentType: contentType ?? 'application/octet-stream'
 });

 const url = await getSignedUrl(this.s3.client, command, {
 expiresIn: 3600
 });

 const objectKey = key.split('/')[1];

 return { url, key: objectKey };
 }
}

Файл загружается по presigned URL и остаётся в S3 indefinitely.

Вопрос: Какой правильный паттерн для создания временных файлов в S3 с автоудалением? Поделитесь примерами реализации для NestJS и AWS SDK.

В Yandex Cloud S3 временные файлы создавайте в папках с префиксом вроде temp/, используя S3 lifecycle для автоматического удаления через 1–7 дней — это избавит от ручной чистки и засорения хранилища. Фронтенд загружает их напрямую по presigned URL, генерируемому в NestJS через AWS SDK с эндпоинтом https://storage.yandexcloud.net, без нагрузки на бэкенд. Такой паттерн сочетает безопасность presigned с автоматизацией lifecycle правил, как рекомендует официальная документация.


Содержание


Введение в временные файлы Yandex S3

Представьте: пользователи загружают фото для экскурсий, но только часть переходит в постоянное хранилище. Остальное висит мертвым грузом, жрет место и деньги. В Yandex S3 (он же Yandex Object Storage) решение — комбо из presigned URL для прямой загрузки и S3 lifecycle для самоуничтожения файлов.

Почему это работает? Presigned дает временную ссылку на загрузку (скажем, на час), фронт шлет файл прямиком в бакет, минуя ваш сервер. А lifecycle? Это правила бакета: “удали все в temp/ через 24 часа”. Никаких cron-джобов, все нативно.

В вашем коде уже почти готово — S3Client с ru-central1 и эндпоинтом https://storage.yandexcloud.net. Добавим префикс temp/ в ключ и lifecycle. Стоимость? Для 2360+ запросов по “yandex s3” в месяц это must-have.

Но подождите, а если файл не удалили? Lifecycle сработает по дате создания объекта. Идеально для сценария “загрузил — проверил — переместил или bye”.


Настройка S3 lifecycle для автоудаления

S3 lifecycle в Yandex Cloud S3 — это XML или JSON-конфиг бакета, где вы задаете правила по префиксу. Для временных файлов: фильтр temp/, действие Expiration с Days: 1.

Пример JSON для удаления через сутки (сохраните как lifecycle.json):

json
{
 "Rules": [
 {
 "ID": "DeleteTempFiles",
 "Filter": { "Prefix": "temp/" },
 "Status": "Enabled",
 "Expiration": { "Days": 1 }
 }
 ]
}

Примените через AWS CLI:

aws s3api put-bucket-lifecycle-configuration \
 --bucket your-bucket \
 --lifecycle-configuration file://lifecycle.json \
 --endpoint-url=https://storage.yandexcloud.net

Хотите хитрее? Переход в COLD через 30 дней, потом delete через 365:

xml
<LifecycleConfiguration>
 <Rule>
 <ID>TempToCold</ID>
 <Status>Enabled</Status>
 <Filter><Prefix>temp/</Prefix></Filter>
 <Transition>
 <Days>30</Days>
 <StorageClass>COLD</StorageClass>
 </Transition>
 <Expiration><Days>365</Days></Expiration>
 </Rule>
</LifecycleConfiguration>

Загрузите так же CLI или UI консоли Yandex. До 1000 правил на бакет, применяются по порядку. Для multipart-загрузок добавьте AbortIncompleteMultipartUpload с DaysAfterInitiation: 1 — не完成的 uploads тоже самоуничтожатся, как в документации по удалению.

Папки сохранятся? Да, если создать маркер temp/ как пустой объект — lifecycle не трогает его, только реальные файлы.

А если версии включены? Добавьте NoncurrentVersionExpiration.


Генерация presigned URL в NestJS

Ваш S3Service крут, но доработаем: ключ с temp/ + короткий expiresIn. Фронт получит URL на час, загрузит — файл в бакете, lifecycle подхватит.

Обновленный метод:

typescript
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

async GetUploadUrl(filename: string, contentType?: string) {
 const key = `temp/${Date.now()}-${filename}`; // Уникальный ключ с префиксом
 const command = new PutObjectCommand({
 Bucket: process.env.YC_S3_BUCKET,
 Key: key,
 ContentType: contentType ?? 'image/jpeg' // Для фото экскурсий
 });

 const url = await getSignedUrl(this.s3.client, command, { expiresIn: 3600 }); // 1 час

 return { url, tempKey: key }; // Верните ключ для дальнейшего перемещения
}

Почему Date.now()? Уникальность, lifecycle считает от создания. ExpiresIn до 7 дней (604800 сек), но для temp — 3600 хватит.

После загрузки: проверьте файл, если ок — CopyObjectCommand в permanent/, потом DeleteObject temp. Или оставьте lifecycle чистить.

Из AWS блога по presigned ясно: это безопасно, токен не нужен фронту.


Полная интеграция в приложение

Соберем все. Контроллер NestJS:

typescript
@Controller('upload')
export class UploadController {
 constructor(private s3Service: S3Service) {}

 @Post()
 async getTempUrl(@Body() { filename, contentType }: { filename: string; contentType?: string }) {
 return this.s3Service.GetUploadUrl(filename, contentType);
 }

 @Post('move')
 async moveToPermanent(@Body() { tempKey, permanentKey }: { tempKey: string; permanentKey: string }) {
 // Copy to permanent/
 const copyCmd = new CopyObjectCommand({
 Bucket: process.env.YC_S3_BUCKET,
 CopySource: `${process.env.YC_S3_BUCKET}/${tempKey}`,
 Key: permanentKey
 });
 await this.s3.client.send(copyCmd);

 // Delete temp
 const deleteCmd = new DeleteObjectCommand({
 Bucket: process.env.YC_S3_BUCKET,
 Key: tempKey
 });
 await this.s3.client.send(deleteCmd);

 return { success: true };
 }
}

Фронт: fetch(‘/upload’) → url → XMLHttpRequest.put(url, file) → если ок, fetch(‘/move’).

Lifecycle как страховка: не переместили? Удалится сам. Масштаб? Бакеты без лимита объектов, но следите за стоимостью.

Из NestJS примера на Medium — аналогично, с ContentType.


Лучшие практики и мониторинг

Не все сразу удалять? Несколько правил: temp/short/ — 1 день, temp/long/ — 7 дней.

Ошибки? CLI покажет статус: aws s3api get-bucket-lifecycle-configuration --bucket your-bucket --endpoint-url=https://storage.yandexcloud.net.

Мониторинг: Cloud Logging + метрики бакета в консоли Yandex. Тест: залейте 10 файлов, подождите — пропадут?

Ограничения: presigned до 5ГБ, lifecycle не для групп бакетов. Версионинг? Включите для recovery.

Почему не cron? Lifecycle бесплатно, асинхронно, масштабируемо. Для “yandex cloud s3” с 224 запросами — топ-решение.

А если трафик spike? Presigned разгружает, lifecycle чистит.


Источники

  1. Yandex Cloud: Managing lifecycles
  2. Yandex Cloud: Bucket lifecycle XML config
  3. Yandex Cloud: Object lifecycles concepts
  4. Yandex Cloud: Deleting objects
  5. AWS re:Post: Lifecycle for folders
  6. Medium: NestJS S3 upload
  7. AWS Blog: Presigned URLs
  8. AWS Docs: Presigned URLs

Заключение

Yandex S3 с lifecycle и presigned URL — идеальный паттерн для временных файлов: дешево, надежно, без DoS на бэкенд. Настройте префикс temp/, 1-дневное удаление, интегрируйте в NestJS — и хранилище самоочистится. Тестируйте на малом бакете, масштабируйте. Экономия места и нервов гарантирована.

Авторы
Проверено модерацией
Модерация
Временные файлы в Yandex Cloud S3 с автоудалением