中文翻译暂不可用,显示俄语原文。

Что такое Session Management в Harness и какие стратегии (TTL, LRU, GC)?

Краткий тезис

Session Management в Harness — это шестой слой (из семи) в архитектуре RAG|agentic RAG, отвечающий за поддержание контекста диалога и состояния сессии. Он управляет временем жизни сессий, кэшированием и очисткой. Основные стратегии: TTL (Time To Live) — автоматическое удаление после периода неактивности, LRU (Least Recently Used) — вытеснение старых сессий при переполнении кэша и GC (Garbage Collection) — фоновая сборка мусора для завершённых/истекших сессий. Дополнительно применяется locking для предотвращения race conditions при конкурентном доступе. Понимание этих механизмов критично для построения масштабируемых многопользовательских агентов.


1. Термин: Session Management (управление сессиями)

Session Management — это процесс поддержания контекста разговора с агентом между отдельными запросами пользователя. В отличие от stateless RAG, где каждый запрос обрабатывается изолированно, в RAG|agentic RAG агент должен помнить историю диалога, промежуточные действия и результаты выполнения инструментов. Harness определяет отдельный слой (слой 6 из 7, harness-one/session), который централизованно управляет:

  • Созданием и удалением сессий (привязка к user_id или conversation_id)
  • Хранением состояния (история сообщений, контекст, переменные)
  • Временем жизни сессии (когда и как сессия завершается)
  • Параллельным доступом (блокировки записи)

Для этого используются три ключевые стратегии: TTL, LRU и GC, а также механизм блокировок.


2. TTL (Time To Live) — время жизни по таймеру

TTL — самая простая стратегия: каждой сессии назначается максимальный срок хранения. Если сессия неактивна (нет запросов) в течение заданного интервала, она помечается как истекшая и удаляется.

  • Как работает: при создании сессии ставится поле expires_at = now + TTL. При каждом запросе к сессии поле обновляется (если применяется скользящее окно) или остаётся фиксированным (абсолютное истечение).
  • Типичные значения TTL: 5–30 минут для интерактивных чатов, до 24 часов для долгоживущих задач.
  • Преимущества: простота реализации, предсказуемое поведение, низкие накладные расходы.
  • Недостатки: даже редко используемая сессия может быть преждевременно удалена, если в последнюю минуту пользователь вернулся; жёсткое ограничение не учитывает загрузку памяти.

Пример конфигурации:

session:
  strategy: TTL
  ttl_seconds: 900  # 15 минут
  sliding_window: true   # продлевать при каждом взаимодействии

3. LRU (Least Recently Used) — вытеснение старых сессий

LRU применяется, когда ресурсы ограничены (например, количество одновременно хранимых сессий фиксировано). Сессия, к которой обращались реже всего (или давно), удаляется при попытке создать новую сессию сверх лимита.

  • Как работает: поддерживается очередь порядка использования (или хэш-таблица с двусвязным списком). При каждом доступе сессия перемещается в конец (самые свежие). Когда достигается лимит, удаляется элемент из начала.
  • Типичный лимит: настраивается под доступную память (например 10 000 сессий на ноду).
  • Преимущества: автоматически адаптируется к паттернам пользователей — неиспользуемые сессии вытесняются, активные остаются.
  • Недостатки: возможна ситуация, когда сессия с очень старым, но всё ещё нужным контекстом удаляется ради набора новых сессий (т.н. «вытеснение полезной сессии»).

Реализация в Python (упрощённая):

from collections import OrderedDict

class LRUSessionStore:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.store = OrderedDict()

    def get(self, session_id: str):
        if session_id in self.store:
            self.store.move_to_end(session_id)
            return self.store[session_id]
        return None

    def put(self, session_id: str, session):
        if session_id in self.store:
            self.store.move_to_end(session_id)
        elif len(self.store) >= self.capacity:
            # удаляем самую старую (первую)
            oldest = next(iter(self.store))
            self.store.popitem(last=False)
        self.store[session_id] = session

4. GC (Garbage Collection) — фоновая сборка мусора

GC — это фоновая процедура, которая периодически сканирует все сессии и удаляет те, которые уже не нужны. В Harness GC обычно комбинируется с TTL и LRU, но может работать и независимо.

Критерии для удаления:

  • Истёк TTL (сессия завершена по таймеру)

  • Сессия явно завершена пользователем (EndSession intent)

  • Сессия в состоянии failed или aborted (агент не смог завершить работу)

  • Сессия превысила максимальное количество шагов (лимит на число итераций)

  • Периодичность: каждые 60–300 секунд в зависимости от нагрузки.

  • Инструменты: фоновый поток или асинхронная задача (в Harness используется asyncio с await asyncio.sleep(interval)).

  • Преимущества: не блокирует основной поток ответов; гарантирует освобождение памяти даже при сбоях TTL/LRU (например, если TTL не продлился из-за ошибки).

  • Недостатки: задержка между фактическим истечением и удалением (окно для потенциальных проблем с памятью).

Псевдокод GC:

async def garbage_collector(store, interval=120):
    while True:
        await asyncio.sleep(interval)
        now = time.time()
        for sid in list(store.keys()):
            session = store.get(sid, lock=False)
            if session.is_expired(now) or session.is_finished():
                store.delete(sid)

