Как отдавать пользователю 10–26 фотографий на каждую машину мгновенно и со своего сервера, не храня 885 ГБ и не платя за облако. Все цифры в записке сняты с боевой базы и живых CDN 31.08.2026.
Система прошла три состояния, и каждое упёрлось в своё ограничение:
SignatureDoesNotMatch), бакет недоступен — то, что было залито, потеряно.Важно: задача — не «придумать кэш» (он написан: WebP-энкодер, дедуп по content-hash, read-through,
рабочее множество lastview:*, GC, бэкфилл), а выбрать политику, при которой объём и цена сходятся.
База на 31.08.2026 — 481 476 машин и 8,6 млн фото. Кэшировать нужно только иностранные площадки (российские CDN браузер грузит сам): 272 702 машины, 5 433 320 фото. Размеры сняты живьём с CDN (выборка n=6 на площадку — уточнится на бэкфилле):
| Площадка | Машин | Фото | Фото/маш. | Оригинал | WebP-1600 | WebP-480 | Политика full-варианта |
|---|---|---|---|---|---|---|---|
| dongchedi | 104 715 | 2 073 974 | 19,8 | 365 КБ 2048px | 214 КБ | 23 КБ | перекодировать (−41%) |
| mobile_de | 144 857 | 2 822 038 | 19,5 | 56 КБ 1024px | 71 КБ ↑ | 19 КБ | хранить как есть |
| encar | 15 455 | 408 282 | 26,4 | 18 КБ 640px! | 17 КБ | 8 КБ | как есть + брать крупный вариант URL |
| che168 | 7 675 | 129 026 | 16,8 | 45 КБ ~1024px | 54 КБ ↑ | 18 КБ | хранить как есть |
↑ — перекодирование раздувает файл: исходники mobile_de/che168 уже мелкие и плотно сжатые.
Правило: full = min(оригинал, WebP-1600). Превью 480px выгодно всегда. Encar отдаёт в объявлениях
кадры 640×360 — крупный вариант надо запрашивать другим URL (правка адаптера, не хранилища).
Вторая половина картины — просмотры. Телеметрия карточек (car_views):
1 055 открытий, 683 уникальные машины, 15 пользователей за всё время; за последние 30 дней —
108 уникальных машин. Просмотры размазаны тонко (в среднем 1,5 открытия на машину),
топ-10% машин собирает 29% просмотров. Вывод: выгоду даёт не «кэш популярного» (популярного почти нет),
а ограничение хранения рабочим множеством — тем, что реально открывали за окно TTL.
Оба инструмента — из вашей курсовой «Подсистема ввода-вывода высоконагруженных систем» (гл. 1); здесь они применяются к сети вместо диска.
Это не теория: аудит прогрева замерил с прод-IP 72 фото/с (mobile_de), 41/с (encar), 17/с (che168), 13/с (dongchedi) — гео-блок этих площадок действует на страницы, но не на фото-CDN. Браузер россиянина до byteimg не дотягивается вовсе — наш сервер дотягивается со скоростью десятков кадров в секунду.
Проверка конфигурации прогрева законом Литтла — настроенные вручную потолки почти совпали с расчётом:
| Площадка | λ, фото/с (замер) | W, с/кадр | L = λ·W (расчёт) | WARM_CONCURRENCY (стоит) |
|---|---|---|---|---|
| dongchedi | 13 | ~2,0 | 26 | 30 |
| mobile_de | 72 | ~0,6 | 43 | 50 |
| encar | 41 | ~0,7 | 29 | 50 |
| che168 | 17 | ~1,5 | 26 | 30 |
Рабочее множество считается законом Литтла: уникально просмотренных машин за 30 дней × средневзвешенная галерея 2,6 МБ (взвешено по фактическому распределению просмотров между площадками):
| Трафик карточек | Уник. машин / 30 дн. | Галереи (ярус 2) | + Обложки (ярус 1) | Итого на диске |
|---|---|---|---|---|
| Сегодня (факт) | 108 | 0,3 ГБ | 15,5 ГБ | ≈16 ГБ |
| ×10 | 1 080 | 2,7 ГБ | 15,5 ГБ | ≈18 ГБ |
| ×50 | 5 400 | 13,6 ГБ | 15,5 ГБ | ≈29 ГБ |
| ×100 | 10 800 | 27,2 ГБ | 15,5 ГБ | ≈43 ГБ → +диск за сотни ₽ |
Четыре яруса, от вечного к внешнему:
| Ярус | Что лежит | Когда пишется | Сколько живёт | Объём |
|---|---|---|---|---|
| 1 · Обложки | первые 3 фото, WebP-480 | на парсинге (и кампаниях) | вечно, вне GC | 15,5 ГБ |
| 2 · Галереи | все кадры: full + превью | по открытию карточки; для «Свежих» — на выдаче | TTL 30 дн. от последнего показа (lastview, GC готов) | 0,3 → 27 ГБ |
| 3 · Источник | оригиналы у площадок | — | пока живо объявление | read-through |
| 0 · Плейсхолдер (позже) | thumbhash / микро-превью в карточке | на парсинге | вместе с карточкой | ~30 байт/фото |
Где лежат байты: локальный диск, не S3. Объём яруса 1+2 на годы вперёд меньше свободных 32 ГБ; раздача статикой из nginx уже обкатана; трафик у Timeweb безлимитный; и главное — исчезает режим отказа «кончился баланс отдельного сервиса → 403 на всё», который уже дважды ронял схему. S3 вернётся в игру, только если рабочее множество перерастёт разумный локальный диск (по таблице выше — это ×100 к трафику и дальше).
Ключи объектов — от URL для всех площадок (сегодня — только dongchedi, ADR 0009).
sha256(url) известен до скачивания: наличие проверяется без БД, эвикция безопасна
(промах после GC сам наполняется через read-through). Это заодно чинит скрытый баг: сейчас GC удаляет файлы
encar/che168/mobile_de, а карта images_local_json в БД остаётся — фронт строит ссылки на удалённые файлы.
Гейт карточки смягчается: показывать карточку по готовности обложек, полную галерею догревать фоном в порядке показа. По математике §03 к моменту клика галерея почти всегда готова, а первый кадр — всегда.
| Вариант | Объём | ₽/мес | Разово | Режим отказа |
|---|---|---|---|---|
| Статус-кво (хотлинк) | 0 | 0 | — | фото из РФ не грузятся — уже происходит |
| Зеркало в Yandex S3 (688 ГБ WebP) | 688 ГБ | ≈1 970 | ≈5 400 (PUT) | ноль на балансе → 403 на всё (случалось) |
| Ярусы в Yandex S3 | ≈20 ГБ | ≈280 | ≈300 | тот же биллинг-риск + латентность промахов |
| Ярусы на локальном диске | 16–43 ГБ | 0 | 0 | переполнение диска — лечится потолком GC и мониторингом |
Цены Yandex Object Storage после повышения мая-2026: хранение 2,376 ₽/ГБ/мес, исходящий трафик 1,68 ₽/ГБ после 100 ГБ, операции — отдельно. Прод-VPS у Timeweb: трафик в тарифе, докупка диска при росте — порядка сотен ₽/мес. Скачивание при бэкфилле обложек (~818 тыс. фото ≈ 60–80 ГБ входящего) бесплатно и займёт ~8–10 часов фоном.
cache_car_images: режим covers_only (первые 3 URL, только превью) — им пользуются парсинг, кампании и бэкфилл; полный прогрев — по открытию карточки (request_gallery_warm уже есть) и на выдаче «Свежих»./api/image-proxy (обобщить ADR 0009); карту images_local_json — из горячего пути в наследие.min(оригинал, WebP); у encar в адаптере запрашивать крупный вариант кадра.referenced безусловно; добавить потолок по месту (size-based) как страховку к TTL.IMAGE_STORE_BACKEND=local, IMAGE_CACHE_ENABLED=true; фронт PHOTO_CACHE_ON=true + пересборка. Точка отката — те же флаги назад.backfill_uncached_images (дросселируя, вне кампаний). Заодно уточнятся средние размеры из §02 (там выборка n=6).image_fetch в админку; алерт при 80% диска.fetch_for_site; крайний случай — дешёвый VPS-релей рядом с источником (сотни ₽/мес), не нужен сегодня.