Обещание без egress и глобальная масштабируемость
Cloudflare R2 позиционирует себя как объектное хранилище, которое не взимает плату за исходящий трафик. Это достигается за счёт интеграции с более чем 300 точками присутствия (PoP) глобальной сети Cloudflare. При запросе данных клиент получает их из ближайшего PoP, и передача происходит по внутренней инфраструктуре провайдера, а не через публичный интернет‑канал. Такой подход устраняет традиционные egress‑комиссии, характерные для сервисов типа AWS S3, и позволяет снизить затраты при масштабных загрузках пользовательского контента (аватары, фотографии, медиа‑файлы).
Общая архитектура R2
R2 построен на двух фундаментальных слоях:
- Метаданные – небольшие структуры, описывающие объект (имя, размер, версии, ссылки на блоки данных). Хранятся отдельно от самого контента и обслуживаются высокодоступными сервисами Cloudflare.
- Пейлоад – бинарные данные объекта, размещённые в распределённом хранилище, оптимизированном под быстрый доступ из любой точки мира.
Разделение позволяет обслуживать запросы метаданных быстро и без блокировок, в то время как тяжёлый контент доставляется через кэш‑слой, расположенный непосредственно в PoP.
Метаданные и Durable Objects
Для обеспечения сильной согласованности метаданных R2 использует Durable Objects – сервер‑сайд объекты, каждый из которых отвечает за определённый диапазон ключей. При записи или чтении метаданных запрос направляется к соответствующему Durable Object, который хранит состояние в реплицированном KV‑хранилище Cloudflare. Благодаря тому, что объект существует в единственном экземпляре в кластере, достигается линейная консистентность: любые изменения видны сразу всем клиентам, независимо от их географического положения.
Чтение данных: кэш и глобальное распределение
Когда клиент запрашивает объект, процесс выглядит так:
- Поиск метаданных – запрос к Durable Object возвращает ссылки на блоки данных и их расположение.
- Проверка кэша PoP – если нужный блок уже присутствует в кэше ближайшего PoP, он отдается мгновенно.
- Запрос к бек‑енд‑хранилищу – если кэша нет, PoP инициирует загрузку блока из центрального репозитория. После получения блок кэшируется локально для последующих запросов.
Такой многоуровневый подход минимизирует задержки и полностью исключает необходимость пересылать данные через публичный интернет, что и объясняет отсутствие egress‑расходов.
Запись данных: локальная загрузка и репликация
Запись в R2 происходит в два этапа:
- Локальная загрузка – клиент (обычно браузер) отправляет файл на ближайший PoP через API, совместимый с S3. PoP принимает поток данных, разбивает его на небольшие чанки и временно сохраняет их в локальном хранилище.
- Фоновая репликация – после завершения загрузки Durable Object обновляет метаданные и инициирует асинхронную репликацию чанков в распределённый бек‑енд. Процесс репликации происходит в фоне, поэтому клиент получает быстрый отклик без ожидания полной синхронизации.
Эта схема гарантирует, что запись выглядит «мгновенной» для пользователя, а консистентность метаданных сохраняется за счёт атомарных операций в Durable Objects.
Практический пример: загрузка и отдача изображений в Next.js
// pages/api/upload.ts
import { R2Bucket } from '@cloudflare/workers-types';
export async function POST(request: Request) {
const form = await request.formData();
const file = form.get('file') as File;
const bucket = (globalThis as any).R2_BUCKET as R2Bucket; // привязка к бакету
// Прямая загрузка в R2 через S3‑совместимый endpoint
await bucket.put(`avatars/${file.name}`, file.stream(), {
httpMetadata: { contentType: file.type },
});
return new Response(JSON.stringify({ ok: true }), { status: 200 });
}
// components/Avatar.tsx
import Image from 'next/image';
export default function Avatar({ src }: { src: string }) {
// URL формируется без указания региона, Cloudflare подставит ближайший PoP
const url = `https://<account>.r2.cloudflarestorage.com/${src}`;
return <Image src={url} alt="User avatar" width={64} height={64} />;
}
В этом примере загрузка происходит напрямую в R2 через API‑совместимый с S3 эндпоинт, а чтение использует публичный URL, который автоматически резолвится к ближайшему PoP. Пользователь видит мгновенную загрузку аватара, а серверные расходы на исходящий трафик остаются нулевыми.
Советы и подводные камни для фронтенд‑разработчиков
- Размер чанков: при загрузке больших файлов рекомендуется разбивать их на части размером 5‑10 МБ. Это ускоряет репликацию и уменьшает вероятность таймаутов PoP.
- Кеш‑контроль: явно указывайте заголовки
Cache-Controlпри записи, чтобы управлять временем жизни контента в PoP. По умолчанию R2 кэширует почти бесконечно, что может стать проблемой при частом обновлении изображений. - Версионирование: используйте префиксы (например,
v1/,v2/) в именах объектов. Это упрощает «безболезненное» переключение между версиями без необходимости инвалидировать кэш. - Идентификация ошибок: ответы от R2 могут возвращать коды 429 (rate limit) при превышении запросов к Durable Objects. В клиентском коде реализуйте экспоненциальный бэкофф.
- Тестирование латентности: хотя большинство запросов обслуживается из PoP, первые обращения к новым объектам могут проявлять задержку из‑за «cold cache». Планируйте предварительное прогревание (warm‑up) для критически важных ресурсов.
Понимание внутренней архитектуры Cloudflare R2 позволяет принимать обоснованные решения о том, когда заменить традиционный S3‑бэкенд, как оптимизировать взаимодействие с объектным хранилищем и как избежать типичных ошибок, связанных с кэшированием и консистентностью. При правильном подходе R2 становится эффективным инструментом для современных фронтенд‑приложений, требующих глобального доступа к медиа‑контенту без дополнительных расходов на egress.