Typesense — поисковый движок с открытым кодом: десятки миллисекунд и терпимость к опечаткам. Его ниша — каталог товаров, документация и база знаний, где Elasticsearch тяжёл, а SQL LIKE не даёт релевантности. Ниже — запуск, схема, импорт, запросы с фасетами, ключи, снапшоты и диагностика.
Чем Typesense отличается
Движок написан на C++, распространяется одним бинарником и не требует JVM. Инвертированный индекс держится в памяти, а документы — на диске. Отсюда предсказуемая задержка: для каталога на сотни тысяч товаров хватает сервера с 4–8 ГБ памяти.
- Опечатки и префиксный поиск, инфиксный поиск включается отдельно.
- Фасеты, фильтры, сортировка и геопоиск в одном запросе.
- Векторный и гибридный поиск для семантических сценариев.
- Кластеризация через Raft и снапшоты для резервного копирования.
Typesense не является аналитической базой. Он не заменяет PostgreSQL для транзакций и не строит сложные агрегаты. Его задача — быстро найти и отранжировать заранее проиндексированные документы.
Запуск
Для проверки удобен Docker: ключ задаётся флагом, данные выносятся в том, порт 8108 открывают при необходимости. В продакшене закрепите тег, а не latest.
docker run -d --name typesense \
-p 8108:8108 \
-v typesense-data:/data \
typesense/typesense:latest \
--data-dir /data --api-key=change-me --enable-cors
Без Docker ставят бинарник и службу systemd. Ключ храните в файле с правами 600: Typesense читает переменные TYPESENSE_.
[Unit]
Description=Typesense
After=network-online.target
[Service]
User=typesense
EnvironmentFile=/etc/typesense/typesense.env
ExecStart=/usr/local/bin/typesense-server \
--data-dir=/var/lib/typesense --api-port=8108 \
--peering-port=8107 --enable-cors
Restart=on-failure
[Install]
WantedBy=multi-user.target
После старта проверьте здоровье. Ответ {"ok":true} означает, что процесс жив, но не гарантирует доступность коллекций.
curl -s http://localhost:8108/health
curl -s -H "X-TYPESENSE-API-KEY: $TYPESENSE_API_KEY" \
http://localhost:8108/collections
Схема коллекции
Коллекция — таблица с явной схемой. Поле facet участвует в фасетах, sort разрешает сортировку, optional допускает пропуск значения. Для векторов задают num_dim.
{
"name": "products",
"fields": [
{"name": "title", "type": "string"},
{"name": "brand", "type": "string", "facet": true},
{"name": "price", "type": "float", "sort": true},
{"name": "description", "type": "string", "optional": true},
{"name": "embedding", "type": "float[]", "num_dim": 384, "optional": true}
],
"default_sorting_field": "price"
}
Схему создают POST-запросом. Поле добавляют PATCH: старые документы получат пустое значение, поэтому новое поле делают optional. Переименование требует переиндексации.
curl -s -X POST http://localhost:8108/collections \
-H "X-TYPESENSE-API-KEY: $TYPESENSE_API_KEY" \
-H "Content-Type: application/json" \
-d @schema.json
Импорт документов
Документы загружаются в JSONL: объект на строку. Параметр action задаёт поведение при совпадении id: create, upsert, update или emplace. Для первой загрузки берут upsert, чтобы повторный запуск не падал.
curl -s -X POST \
"http://localhost:8108/collections/products/documents/import?action=upsert" \
-H "X-TYPESENSE-API-KEY: $TYPESENSE_API_KEY" \
--data-binary @products.jsonl
Ответ приходит по строке: {"success":true} или объект с ошибкой. Разбирайте его скриптом и останавливайте загрузку при высокой доле ошибок. Частые причины — несовпадение типа, отсутствие обязательного поля и слишком длинная строка. Большие файлы делите по 10–20 тысяч.
PATCH меняет описание коллекции, но не пересчитывает старые документы. Если включить новое поле в поиск, старые записи не станут по нему находиться, пока их не перезальют.
Поиск с опечатками
Запрос идёт в /documents/search. query_by перечисляет поля по важности, num_typos задаёт число опечаток, per_page ограничивает выдачу. Для коротких слов допуск снижается автоматически.
curl -s -G http://localhost:8108/collections/products/documents/search \
-H "X-TYPESENSE-API-KEY: $TYPESENSE_API_KEY" \
--data-urlencode "q=наушники" \
--data-urlencode "query_by=title,description" \
--data-urlencode "num_typos=2" \
--data-urlencode "per_page=20"
Префиксный поиск включён по умолчанию: «науш» находит «наушники». Для подстроки включают infix=always или infix=fallback, но это дороже по памяти. search_cutoff_ms ограничивает время: при перегрузке лучше вернуть частичный результат, чем держать соединение.
Релевантность настраивают через query_by_weights, prioritize_exact_match и drop_tokens_threshold. Не начинайте с десятка параметров: соберите реальные запросы.
Фильтры и фасеты
Фильтр отделяет точные значения от ранжирования. Синтаксис использует двоеточие, для массивов — скобки. Числа сравнивают операторами, строки перечисляют через запятую.
curl -s -G http://localhost:8108/collections/products/documents/search \
-H "X-TYPESENSE-API-KEY: $TYPESENSE_API_KEY" \
--data-urlencode "q=наушники" \
--data-urlencode "query_by=title" \
--data-urlencode "filter_by=brand:=[Sony,Bose] && price:<=30000" \
--data-urlencode "facet_by=brand" \
--data-urlencode "max_facet_values=10"
Фасеты возвращают counts для чекбоксов интерфейса. Если фасетов много, ограничьте max_facet_values и используйте facet_query. Для диапазонов стройте фильтры на фронтенде.
Ключи и доступ
Админский ключ даёт полный доступ и не должен попадать в браузер. Для поиска выпускают ключ с действием documents:search и списком коллекций. Значение показывается один раз — сохраните его в секретах.
curl -s -X POST http://localhost:8108/keys \
-H "X-TYPESENSE-API-KEY: $TYPESENSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"description":"search-only","actions":["documents:search"],"collections":["products"]}'
Ключ ограничивают по времени, но это не вся защита. Настройте HTTPS через прокси, ограничьте частоту запросов и не индексируйте лишние персональные данные.
Снапшоты и диагностика
Снапшот создаёт копию данных. Храните её вне сервера и проверяйте восстановление. Для кластера снапшот делают на каждом узле или копируют том.
curl -s -X POST \
"http://localhost:8108/operations/snapshot?snapshot_path=/var/backups/typesense-$(date +%F)" \
-H "X-TYPESENSE-API-KEY: $TYPESENSE_API_KEY"
curl -s -H "X-TYPESENSE-API-KEY: $TYPESENSE_API_KEY" \
http://localhost:8108/stats.json
stats.json показывает задержки и число запросов. Если p95 растёт, проверьте память, индекс и тяжёлые запросы. Журнал ищется через journalctl, место — через df. При нехватке RAM движок читает с диска.
Итог
Typesense закрывает задачу мгновенного поиска без тяжёлой инфраструктуры: один бинарник, схема, JSONL-импорт и запросы с опечатками. Начните с Docker или systemd, опишите поля и фасеты, выпустите search-only ключ, настройте снапшоты и следите за памятью. Для каталога и базы знаний этого достаточно; для аналитики — PostgreSQL.