Кэш архива на эдже¶
Нода, которая отдает не свой архив, а чужой — через remotes или legacy-источник, — по умолчанию ходит за каждым фрагментом к источнику. Кэш архива меняет это: прочитанный фрагмент оседает на локальном кэш-диске, и следующее обращение к этому же участку обслуживается локально, без сетевого запроса.
Так строится эдж CDN: origin хранит архив, эджи держат у себя то, что реально смотрят зрители.
Кэш прозрачен. Он меняет источник байтов, но не выдачу: URL фрагмента, плейлисты и границы архива у зрителя те же самые.
Что отличает кэш-диск от архивного¶
Кэш-диск — это роль диска, а не флаг на нем:
- живая запись его не выбирает — архив пишется только на диски из
disks; - ретенция потоков им не распоряжается —
max_depthиmax_bytesпотока к кэшу не применяются; - место освобождает вытеснение — по давности последнего обращения, а не по возрасту содержимого;
- в каталоге он изолирован — кэш-блобы лежат в своих таблицах, и архивные обходы (зачистка, backfill, разбивка занятого места на доли) их не видят.
Поэтому кэш эджа не выглядит на дашборде бесхозным архивом, а вытеснение кэша не конкурирует с записью архива за один мьютекс.
Настройка¶
Кэш настраивается в двух местах: диски — на ноде, включение — у потока.
dvr:
root: /storage
cache_disks:
- path: ssd1 # путь относительно root, как у архивного диска
max_bytes: 536870912000 # потолок кэша на этом диске, байт
min_free_bytes: 10737418240
- path: ssd2
streams:
- name: cam1
remotes:
- url: http://origin:5080
cache: {} # включает кэширование архивных чтений
Параметры кэш-диска:
| Параметр | Описание |
|---|---|
path |
путь диска относительно root |
max_bytes |
сколько кэшу позволено занять на этом диске; пусто — ограничен только свободным местом |
min_free_bytes |
сколько свободного места оставить на томе |
Лимиты независимы: вытеснение работает, пока диск не уложится в оба. mode у кэш-диска нет — Degraded относится к участнику RAID архива, а кэш ничего не реплицирует.
Секция cache у потока — top-level секция документа, соседняя с dvr, а не поле внутри него: кэш и запись независимы. Эдж кэширует чужой архив, своего не записывая. Полей у секции пока нет, само ее присутствие включает кэширование.
Валидация конфига отвергает:
- путь, указанный одновременно в
disksи вcache_disks; - дубликат внутри
cache_disks; - кэш-диск, чей путь совпадает с
root— вытеснение стерло бы дом каталога.
Разные пути на одном физическом томе — валидная конфигурация, но при старте пишется предупреждение: вытеснение освобождает место, которое тут же занимает архив.
Секция cache у потока на ноде без кэш-дисков — валидный no-op с предупреждением в логе. Это нормально для кластера, где один документ потока разъезжается по разным нодам.
Чистый эдж¶
Хранилище из root и одних кэш-дисков, без архивных, — валидная конфигурация: каталог есть, живой записи нет, архив подкачивается из remotes.
dvr:
root: /storage
cache_disks:
- path: ssd1
max_bytes: 536870912000
streams:
- name: cam1
remotes:
- url: http://origin:5080
cache: {}
Warning
root — дом каталога, а не диск записи. Раньше пустой disks означал «пиши прямо в root»; теперь это ошибка записи, видимая в статистике, а не молчаливая запись мимо RAID. Если нода должна писать архив — перечислите диски явно в disks.
Допуск в кэш¶
Класть в кэш все подряд с первого запроса — значит вытеснять популярное случайным: одиночная перемотка в глубину архива выбила бы с диска то, что смотрят все. Поэтому у допуска две оси, и настраиваются они на ноде, целиком на весь кэш:
| Параметр | Описание |
|---|---|
cache_fresh_age_secs |
фрагмент моложе этой глубины (секунд от текущего времени) кэшируется с первого же запроса; по умолчанию — сутки |
cache_admission_hits |
с какого по счету запроса кэшируется фрагмент старше границы свежести; по умолчанию — со второго, 1 выключает барьер |
dvr:
root: /storage
cache_fresh_age_secs: 86400
cache_admission_hits: 2
cache_disks:
- path: ssd1
Свежий хвост архива смотрят почти все, поэтому он попадает в кэш сразу. Глубина архива интересна единицам — она попадает в кэш, только если ее запросили повторно.
Порядок источников чтения¶
Архивный фрагмент ищется по цепочке, и первый ответивший источник закрывает запрос:
- сегментатор живого потока;
- кэш;
- локальный архив;
- кластер (remotes);
- legacy-источник
old_m4f; - legacy-архив на диске.
Попадание в кэш отвечает, не обращаясь к нижестоящим источникам. Если фрагмент есть и на архивном диске, и на кэш-диске, чтение обслуживает кэш: SSD-кэш быстрее шпинделей. У потока без секции cache шага кэша в цепочке нет вовсе.
Что попадает в кэш при промахе¶
Успешное чтение из источника ниже кэша записывается на кэш-диск вместе с init-фрагментом. Отличия от архивной записи:
- отсечка
max_depthне применяется — в кэш кладется то, что попросил зритель, даже если это глубже любой настроенной глубины архива; - многотрековый сегмент кладется целиком, всеми дорожками — поэтому переключение битрейта у зрителя обслуживается попаданиями, а не промахами;
- запись идемпотентна — одновременный промах нескольких зрителей по одному фрагменту оставляет в кэше ровно одну копию;
- ошибка записи в кэш не влияет на ответ клиенту — байты у него уже есть, ошибка только логируется и учитывается в метриках.
Догрузка вперед (read-ahead)¶
Просмотр архива последователен, поэтому после обслуженного чтения Sapsan фоном подкачивает следующий по времени фрагмент той же дорожки, если его в кэше нет. Догрузка работает на чистом эдже и при чтении с удаленного источника, ограничена одним фрагментом вперед и ответ клиенту не задерживает.
Это не прогрев: заполнения кэша по расписанию или по EPG здесь нет.
Вытеснение¶
Место на кэш-диске освобождается часовыми блобами в порядке давности последнего обращения — наименее недавно использованный уходит первым, пока диск не вернется в оба лимита.
- Возраст содержимого критерием не служит: вчерашняя популярная передача переживет сегодняшнюю непопулярную, а текущий час вытесняется наравне с остальными.
- Единственное изъятие — блоб, в который прямо сейчас дописывает писатель: он остается, вытесняется следующий по давности.
- Зачистка архива (
max_depthиmax_bytesпотока) и защита эпизодов кэш-диски не рассматривают вовсе.
Порядок вытеснения держит свой индекс каталога и переживает рестарт. Отметка обращения пишется в каталог лениво — не чаще раза примерно в 10 минут на блоб, чтобы чтение не превращалось в запись.
Учет кэша (занятый объем, времена обращения) восстанавливается при старте range-сканом каталога, без обхода файлов данных. Стертый кэш-диск — валидное холодное состояние: сервер стартует, кэш наполняется с нуля, архив не затронут.
Таймлайн и глубина на эдже¶
Глубина rewind и диапазоны архива кэшируемого потока считаются объединением локального каталога (архив и кэш) и всех удаленных источников — и анонсируются в rewind-плейлистах и в ответах о диапазонах архива. Зритель на эдже видит ту же глубину, что на origin.
Окна источников объединяются списком, с сохранением дыр между ними: сплошной интервал от начала до конца источника подменять список нельзя — иначе таймлайн красит зеленым участки, по которым данных нет, и клик по ним падает в 404. Дыры не больше запрошенного разрешения при этом сливаются по общему правилу.
Если origin недоступен, а запрошенный диапазон закэширован целиком, плейлист строится по локальному каталогу и фрагменты отдаются из кэша — просмотр продолжается.
Наблюдаемость¶
Каждое архивное чтение атрибутируется звеном цепочки, которое его обслужило:
- cache — отдал кэш-диск ноды (попадание);
- local — отдал архивный диск ноды;
- remote — пришлось идти к удаленному источнику (промах).
Архивным чтением считается только клиентское чтение фрагмента. Выдача живым сегментатором и догрузка read-ahead в атрибуцию не входят — иначе read-ahead завышал бы хитрейт тем сильнее, чем лучше он работает; догрузки считаются отдельным счетчиком.
В Prometheus и local-prom уходят кумулятивные счетчики:
| Метрика | Смысл |
|---|---|
dvr_archive_reads_total{source} |
архивные чтения по осям cache / local / remote |
dvr_archive_read_bytes_total{source} |
байты этих чтений |
dvr_read_ahead_fetches_total |
догрузки read-ahead |
dvr_disk_reads_total{disk_id}, dvr_disk_read_bytes_total{disk_id} |
обращения к диску; у кэш-диска это его попадания |
dvr_disk_cache_bytes{disk_id} |
объем кэша на диске |
Локальная ручка GET /streamer/api-v4/dvr отдает по каждому кэш-диску его роль, объем и лимит, а по ноде — скорости чтений по трем осям. Кумулятивных счетчиков в management API нет: там скорости и гейджи.
Хитрейт нигде не передается готовым числом — это cache / (cache + local + remote), и потребитель считает его сам: скорости складываются, деление идет после. Среднее из средних врет на неравном весе нод.
Экраны консоли, где это видно, описаны в документации Catena: ёмкость DVR в кластере и настройки ноды.
Чем это отличается от ленивой репликации remotes¶
У remotes есть похожее поведение: фрагмент, прочитанный с удаленного сервера, дописывается в локальный архив. Разница принципиальная:
| Ленивая репликация remotes | Кэш архива | |
|---|---|---|
| Куда пишется | на архивные диски | на кэш-диски |
| Зачем | перенести чужую историю к себе навсегда | обслужить повторные обращения локально |
| Когда освобождается | зачисткой по ретенции потока | вытеснением по давности обращения |
| Нужны ли архивные диски | да | нет, эдж может быть без них |
Репликация — про переезд архива, кэш — про экономию сети на популярном.