Временные файлы в Yandex Cloud S3 с автоудалением
Как создавать временные файлы в Yandex Cloud S3 с автоматическим удалением через lifecycle. Presigned URL для прямой загрузки в NestJS, настройка правил бакета, примеры кода и лучшие практики для избежания засорения хранилища.
Как создавать временные файлы в Yandex Cloud S3 с автоматическим удалением через определённое время?
Задача:
Создание экскурсий с фотографиями, хранящимися в Yandex Cloud S3. Чтобы избежать перегрузки бэкенда по памяти и CPU (DoS), фронтенд получает presigned URL для прямой загрузки файлов в S3.
Проблема:
Файлы загружаются и остаются в хранилище навсегда, что приводит к засорению. Необходимо реализовать механизм, чтобы файлы автоматически удалялись, если их не переместили в постоянное хранилище (например, другую папку).
Текущая реализация:
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!,
},
});
}
}
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
- Настройка S3 lifecycle для автоудаления
- Генерация presigned URL в NestJS
- Полная интеграция в приложение
- Лучшие практики и мониторинг
- Источники
- Заключение
Введение в временные файлы 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):
{
"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:
<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 подхватит.
Обновленный метод:
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:
@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 чистит.
Источники
- Yandex Cloud: Managing lifecycles
- Yandex Cloud: Bucket lifecycle XML config
- Yandex Cloud: Object lifecycles concepts
- Yandex Cloud: Deleting objects
- AWS re:Post: Lifecycle for folders
- Medium: NestJS S3 upload
- AWS Blog: Presigned URLs
- AWS Docs: Presigned URLs
Заключение
Yandex S3 с lifecycle и presigned URL — идеальный паттерн для временных файлов: дешево, надежно, без DoS на бэкенд. Настройте префикс temp/, 1-дневное удаление, интегрируйте в NestJS — и хранилище самоочистится. Тестируйте на малом бакете, масштабируйте. Экономия места и нервов гарантирована.