Align media delivery architecture docs with implementation

This commit is contained in:
evgeny committed 2026-10-24 17:26:12 +02:00
1 parent 9d9c82fb03
commit 4bab1f7ea4
2 files changed
+59 -32

No files matched your search

+52 -32
View File
@@ -1,47 +1,67 @@
Архитектура распространения медиа
1. src (создатель медиа) - подписывает, разделяет на блоки, создает UUID, отправляет в сообщении информацию о всех блоках для возможности скачивания и валидации (проверки сумм блоков и подписи файла).
2. req узел - кто хочет получить медиа.
Актуальный протокол репликации и передачи блоков описан в sync.md и media_delivery_proto.h.
Контракты API и владения — в media_index.h, media_download.h, media_delivery.h и file_transfer.h.
Подготовка и регистрация
основаня идея распространения контента
- каждый узел выбирает удобный (по пигну) на старте. выбирает следующий если таймаут (2 сек)
и устанавливает к нему прямое подключение. уведомляет суперузел что он обслуживает этот узел.
Суперузлы хранят в памяти список узлов (ll_queue) которые они обслуживают. Если соединение рвётся - узел автоматически выпадает из списка обслуживаемых.
media_index готовит неизменяемый файл в media_async worker: при необходимости копирует,
вычисляет SHA256 всего содержимого, создаёт UUID файла/блоков и Ed25519-подписи байтов блоков.
Размер обычного блока выбирается в пределах 10..25 MiB; последний блок может быть короче.
В uasync completion регистрирует результат в SQLite. Подписанное сообщение чата содержит
UUID, геометрию, SHA256 и подписи блоков, необходимые получателю для проверки.
- req-узел когда принял полный блок - уведомляет суперузел об этом.
Назначение заранее учитывается в media_preparations. Файловые операции и SQLite не образуют
общую транзакцию; неуспешная подготовка остаётся учтённой для удаления канала.
Зашифрованное групповое вложение проходит тот же pipeline как непрозрачный контейнер;
ключи и расшифрованные копии обслуживает chat_attachment, отдельно от каталога держателей.
- суперузлы между собой реплицируют таблицу с узлами для скачивания.
Также если подключен автор контекнта то суперузел автору контента отправляет обновления о доступности его блоков.
Поиск и загрузка
На всех узлах есть таблица с узлами для скачивания блоков.
ID primary/autincrement | UUID блока | id группы | id ноды | timestamp когда добавлено-обновлено
Суперузлы в нее сохраняют весь контент групп в которых они состоят. обычные узлы сохраняют только свой контент.
Если нет суперузлов или они недоступны - то роль суперузла выполняет автор контента.
media_download получает метаданные файла и ищет держателей через QUERY пачками до32 блоков.
Источники поиска — до9 суперузлов из таблицы своей CHAT-группы в порядке node_id, затем автор.
Это отдельный поиск кандидатов; полномочия репликации проверяются по подписанным adm_tags.
На блок сохраняется до4 держателей, на загрузку — до10 peer slots. Сортировки по RTT здесь нет.
Один QUERY имеет собственные UUID/дедлайн, по умолчанию2с; трафик блоков не продлевает его.
Каждый CM handle принадлежит своей операции; CM может получить прямой, обратный или непрямой путь.
BLOCK_REQ создаёт новый transfer_id. Ответы принимаются только от текущего держателя
с совпавшими group/media/block/chunk/UUID. Записывается непрерывный префикс, дубли не дают прогресса.
Простой по умолчанию2с переключает попытку; бюджет ошибок по умолчанию3 на блок.
OVERLOADED задаёт отдельное ожидание, RELAY_FULL предлагает читателей конкретного блока.
Полученные байты проверяются по ожидаемой Ed25519-подписи автора. Все блоки собираются
в соседний temp с обязательной проверкой полного SHA256, затем выполняются atomic rename
и регистрация скачанного файла в SQLite. Приём, проверка, сборка и выдача выполняются в uasync.
Этот путь не обещает durable fsync, используемый отдельно encrypted_file/file_io_temp.
Выдача и relay
Репликация между суперузлами.
Растущий temp представлен media_block_source: producer и каждая выдача удерживают свою ссылку.
Доступен только успешно записанный префикс; проверенный temp живёт до последнего читателя.
У каждого stream_ctx собственные offset/UUID, waiter, отложенный pump и таймер.
До5 relay-читателей на растущий блок; до3 выдач индексированных файлов одновременно,
также действует настраиваемый лимит на файл. DATA/DONE соблюдают router backpressure.
При отмене/удалении снимаются callbacks, waiter, pump и timer до освобождения владельца.
Каждый суперузел хранит указатель (id) по какой элемент синхронизировано с каждым другим суперузлом.
отправляет злементы и ждёт подтверждения (не более 4 в очереди). переповтор - 10 сек без ответа.
Суперузлы мониторят появление других суперузлов в bgp таблице. когда узел появился пробуют установить прямое подключение (без посредников и без обратного подключения - это сделает другой суперузел сам).
Если не получается 10 сек - отключаемся и следующая попытка через час.
то есть как только видят повяление - пробуют установить прямую связь каждый с каждым.
Прямое подключение - это независимый автономный процесс.
Для этого нужен коллбэк из bgp - появился новый узел и обновился узел. Его надо добавить если нет с возможностью цепочки подписчиков (как коллбэки статусов в etcp).
Каталог и репликация
при появлении суперузлата также начинаем репликацию таблицы блоков - отдельный асинхронный процесс.
для каждого суперузла свой контекст (таймаут, состояние протокола). отправляем обновление и ждём подтверждение с таймаутом (2 сек). таумаут постепенно увеличиваем до 10 минут если нет подтверждений и сбрасываем если появилось.
block_availability хранит объявления PROCESSING/COMPLETE по (group,block,node).
Merge выбирает максимум (status,timestamp); дубликат не создаёт новый локальный ID.
Собственные блоки переанонсируются из media_files с проверкой типов/границ полей SQLite.
HAVE_BLOCK_ACK отправляется ответом на объявление, но инициатор его не ждёт и игнорирует при приёме.
SERVE_REG/LEAVE обслуживают вспомогательный список; основной загрузчик их не посылает,
SERVE_ACK не отправляется. Учёт обслуживаемых узлов не определяет репликацию или загрузку.
Контекст репликации создаётся на пару (group,peer), если обе стороны — суперузлы этой группы
по подписанным adm_tags. Каждый контекст имеет свой CM handle. Роли перечитываются
при init/member_sync и каждые30с; потеря роли очищает только соответствующую группу.
Приём хранит source_epoch/last_recv_id. Страница до32 записей и новый RX-курсор фиксируются
одной транзакцией. SYNC использует little endian; остальные числовые wire-поля — native endian.
Один RPC повторяется по дедлайнам2..30с, watchdog работает каждые200мс; CHANGED — подсказка
о новых данных, потеря которой восстанавливается периодическим запросом каждые30с.
Обычный stop/start сохраняет каталог, эпоху и RX-курсоры; удаление канала удаляет его данные.
Алгоритм.
при получении сообщения (в зависимости от настроек кеширования) узлы получившие сообщение с медиа хотят получить вложение. Порядок действий:
- выбирают суперузел с минимальным rtt и отправляют ему запрос - хочу скачать медиа. дай список узлов с которых качаем.
В запросе - UUID запрашиваемых блоков (обязательно один файл можно несколько блоков для него), id src узла.
запрос направляется в суперузел изи автору контента - если нет суперузлов или они недоступны. таймаут 2 сек.
далее выбираем лучшие узлы (не более 10 штук по пингу) и пробуем к ним установить подключение (прямое или через промежуточный узел).
перебираем пытаясь подключиться, пока не наберем нужное количество (по количеству блоков, но не более 10 штук)
к кому подключились - запрашиваем блоки также с выбором следующего при таймауте или неудаче.
отправляем им запрос и пытаемся скачать.
Отдельный file_transfer передаёт неизменяемые файлы между явно заданными узлами для DM.
Он использует подписанные router-пакеты и свои CM handles, а авторизацию, ключи, проверку
содержимого, временный путь и окончательную публикацию предоставляет вызывающий модуль.
+7
View File
@@ -94,8 +94,13 @@ from/next, количество, порядок ID, статусы, дубли
HAVE_BLOCK и BLOCK_PROCESSING проходят тот же merge; несовпадение router-группы
с полем объявления отклоняется. ACK HAVE: 0 — изменение, 1 — no-op, 2 — ошибка БД.
Инициатор HAVE_BLOCK не ожидает этот ACK, не ставит таймер и игнорирует его при приёме.
Собственные зарегистрированные блоки добавляются непосредственно из media_files,
в том числе на единственном суперузле; повторный анонс использует стабильное время.
Переанонс проверяет типы SQLite, точные 16-байтные UUID, chunk в диапазоне uint32
и полный канонический ненулевой decimal chat_id в диапазоне uint64 до копирования/отправки.
Повреждённая строка пропускается с WARN; SQL-ошибка завершает обход с ERROR и счётчиками
неполной обработки. Уже отправленные корректные объявления сохраняются.
После commit изменений отправляется CHANGED остальным peers этой группы.
CHANGED во время RPC ставит dirty, и после commit выполняется ещё один запрос.
@@ -123,6 +128,8 @@ Watchdog каждые 200ms проверяет дедлайны, повторя
сходимость трёх узлов и backpressure/reset. `test_media_delivery_sql` проверяет
настоящую схему, restart/reset и сохранность media_files. Full/chat/integration
проверяют реальные транспорт, CHAT-членство и подписанные роли.
SQL-тест также проверяет 36 вариантов полей локального переанонса, смесь корректных/
повреждённых строк и пять SQL faults с последующим восстановлением и точными wire-полями.
## Попытки передачи и relay