From 4bab1f7ea4f366a34cc7b0974c26176407cc1f0e Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 17:19:43 +0200 Subject: [PATCH] Align media delivery architecture docs with implementation --- src/media_delivery/_desc.txt | 84 ++++++++++++++++++++++-------------- src/media_delivery/sync.md | 7 +++ 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/media_delivery/_desc.txt b/src/media_delivery/_desc.txt index 61cc74e2..62d75cf1 100644 --- a/src/media_delivery/_desc.txt +++ b/src/media_delivery/_desc.txt @@ -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, а авторизацию, ключи, проверку +содержимого, временный путь и окончательную публикацию предоставляет вызывающий модуль. diff --git a/src/media_delivery/sync.md b/src/media_delivery/sync.md index 1e8865de..d497f8c5 100644 --- a/src/media_delivery/sync.md +++ b/src/media_delivery/sync.md @@ -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