5. Locking — блокировки для предотвращения race conditions

В многопользовательской распределённой системе возможны ситуации, когда один и тот же запрос обрабатывается дважды (дублирование), или два запроса приходят одновременно к одной сессии. Locking (блокировка) гарантирует, что запись в сессию (обновление истории, состояния) происходит атомарно.

В Harness используется два уровня блокировок:

  • Оптимистичная блокировка (optimistic locking): перед записью проверяется версия сессии (например, updated_at или version_id). Если версия изменилась с момента чтения — операция повторяется.
  • Пессимистичная блокировка (pessimistic locking): на время записи захватывается распределённый lock (через Redis, ZooKeeper или базу данных). Используется для критических операций.

Типичная стратегия: пессимистичные блокировки на уровне процесса (in-process mutex) для одной рабочей ноды, оптимистичные для межнодовой консистентности.

Пример с asyncio:

import asyncio

class SessionStore:
    def __init__(self):
        self._locks = {}  # session_id -> asyncio.Lock

    async def get_or_create_lock(self, session_id):
        if session_id not in self._locks:
            self._locks[session_id] = asyncio.Lock()
        return self._locks[session_id]

    async def update_session(self, session_id, data):
        lock = await self.get_or_create_lock(session_id)
        async with lock:
            session = await self.load(session_id)
            session.update(data)
            await self.save(session)

6. Сравнение стратегий

ХарактеристикаTTLLRUGC
Основание удаленияВремя с последнего доступаЧастота/давность использованияЯвные признаки завершения
Управление памятьюКосвенное (если сессия не удалена, память занята)Прямое (фиксированный лимит)Фоновое (дополняет TTL/LRU)
Простота реализацииОчень низкаяСредняяСредняя
Гарантия утилизацииНет (может висеть мёртвая сессия до TTL)Да (старые вытесняются)Да (но с задержкой)
ПрименимостьЧат-боты с короткими диалогамиВысоконагруженные системы с ограниченной памятьюКомплексные сценарии с долгими сессиями

В Harness по умолчанию используется комбинация: TTL (скользящее окно) + LRU (лимит на количество активных сессий) + GC (фоновая очистка каждые 5 минут). Параметры выставляются в конфигурации слоя session.


7. Архитектура Session Management в Harness

Слой session в Harness представлен модулем harness_one.session. Внутри:

  • SessionStore — абстракция над хранилищем (in-memory dict, Redis, SQLite). По умолчанию in-memory для низкой задержки.
  • SessionWorker — фоновый воркер, запускающий GC и проверяющий TTL.
  • SessionMiddleware — ASGI/WSGI middleware, который для каждого входящего запроса извлекает session_id (из cookie, заголовка или тела), загружает сессию, передаёт в обработчик и сохраняет обновлённую.

Конфигурация указывается в YAML-файле агента:

layers:
  - name: session
    config:
      store:
        type: redis
        url: redis://localhost:6379/0
      ttl: 600
      capacity: 10000
      gc_interval: 120
      locking: pessimistic

8. Практические соображения

  • Выбор TTL: слишком короткий — пользователь теряет контекст; слишком длинный — растёт память. Рекомендуется ставить TTL = медианная продолжительность диалога * 2.
  • LRU и долгие задачи: если агент выполняет задачу 30 минут, а лимит LRU настроен на 5 минут неактивности, сессия может быть вытеснена даже при наличии активного background-воркера. Решение — отмечать сессию как «занятую» (busy) и исключать из LRU.
  • GC и консистентность: при распределённых хранилищах (Redis) GC должен быть идемпотентным и обрабатывать удаление с помощью скриптов Lua.
  • Мониторинг: метрики — количество активных сессий, процент истекших по TTL, частота вытеснений LRU.

Пет-проект для закрепления

Задача: реализовать упрощённый Session Manager для агента чат-бота с поддержкой TTL+LRU+GC и блокировками.

Инструменты: Python (asyncio), Redis (опционально), тестовый фреймворк pytest.

Шаги:

  1. Создайте класс Session с полями: id, messages, created_at, last_accessed, status (active/finished).
  2. Реализуйте SessionManager с методами get(sid), create(sid), delete(sid), get_or_create(sid).
  3. В get_or_create добавьте LRU-очередь (коллекция OrderedDict) с capacity.
  4. Добавьте TTL: при каждом get обновляйте last_accessed, и в фоновой задаче (asyncio.create_task) раз в 30 секунд проверяйте все сессии на истечение.
  5. Реализуйте GC: принудительно удаляйте сессии со статусом finished.
  6. Добавьте asyncio.Lock для каждой сессии — оборачивайте запись в блокировку.
  7. Напишите тесты: проверьте, что после TTL сессия удаляется, что LRU вытесняет старые, что одновременные запросы не приводят к race condition.

Ожидаемый результат: консольное приложение, эмулирующее 10 параллельных пользователей; метрики (сколько сессий создано, сколько удалено по TTL/LRU, максимальный размер кэша).


Связь с другими вопросами

ВопросТема
745Что такое agentic RAG и чем отличается от обычного RAG
746Компоненты архитектуры Harness
747Слой Tool Execution
748Слой Reasoning (планирование)
750Слой Memory (краткосрочная и долгосрочная память)
751Мониторинг и observability в Agentic RAG

Навигация