144 lines
12 KiB
Markdown
144 lines
12 KiB
Markdown
# Личные сообщения и durable IO в 0.8.3
|
||
|
||
В присланном журнале сервера 0.8.2 есть три `message-processing-slow` с 4792–5549 мс,
|
||
route=local. Этот таймер начинается после приёма XML и заканчивается после
|
||
`mark_handled`: ожидание происходит внутри обработчика. Старые события не
|
||
содержат разбивки по этапам и сами по себе не доказывают причину медленного IO.
|
||
Исходный пользовательский журнал не включён в архив проекта.
|
||
|
||
## Изменение записи
|
||
|
||
До 0.8.3 append в MAM требовал 4 последовательных sync: rollback marker file+dir,
|
||
native list, удаление marker+dir. Обычное сохранение SM требовало 2: JSON file+dir.
|
||
В TLS fixture1:1 с двумя аккаунтами, четырьмя SM-ресурсами и carbons это обычно
|
||
16 sync на сообщение. Увеличение задержки каждого настоящего fsync/fdatasync
|
||
внутри изолированного data_path воспроизводит задержку в несколько секунд.
|
||
Interposer не пропускает sync и не меняет системные настройки/production.
|
||
|
||
В 0.8.3 принятые данные сначала фиксируются в журнале восстановления с checksum:
|
||
|
||
- MAM `.rredo`: native `.list` сохраняет формат Prosody; обычный append требует
|
||
одного sync журнала. Replay восстанавливает недостающий native suffix и
|
||
отбрасывает незавершённый хвост. Полностью записанная повреждённая запись
|
||
вызывает ошибку. Ротация —128 frames/1 MiB плюс максимум одна 16 MiB запись.
|
||
- SM `.wal`: authoritative полные snapshots и tombstones; readable JSON —
|
||
восстанавливаемый экспорт. Обычное сохранение требует одного sync_data.
|
||
Ротация —16 MiB плюс максимум один snapshot; aggregate WAL cap 512 MiB,
|
||
logical slots по-прежнему 256 MiB/8 MiB на slot/4096 stanzas и 4096 sessions.
|
||
Tombstone GC сначала синхронизирует удаление JSON, затем удаляет WAL.
|
||
|
||
Recovery синхронизирует полностью проверенный журнал перед публикацией
|
||
восстановленного состояния: полный frame после SIGKILL может остаться
|
||
только в page cache. Checksum подтверждает целостность, но не запись на диск. Ошибка этого sync запрещает startup/handoff.
|
||
|
||
Создание журналов, восстановление и ротация дополнительно синхронизируют файлы
|
||
и каталог. Экономия относится к обычной записи, не каждому возможному запросу.
|
||
Отключения durability или периодической записи вместо commit-before-ACK нет.
|
||
При неопределённой ошибке SM IO store блокирует дальнейшие изменения до
|
||
восстановления. После успешного WAL commit ошибка экспорта не откатывает память.
|
||
Queues/archives не образуют общей транзакции с сетью: гарантия replay остаётся
|
||
at least once, а не exactly once между всеми хранилищами.
|
||
|
||
## Передача данных Prosody и откат версии
|
||
|
||
После SIGKILL/OS crash сначала используйте 0.8.3 для восстановления redo. Не
|
||
удаляйте `.wal`/`.rredo` вручную и не редактируйте JSON export как источник истины.
|
||
Для смены writer или отката на старую версию остановите сервер и выполните:
|
||
|
||
```bash
|
||
rust-xmpp storage-checkpoint /etc/prosody/prosody.cfg.lua
|
||
```
|
||
|
||
Команда берёт data-directory lock и SM store lock, восстанавливает журналы,
|
||
синхронизирует native archives и актуальные JSON, затем durably удаляет redo.
|
||
При ошибке передача владения не завершена; разберите ошибку, не запускайте
|
||
другой writer. Перед работой с production данными сохраните обычную резервную
|
||
копию всего data_path. Native Prosody не понимает resumable SM snapshots Rust;
|
||
checkpoint SM обеспечивает совместимый JSON для старого Rust, не переносит
|
||
TLS/TCP/SM-сессию в Prosody. Простой graceful stop не заменяет эту явную команду.
|
||
Нельзя одновременно изменять `.list` средствами Prosody и Rust. Неjournaled
|
||
in-place append при активном redo считается незавершённым и может быть отброшен.
|
||
При восстановлении согласованной копии всего data_path журнал остаётся
|
||
авторитетным независимо от смены inode файла. Любая внешняя модификация,
|
||
включая atomic replacement, требует handoff. Внутренний rewrite сначала
|
||
фиксирует старый native snapshot и удаление redo, затем атомарно заменяет native
|
||
файл: авария в промежутке сохраняет старый snapshot, после замены — старый/новый.
|
||
Редактирование baseline in place без handoff не поддержано. Anchor проверяет
|
||
последние 4 KiB baseline, не является checksum всего старого архива.
|
||
|
||
## Внешний перезапись baseline: «archive redo baseline changed in place»
|
||
|
||
Если какой-либо writer (бакап-восстановление, копия data_path, Prosody, старая
|
||
версия сервера) перезаписал native `.list` поверх зафиксированного baseline без
|
||
handoff, `.rredo` перестаёт совпадать со своим anchor. Каждый вызов
|
||
`archive_for_user` возвращал `Err`, и через `?` ошибка роняла session
|
||
(`connection-closed reason=protocol-or-transport-error`) — клиент (напр. Monal)
|
||
реконнектился по кругу, сообщение не доставлялось.
|
||
|
||
Начиная с 0.8.4 MAM-архивация на трёх местах личной доставки (отправитель,
|
||
получатель, входящая федерация) стала best-effort: при storage-ошибке исходная
|
||
stanza всё равно маршрутизируется, обе сессии остаются живы, в лог пишется
|
||
санитизированный `mam-archive-skipped`.
|
||
|
||
Критическое уточнение (0.8.4-followup): живой append больше НЕ fail-closed.
|
||
`recover_redo` при первом же untrusted-baseline сбое читает только 96-байтовый
|
||
header (ранняя anchor-проверка до чтения/валидации кадров) и сам зареТирует
|
||
неподключённый журнал (`retire_redo`), принимая синхронизированный native как
|
||
Prosody. Это устраняет O(журнал) чтение+checksum на каждый access при больших
|
||
сломанных журналах (секунды `message-processing-slow`) и возвращает append на
|
||
скорость Prosody. Полная коррупция header-checksum или неполный baseline по-прежнему
|
||
блокируют startup (инвариант 0.8.3): они не матчатся `is_untrusted_redo` и остаются
|
||
fail-closed, чтобы не потерять committed redo frames без цели.
|
||
|
||
Ручной `storage-checkpoint` для этого больше не требуется; offline handoff
|
||
остаётся идемпотентным способом штатного durable handoff перед сменой writer:
|
||
|
||
```bash
|
||
rust-xmpp storage-checkpoint /etc/prosody/prosody.cfg.lua
|
||
```
|
||
|
||
`prepare_rewrite` по-прежнему распознаёт непроверенный baseline (changed in place,
|
||
shorter, missing, journal exceeds bound), считает текущий durably-синхронизированный
|
||
native авторитетным, синхронизирует его и удаляет неприменимый журнал. После
|
||
MAM history читается, а новые append создадут свежий redo поверх нового baseline.
|
||
Сверку совместимости native-формата см. в исходниках Prosody
|
||
(`util/datamanager.lua` `list_append`/`list_store`, `plugins/mod_storage_internal.lua`
|
||
`archive:append`): Prosody пишет `item(...)` и при полной перезаписи применяет
|
||
atomic rename — что и меняет первые bytes baseline, оставляя Rust-журнал устаревшим.
|
||
|
||
## Диагностика
|
||
|
||
`message-processing-slow` по-прежнему имеет threshold 250 мс, duration_ms, route
|
||
и connection. Новые поля в integer milliseconds:
|
||
|
||
| Поле | Измеряемый этап |
|
||
| ---------------- | -------------------------------------------------------------------- |
|
||
| pre_routing_ms | Работа до личного MAM; для MUC/ранних выходов — обработка до handled |
|
||
| sender_mam_ms | Политика и архивирование отправителя |
|
||
| recipient_mam_ms | Проверка аккаунта, политика и архивирование получателя |
|
||
| delivery_ms | Маршрутизация, targets, mutex и transmit |
|
||
| carbons_ms | Выбор ресурсов и отправка carbon |
|
||
| offline_push_ms | Offline commit и подготовка/enqueue push |
|
||
| handled_ms | Session mutex и durable mark_handled |
|
||
|
||
Неисполненные этапы равны 0. target_count — выбранные локальные ресурсы,
|
||
carbon_count — успешные carbon transmit. Это серверные этапы, не время
|
||
отображения клиентом. Тексты сообщений, SASL и токены не логируются.
|
||
|
||
## Проверки и границы
|
||
|
||
`tests/slow_storage_latency_network.py` — реальный STARTTLS, MAM/carbons/PEP,
|
||
контролируемый sync delay и счётчик операций. `tests/slow_storage_crash_network.py`
|
||
— sender SM ACK, немедленный SIGKILL, проверка committed journals/обоих архивов
|
||
до повторной аутентификации и replay всех ресурсов. Rust regressions проверяют
|
||
partial tails, corruption, ротацию, tombstones, migration и offline handoff.
|
||
Отдельный `journal_recovery_sync_network.py` вводит scoped EIO для WAL/redo
|
||
и проверяет отказ startup/handoff без потери journal и успешный повтор.
|
||
Точные измерения и результаты этой сборки — [VALIDATION.md](VALIDATION.md).
|
||
|
||
Общий mutex SM store, обязательное durable IO, большие pending snapshots,
|
||
полные MAM queries/maintenance, PEP storage и federation всё ещё имеют стоимость.
|
||
Медленный диск не становится быстрым от сокращения числа sync. Проверка на
|
||
loopback не устанавливает причину каждого production warning; новые phase
|
||
fields позволяют проверить это на целевой системе.
|