Ящик для диалогов (Транскрипт)
Ящик для диалогов автоматически записывает сообщения, отправленные и полученные роботом (организованные по ключу сессии platform:detail_type:target_id), и предоставляет исторические данные для восстановления диалога (event.history), сравнения изменений в теневом модуле, воспроизведения при холодном запуске и т.д. В настоящем документе описаны модель хранения, что защищает каждый регулятор стратегии сохранения, а также реальная стоимость разрешения API и снятия ограничений во время выполнения.
Прочтите раздел «Риски и аудит» перед изменением ограничений — у каждого регулятора есть свои причины существования.
Как это работает
Сообщение ──append()──▶ Буфер записи (в памяти deque, жёсткий лимит 4096 строк)
│ Батчевая запись на диск каждые 1 секунду (один батч ≤64 строк, одна транзакция)
▼
Таблица transcript в бэкенде storage (по умолчанию sqlite)
│ Приблизительно каждые 32 строки запускается ленивая очистка хранения
▼
┌─ FIFO-обрезка по сессиям (max_per_session, по умолчанию 50 строк)
└─ Глобальное удаление по TTL (ttl_hours, по умолчанию 168 = 7 дней)
Три уровня защиты, каждый защищает от разных рисков:
| Параметр | Значение по умолчанию | Что защищает | Последствия отключения (=0) |
|---|---|---|---|
| Лимит буфера записи | 4096 строк (константа, не настраивается) | Память: накопление сообщений при недоступности хранилища не приведёт к переполнению RAM | Невозможно отключить — это защита памяти |
| Обрезка текста | 2000 символов (константа, не настраивается) | Использование памяти и диска на одну строку | Невозможно отключить |
max_per_session |
50 строк/сессия | Рост диска: бесконечное накопление истории в одной сессии | История сессии будет бесконечно расти |
ttl_hours |
168 (7 дней) | Рост диска: долгосрочное накопление истории по всем сессиям | Таблица transcript будет бесконечно расти |
Важное понимание: буфер записи имеет жёсткий лимит, поэтому отключение политик хранения не приведёт к превышению памяти;
Реальные риски связаны с уровнем хранения и потребителями данных (см. следующий раздел).
Конфигурация
[ErisPulse.transcript]
enabled = true # Автоматическая запись включена
max_per_session = 50 # Количество записей в сессии (0 = отключить обрезку сессии)
ttl_hours = 168.0 # Общая продолжительность хранения (в часах, 0 = никогда не удалять)
Переменные среды также применяются: ERISPULSE_TRANSCRIPT_MAX_PER_SESSION, ERISPULSE_TRANSCRIPT_TTL_HOURS.
Изменения конфигурации применяются в режиме горячего перезагрузки (в пределах частоты записи в буфер, задержка до ~1 секунды).
API для переопределения во время выполнения
Для динамического изменения (например, временного увеличения срока хранения для определённой сессии):
from ErisPulse import transcript
# Переопределение (приоритет над конфигурацией; только в памяти, после перезапуска восстанавливается значение из конфигурации)
transcript.set_retention(max_per_session=500, ttl_hours=24 * 30)
# Получение текущих значений и источника
transcript.get_retention()
# {'max_per_session': 500, 'ttl_hours': 720.0, 'overridden': ['max_per_session', 'ttl_hours']}
# Восстановление значений из конфигурации
transcript.reset_retention()
- Передача
0отключает соответствующую стратегию (в журнале будет записано предупреждение); отрицательные числа вызывают исключениеValueError; - Переопределение вступает в силу при следующей очистке (ленивая, задержка максимум до добавления ~32 записей);
- Единственный экземпляр
transcriptможно импортировать из корневого пакета, а также черезsdk.transcript.
Риски и аудит: что нужно знать перед отключением ограничений
После отключения max_per_session / ttl_hours (или при значительном увеличении их значений) таблица transcript будет расти бесконечно вместе с количеством сообщений. Это приведёт к следующим последствиям:
- Заполнение диска — SQLite-файл будет постоянно увеличиваться, и при заполнении диска слой хранения фреймворка станет недоступен;
- Ухудшение производительности запросов — стоимость сканирования в
get()/recent()/get_by_trace()будет расти вместе с ростом таблицы; - Давление на память (истинный источник OOM) — потребители, загружающие историю целиком в память (восстановление диалога с историей, панель с полной выгрузкой, ваш бизнес-код
get(session, n)с большим n), будут потреблять всё больше памяти, что в экстремальных случаях приведёт к OOM kill.
Перед отключением задайте себе три вопроса: как долго вам нужна история? Какого размера? Кто будет её очищать?
Если ответ зависит от "никогда не удалять", правильным решением обычно является внешняя архивация (периодическая отправка старых данных в специальную базу), а не бесконечное хранение в почтовом ящике.
Средства аудита (регулярная проверка размера таблицы):
# С помощью ORM/SQL-конструктора проверьте количество строк и объём (пример для sqlite)
from ErisPulse import storage
row = storage.Table("transcript").raw_sql("SELECT COUNT(*) AS n FROM transcript")
-- Прямой запрос к файлу sqlite (объём таблицы)
SELECT COUNT(*) FROM transcript;
В логах появится предупреждение стратегия хранения была отключена — это означает, что кто-то отключил стратегию, установив значение 0. Убедитесь, что это ваше действие.
Границы с другими механизмами
- Таймеры интерактивных сеансов / wait_reply: см. Система интерактивных сеансов, не связано с ящиком входящих;
- Восстановление диалога с историей:
resume(with_history=N)берёт последние N элементов из ящика входящих — после обрезки ящика количество восстанавливаемой истории уменьшается (увеличениеmax_per_sessionпродлевает окно восстановления); - Дифф теневых модулей: зависит от цепочки запросов
get_by_trace, слишком короткий TTL ящика входящих сокращает окно для дифф-операций.