- Уменьшено число последовательных синхронизаций до доставки: ordinary
Test, build and publish / All tests and Linux binary (push) Failing after 30s
Test, build and publish / Container smoke test and Gitea publish (push) Skipped

MAM
  append использует один sync вместо четырёх, SM checkpoint — один
  вместо двух.
  Checksummed redo journals сохраняют commit-before-ACK и
  восстанавливают
  native archive/SM JSON exports после аварии.
- Ограничены рост журналов, ротация и tombstone GC. Старый `.rappend` и
  legacy
  JSON импортируются. Retain/save/purge не возвращают удалённые записи.
- Recovery синхронизирует валидный journal перед публикацией состояния;
  проверка
  checksum не заменяет durability barrier. EIO при adoption блокирует
  startup
  или offline handoff. Новые fault fixtures проверяют сохранность
  журнала и retry.
- Replay SM проверяет checksum/JSON всех frames, но создаёт и валидирует
  полный
  Record только для последнего snapshot, сокращая работу с superseded
  queues.
- `message-processing-slow` содержит времена MAM отправителя/получателя,
  доставки, carbons, offline/push и handled, а также число
  ресурсов/копий.
  Payload и секреты не добавляются в логи.
- Добавлена offline команда `storage-checkpoint CONFIG`: durable экспорт
  и
  передача archive writer/подготовка отката на legacy Rust. Не
  запускайте другого
  writer и не удаляйте journals до успешного завершения команды.
- Новые crash, partial-write, corruption, EIO, recovery, handoff и
  latency
  регрессии включены в GitHub Actions через verify-local.sh; отчёты
  сохраняются.

Измерения, ограничения и эксплуатация:
[docs/DURABLE-MESSAGE-IO.md](docs/DURABLE-MESSAGE-IO.md),
[docs/VALIDATION-0.8.3.md](docs/VALIDATION-0.8.3.md).
This commit is contained in:
wt committed 2026-10-21 13:14:19 +07:00
1 parent e9329ccbb2
commit bff609097b
53 files changed
+8924 -1792

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
# Личные сообщения и 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 всего старого архива.
## Диагностика
`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 позволяют проверить это на целевой системе.