title: 'Full-Text Search' sidebar_label: 'Full-text Search' description: 'Learn how Spice can perform full text search' sidebar_position: 2 tags:
Spice provides full-text search functionality with BM25 scoring. This search method is optimized for keyword-based queries and is useful when:
Datasets can be augmented with a full-text search index that enables efficient search. Dataset columns are included in the full-text index based on the column configuration.
Spice supports two full-text search engines:
| Engine | Description |
|---|---|
| Tantivy (default) | Built-in, in-process BM25 engine. No external dependencies. |
| Elasticsearch | Indexes into an external Elasticsearch cluster, fronted by a local Tantivy warm tier that serves searches. Useful when Elasticsearch is already part of the infrastructure or when its operational characteristics (sharding, replication, snapshots) are preferred. |
When no engine is specified, Tantivy is used automatically.
The built-in Tantivy engine indexes text with Tantivy's en_stem tokenizer: terms are lowercased and reduced to their English (Snowball) stem, with token positions retained so phrase queries keep working. Query terms are analyzed the same way, so a search for running also matches documents containing run and runs.
Stemming is always on for the built-in engine and has no configuration parameter. It is English-only — text in other languages is still tokenized and lowercased, but not stemmed.
The local warm index used with engine: elasticsearch is a Tantivy index and analyzes text the same way. Searches served directly by Elasticsearch (opens in a new tab) — multi-column datasets, a warm index that could not be built, or an on_zero_results: use_source fallback — are analyzed by Elasticsearch's own analyzer for that index instead.
To enable full-text search, configure your dataset columns within your dataset definition as follows:
In this example, full-text search indexing is enabled on both the title and body columns using the default Tantivy engine. The row_id specifies a unique identifier for referencing search results and retrieving additional data.
By default the built-in Tantivy index is held in memory and rebuilt on every start. Set index_store: file on a column to persist it to disk instead, so a restart reopens the existing index rather than re-indexing the dataset:
| Field | Default | Description |
|---|---|---|
index_store | memory | Where the index lives: memory (rebuilt on every start) or file (persisted on disk). If any indexed column of a dataset sets file, the dataset's index is persisted. |
index_directory | .spice/data/fts/<catalog>/<schema>/<table>/ | Directory for a persisted index. Only applies with index_store: file — combining it with index_store: memory logs a warning and keeps the in-memory store. |
A persisted index records the schema it was built with, and that schema is what serves queries. On start, Spice compares the persisted schema against the current configuration:
index_store: memory is never affected — it rebuilds from scratch on every start and so always matches the configuration.
Beyond the indexed text, the built-in index also carries the dataset's primary key (or row_id) and every column declared with metadata.vectors (opens in a new tab). Those columns are returned by searches and can be filtered on, and a WHERE predicate over them is applied inside the index scan — before the search's row limit, so a filtered search returns the best matches among the rows that pass the filter rather than whatever survives filtering the unfiltered top matches:
A column whose type the index cannot represent — a date or timestamp — is skipped when the index is built, with a warning naming the column; predicates on it are applied above the scan as before. See Filter Pushdown (opens in a new tab) for the full set of predicates the index applies.
To use Elasticsearch instead of the built-in Tantivy engine, add a dataset-level full_text_search block with engine: elasticsearch and the connection parameters:
The dataset-level full_text_search block selects the engine and provides connection parameters. Column-level full_text_search.enabled controls which columns are indexed.
With engine: elasticsearch, Spice maintains a local Tantivy warm index in front of the Elasticsearch index:
acceleration.on_zero_results: use_source (opens in a new tab); with the default return_empty, searches are served from the warm tier alone.Two cases fall back to querying Elasticsearch directly, each logged at startup:
LargeUtf8 → Utf8) is a common cause, because the two tiers must agree on the search column and primary-key fields.:::note[Enterprise edition] The Elasticsearch full-text search engine is available in the Spice Enterprise edition (opens in a new tab). :::
| Parameter | Description | Example |
|---|---|---|
elasticsearch_endpoint | Required. Elasticsearch cluster URL. | http://localhost:9200 |
elasticsearch_user | Optional. Username for HTTP basic authentication. | ${secrets:ES_USER} |
elasticsearch_pass | Optional. Password for HTTP basic authentication. | ${secrets:ES_PASS} |
elasticsearch_index | Optional. ES index name for FTS documents. Defaults to the dataset name. | articles-fts |
client_timeout | Optional. Total HTTP request timeout. Default: 30s. | 30s |
connect_timeout | Optional. HTTP connect timeout. Default: 10s. | 10s |
Optional parameters to control Elasticsearch index creation and write behavior:
| Parameter | Description | Default |
|---|---|---|
number_of_shards | ES number_of_shards index setting (applied at index creation). | ES default |
number_of_replicas | ES number_of_replicas index setting (applied at index creation). | ES default |
refresh_interval | ES refresh_interval index setting (applied at index creation). | ES default |
bulk_load_refresh_interval | Temporary refresh_interval during bulk writes. Set to -1 to disable refresh during loading. | Not set |
force_merge_after_write | Run _forcemerge after full/append writes. | false |
force_merge_segments | Max segments for _forcemerge. Setting this also enables force merge. | 1 (when force merge enabled) |
batch_write_rows | Max rows per _bulk request. | 1000 |
index_settings | JSON object passed as ES index settings at creation. | Not set |
When multiple datasets or columns share the same Elasticsearch connection, use YAML anchors to avoid repeating config:
Elasticsearch can serve as both the vector engine and the FTS engine for the same dataset. Configure vectors and full_text_search independently:
Use rrf() (opens in a new tab) to combine vector and full-text results with hybrid search.
After enabling indexing, you can perform searches using the HTTP API endpoint /v1/search. Results will be ranked based on the relevance to your keyword query across indexed columns (title and body in this example).
For details on using this endpoint, see the API reference for /v1/search (opens in a new tab).
Spice also provides full-text search through SQL using a user-defined table function (UDTF), text_search().
Here's how you can query using SQL:
This returns the top 5 results from the doc.pulls dataset that best match your search keywords within the body column.
The text_search() function has the following signature:
By default, text_search retrieves up to 1000 results. To adjust this, specify the limit parameter in the function call.
Use this function to integrate full-text search directly into your data workflows.