SHA256
Сохранить текущий снимок DM-синхронизации
This commit is contained in:
@@ -26,6 +26,10 @@
|
||||
|
||||
- `docs/Personal_Messages/Технические_вставки_DM_v1.md`
|
||||
|
||||
Изменяемое состояние доставки, retry-расписание и репликация между двумя access-серверами подробно описаны отдельно:
|
||||
|
||||
- `docs/Personal_Messages/Доставка_и_синхронизация_DM.md`
|
||||
|
||||
Устаревшая предыдущая версия сохранена отдельно:
|
||||
|
||||
- `docs/Personal_Messages/Спецификация_DM_v0.5_устаревшая.md`
|
||||
@@ -300,12 +304,17 @@
|
||||
|
||||
`sync_servers` не являются списком пользовательских серверов доставки DM.
|
||||
|
||||
### 7.3. Несколько серверов у отправителя и получателя
|
||||
### 7.3. Два сервера у отправителя и получателя
|
||||
|
||||
Протокол должен поддерживать ситуацию, когда:
|
||||
Актуальный runtime исходит из ограничения:
|
||||
|
||||
- у отправителя несколько `access_servers`;
|
||||
- у получателя несколько `access_servers`;
|
||||
- у одного пользователя не более двух `access_servers`;
|
||||
- следовательно, у локального сервера есть не более одного peer для репликации данных пользователя.
|
||||
|
||||
Протокол поддерживает ситуацию, когда:
|
||||
|
||||
- у отправителя один или два `access_servers`;
|
||||
- у получателя один или два `access_servers`;
|
||||
- часть серверов у сторон совпадает;
|
||||
- часть серверов уникальна.
|
||||
|
||||
@@ -332,7 +341,14 @@
|
||||
- `ReceiveIncomingMessage` — приём одной входящей копии, входящих редактирований и входящего read-receipt;
|
||||
- `ReceiveOutcomingMessage` — алиас `SendMessagePair`.
|
||||
|
||||
### 8.2. Новые методы, которые нужны
|
||||
### 8.2. Межсерверная синхронизация
|
||||
|
||||
- `ReceiveOutcomingMessage` — прежняя полная пара второго access-сервера отправителя;
|
||||
- `ReceiveIncomingMessage` — прежняя одиночная входящая копия;
|
||||
- `DmSyncBatch` — pull событий с `synced=false` и ACK сохранённой предыдущей страницы;
|
||||
- `GetDmDeliveryStatus` — единственная новая read-only проверка доставки по существующему `messageKey`.
|
||||
|
||||
Delivery-state между серверами не передаётся и не объединяется.
|
||||
|
||||
### 8.3. Серверный слой диалогов
|
||||
|
||||
@@ -446,8 +462,12 @@ Request:
|
||||
Правила:
|
||||
|
||||
- клиенту достаточно отправить пару на один любой доступный сервер;
|
||||
- успешный `status=200` означает локальное сохранение;
|
||||
- первая сетевая попытка выполняется до ответа клиенту, параллельно для двух серверов получателя;
|
||||
- сервер после принятия сам отвечает за дальнейшую межсерверную доставку.
|
||||
|
||||
Ответ сохраняет прежние `baseKey`, `incomingKey`, `outgoingKey` и счётчики доставки в клиентские сессии. Единственное новое поле — `deliveryState`: `accepted`, `delivered` или `failed`.
|
||||
|
||||
### 10.2. `ReceiveIncomingMessage`
|
||||
|
||||
Назначение:
|
||||
@@ -469,7 +489,7 @@ Request:
|
||||
}
|
||||
```
|
||||
|
||||
`sourceServerLogin` необязателен и используется как best-effort подсказка, чтобы сервер при дальнейшей пересылке не отправлял то же событие обратно серверу-источнику.
|
||||
`sourceServerLogin` пока доверяется без отдельной межсерверной подписи. Сервер всё равно проверяет пользовательскую подпись самого `SHiNE_DM`. Успешный ответ сохраняет старые поля `messageKey`, `baseKey` и счётчики realtime-доставки.
|
||||
|
||||
### 10.3. `DeleteMessage`
|
||||
|
||||
@@ -558,6 +578,8 @@ UI-следствие для клиента:
|
||||
- если точное время для старого сообщения неизвестно, но по более новым данным видно, что сообщение уже точно прочитано, UI может показывать его как прочитанное без точного времени;
|
||||
- read-receipt при этом остаётся отдельным DM-событием синхронизации, но в обычную историю страницы не подмешивается.
|
||||
|
||||
Для исходящих элементов сервер также возвращает единственное поле `deliveryState`.
|
||||
|
||||
## 11. Межсерверная доставка
|
||||
|
||||
### 11.1. Клиентская сторона
|
||||
@@ -568,47 +590,23 @@ UI-следствие для клиента:
|
||||
|
||||
### 11.2. Серверная сторона
|
||||
|
||||
После принятия валидного события сервер должен отправлять его:
|
||||
После локального принятия пара получает изменяемое состояние `accepted`. Сервер сразу вызывает до двух текущих маршрутов получателя параллельно. Успех хотя бы одного маршрута переводит сообщение в терминальное `delivered`; ждать второй маршрут не требуется.
|
||||
|
||||
- на все серверы из `access_servers` отправителя;
|
||||
- на все серверы из `access_servers` получателя.
|
||||
После первой попытки полная пара передаётся единственному второму access-серверу отправителя через прежний `ReceiveOutcomingMessage`. Peer самостоятельно пробует актуальные маршруты получателя; delivery-state в запрос не входит.
|
||||
|
||||
Если часть серверов совпадает, это допустимо.
|
||||
|
||||
Если один и тот же сервер присутствует у обеих сторон, он не должен слать сообщение сам себе повторно, но обязан локально сохранить событие и доставить его в нужные пользовательские сессии.
|
||||
|
||||
Идемпотентность обязательна.
|
||||
Идемпотентность обязательна. ACK означает запись в БД; повтор уже известной ревизии считается успешным ACK.
|
||||
|
||||
### 11.3. Догоняющая синхронизация истории
|
||||
|
||||
Для восстановления пропущенных DM-событий между access-серверами используется отдельная операция:
|
||||
Для восстановления пропущенных DM-событий между access-серверами используется pull-операция:
|
||||
|
||||
- `DmSyncBatch`
|
||||
|
||||
Сервер-получатель синхронизации запрашивает у другого access-сервера историю одного пользователя по курсору:
|
||||
Второй сервер запрашивает outbox-события владельца с `synced=false`, сохраняет их и передаёт подтверждённые `syncId` в `ackSyncIds` следующего запроса. Источник ставит `synced=true` только после ACK. Каждый цикл начинается с начала списка; курсор нужен только для страниц текущего цикла.
|
||||
|
||||
- `ownerLogin`;
|
||||
- `afterStoredAtMs`;
|
||||
- `afterMessageKey`;
|
||||
- `limit`, максимум `500`;
|
||||
- `maxBytes`, ограничение суммарного размера raw-блоков пачки.
|
||||
Полная пара отправителя передаётся двумя blob. Входящая копия получателя и tombstone передаются одним blob.
|
||||
|
||||
Удалённый сервер отдаёт все DM-события, относящиеся к этому пользователю:
|
||||
|
||||
- контентные копии и read-receipt по `target_login`;
|
||||
- tombstone типов `5/6/7/8`, где пользователь участвует как `fromLogin` или `toLogin`.
|
||||
|
||||
Порядок пачки:
|
||||
|
||||
- `created_at_ms ASC`;
|
||||
- `message_key ASC`.
|
||||
|
||||
Курсор хранится локально для пары:
|
||||
|
||||
- пользователь;
|
||||
- удалённый access-сервер.
|
||||
|
||||
При первом добавлении сервера или отсутствии курсора синхронизация стартует с `0` и постепенно подтягивает всю доступную историю пачками.
|
||||
Полная пара отправителя передаётся одним элементом с двумя blob в порядке incoming/outgoing. Входящая копия получателя и tombstone передаются одним blob.
|
||||
|
||||
При применении событий, полученных через `DmSyncBatch`, сервер:
|
||||
|
||||
@@ -618,17 +616,17 @@ UI-следствие для клиента:
|
||||
- не отправляет realtime/push-уведомления клиентам;
|
||||
- не запускает повторный fan-out, чтобы не создавать циклы.
|
||||
|
||||
Плановый sync запускается фоном после старта WebSocket-сервера и повторяется раз в 6 часов.
|
||||
Синхронизация настроек и DM выполняется одним периодическим процессом и через один последовательный WS-сеанс с peer. Выборка DM использует частичный индекс по `synced=false`.
|
||||
|
||||
В текущей реализации межсерверная авторизация для `DmSyncBatch` ещё не включена. Сервер отдаёт пачку только если сам локально является access-сервером `ownerLogin` по актуальной таблице `user_access_servers_current`.
|
||||
В текущей реализации межсерверная авторизация DM ещё не включена. Принимающий сервер проверяет, что сам является access-сервером `ownerLogin`, и всегда проверяет пользовательские подписи signed-блоков.
|
||||
|
||||
### 11.4. Ошибки доставки
|
||||
|
||||
Если часть серверов временно недоступна:
|
||||
Если ни один сервер получателя не подтвердил запись, попытки выполняются сразу, через 30 секунд, 5 минут, 25 минут и 1 час от первоначального принятия.
|
||||
|
||||
- это не должно отменять локальное принятие уже валидного сообщения;
|
||||
- повторная доставка может делаться отдельным retry-механизмом;
|
||||
- повторное получение того же события должно быть безопасным.
|
||||
Перед попытками через 5 минут, 25 минут и час сервер read-only спрашивает peer отправителя через `GetDmDeliveryStatus(messageKey)`. Если peer уже доставил хотя бы на один сервер, сообщение считается доставленным.
|
||||
|
||||
После неудачной последней попытки устанавливается `failed`; дальнейших автоматических попыток и кнопки ручного повтора нет.
|
||||
|
||||
## 12. Хранение в БД
|
||||
|
||||
@@ -653,11 +651,13 @@ UI-следствие для клиента:
|
||||
|
||||
Сообщение об удалении переписки тоже хранится в БД, а старые сообщения до его времени из БД удаляются.
|
||||
|
||||
Для догоняющей межсерверной синхронизации дополнительно используются:
|
||||
Изменяемая сетевая часть хранится отдельно:
|
||||
|
||||
- индекс по `target_login`, `created_at_ms`, `message_key`;
|
||||
- отдельные индексы по delete-событиям для `from_login` и `to_login`;
|
||||
- таблица `dm_sync_peer_state` с курсором чтения для пары `ownerLogin + remoteServerLogin`.
|
||||
- `dm_delivery_state` — состояние и расписание доставки исходящей пары;
|
||||
- `dm_sync_outbox` — событие владельца и единственный флаг ACK `synced`;
|
||||
- частичные индексы содержат только due/unsynced строки.
|
||||
|
||||
Legacy-таблица `dm_sync_peer_state` после миграции v12 физически остаётся для безопасной установки ZIP-накладки, но новым DM-кодом не используется.
|
||||
|
||||
## 13. Что обязательно должно измениться в коде относительно v0.5
|
||||
|
||||
@@ -670,7 +670,7 @@ UI-следствие для клиента:
|
||||
- межсерверная маршрутизация DM должна идти через `access_servers`;
|
||||
- сервер должен добирать отсутствующих пользователей из Solana PDA до проверки подписи DM;
|
||||
- при выборе актуальной версии должен учитываться `reencryptedAtMs`, если `revisionTimeMs` совпадает;
|
||||
- логика должна быть безопасна для нескольких серверов у каждой стороны.
|
||||
- логика должна быть безопасна для одного или двух серверов у каждой стороны.
|
||||
|
||||
## 14. Что в v1 пока не входит
|
||||
|
||||
@@ -678,4 +678,4 @@ UI-следствие для клиента:
|
||||
- хранение отдельного `keyId` шифрования в DM;
|
||||
- ротация `clientKey`;
|
||||
- финальная конкретная UI-реализация массовой перешифровки;
|
||||
- межсерверная авторизация `DmSyncBatch`.
|
||||
- межсерверная авторизация DM-синхронизации и доставки.
|
||||
|
||||
Reference in New Issue
Block a user