Семантический кэш на pgvector: как я срезал счёт за LLM на 38%
Понедельник, открываю биллинг — за выходные секретарь обработал 4200 диалогов, цифры кусаются. Лезу в логи и вижу: 60 % запросов почти одинаковые. «Статус по заявке 15234», «Когда отгрузка по 15234», «А по 15234 что?». Каждый раз честно зовём LLM, ждём 2–3 секунды и платим за одно и то же.
Обычный кэш по строке тут бесполезен — люди пишут по-разному, с опечатками и синонимами. А по смыслу это один вопрос. Решил попробовать pgvector, собрал за два вечера и реально сэкономил.
Зачем мне это понадобилось
Многие говорят: «Просто хэшируй промпт в Redis». Работает, когда у тебя три жёстких сценария. В реальной жизни клиенты формулируют как хотят. Семантический кэш превращает запрос в вектор, ищет ближайший и, если близость выше порога, отдаёт старый ответ. Получается 80 мс вместо 2+ секунд.
Плюс меньше нагрузка на квоты — когда GigaChat начинает тормозить прямо на демо, это спасает.
Как я всё запилил
PostgreSQL 16 + pgvector 0.7, эмбеддинги e5-large-ru. Одна таблица и два индекса:
CREATE TABLE llm_cache (
id BIGSERIAL PRIMARY KEY,
query_embedding vector(768) NOT NULL,
query_text TEXT NOT NULL,
response_text TEXT NOT NULL,
...
);
CREATE INDEX ON llm_cache USING hnsw (query_embedding vector_cosine_ops);
Поиск простой: эмбеддим запрос, фильтруем по tenant и хэшу шаблона, берём ближайший вектор. Если косинус ≥ 0.93 — возвращаем ответ и обновляем статистику.
Какой порог выбрать
0.93 получилось оптимально на моих данных. Ниже — путает разные заявки, выше — кэш почти не работает. Для FAQ можно опустить до 0.91, а для запросов с номерами и датами лучше поднять или отключить совсем.
Что я успел накосячить
- Один кэш на всех клиентов — быстро получил ответы из чужой CRM. Пришлось добавить tenant_id везде.
- Смена промпта — старые ответы продолжали вылетать. Завёл prompt_template_hash.
- Вечный кэш — данные в CRM меняются. Поставил TTL 7 дней + инвалидацию по упоминанию сущности.
- Холодный HNSW после рестарта — лечится pg_prewarm и фоновым прогревом.
Что вышло по цифрам
Hit rate 38 %, средняя задержка упала с 2400 до 1540 мс, токенов стало на 38 % меньше. Таблица кэша 280 МБ за месяц — копейки по сравнению с экономией.
Когда лучше не заморачиваться
Генеративные задачи, уникальный контекст, длинные цепочки агентов — там кэш только навредит. Я оставляю обычный путь, если в промпте есть пользовательский текст или данные меняются каждую минуту.
Итог
pgvector отлично работает не только для RAG. Если у тебя больше 20 % повторяющихся по смыслу запросов — кэш окупится за неделю. У меня окупился.
