Full-Text & Vector Search, Analytics & Eventing
|
This section documents the current Couchbase Server 7.6.x line as published at the Couchbase Server documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. Some capabilities (Enterprise-Edition-only Analytics, auditing, encryption at rest, the Backup service and rack-zone awareness, and Capella-only App Services and Columnar) are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Couchbase iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
The Data, Query and Index services cover key-value access and SQL++. Three further services handle work those cannot: the Search service for linguistic and vector similarity matching, the Analytics service for ad-hoc analytical queries that must not disturb the operational load, and the Eventing service for running code on every data change.
The Search service (FTS)
Search indexes and index definitions
# Create a full-text index over one collection (REST API; the UI and SDKs wrap this JSON).
PUT /api/index/hotels-idx
{
"type": "fulltext-index",
"sourceName": "travel-sample",
"params": {
"mapping": {
"default_analyzer": "standard",
"types": {
"inventory.hotel": {
"properties": {
"name": { "fields": [{ "name": "name", "type": "text", "analyzer": "en" }] },
"reviews.content": { "fields": [{ "name": "review", "type": "text" }] }
}
}
}
}
}
}
# https://docs.couchbase.com/server/current/fts/fts-introduction.html
A Search index is an inverted index maintained by the Search service, separate from GSI. Its index definition selects the collections and fields to index, the analyzer per field, and whether to store values for result highlighting. A dynamic mapping indexes every field it encounters; a static mapping (above) indexes only the named fields and is far smaller and faster to build. See Full Text Search: Introduction.
Analyzers: tokenizers, token filters, languages
# An analyzer = optional character filters -> one tokenizer -> zero or more token filters. # # "The Grand-Hotel, PARIS!" # --unicode tokenizer--> [The, Grand, Hotel, PARIS] # --to_lower--> [the, grand, hotel, paris] # --stop (en)--> [grand, hotel, paris] # --stemmer (en)--> [grand, hotel, pari] # # Built-in language analyzers (en, fr, de, cjk, ...) bundle the right stop list and stemmer. # https://docs.couchbase.com/server/current/fts/fts-introduction.html
The analyzer used at index time must match the one used at query time, or the tokens will not line up and
nothing matches. Language analyzers add stemming and stop-word removal; the keyword analyzer emits the
whole field value as a single token, which is what you want for codes, enums and exact-match facets.
Query types
# SQL++ SEARCH() with a query-string query
SELECT name, SEARCH_SCORE() AS score
FROM `travel-sample`.inventory.hotel AS h
WHERE SEARCH(h, "name:grand +country:France -city:Paris")
ORDER BY score DESC
LIMIT 10;
# Structured form: a full-text query object instead of a string
SELECT name FROM `travel-sample`.inventory.hotel AS h
WHERE SEARCH(h, { "query": { "match_phrase": "grand hotel", "field": "name" } });
# https://docs.couchbase.com/server/current/fts/fts-query-types.html
| Query type | Matches |
|---|---|
match / match-phrase |
analyzed text; a phrase keeps term order and adjacency |
prefix / wildcard / regexp |
a term by prefix, by |
fuzzy |
terms within an edit distance of the input (typo tolerance) |
term |
a single token with no analysis applied (exact) |
numeric-range / date-range |
a |
geo distance / bounding-box / polygon |
documents whose geo-point falls inside a region |
bool / conjunction / disjunction |
combine sub-queries with |
query-string |
the mini-language above: |
See Types of Queries for the JSON shape of each.
Relevance scoring, index partitions and replicas
Hits are ranked by a TF-IDF / BM25-style relevance score — rarer query terms and denser matches score
higher — exposed to SQL++ as SEARCH_SCORE(). Each Search index is divided into partitions spread across
Search nodes so a query scans them in parallel, with optional replica partitions for availability and
query throughput:
# In the index definition
"planParams": { "indexPartitions": 6, "numReplicas": 1 }
# https://docs.couchbase.com/server/current/fts/fts-introduction.html
SEARCH() and SEARCH_SCORE() from SQL++
SEARCH() is a predicate the Query service pushes down to the Search service; the surrounding SQL++ then
joins, filters, groups and orders the hits like any other rows. SEARCH_SCORE() exposes the relevance
score and SEARCH_META() the stored fields and highlights of each hit. See
Search Functions.
Vector Search and hybrid search (7.6)
# 7.6: a dense-vector field in the FTS index, plus a knn clause in the query
SELECT name, SEARCH_SCORE() AS score
FROM `travel-sample`.inventory.hotel AS h
WHERE SEARCH(h, {
"query": { "match": "quiet romantic", "field": "description" },
"knn": [ { "field": "desc_vec", "vector": [0.021, -0.44 /* ... 1536 dims */], "k": 20 } ]
});
# https://docs.couchbase.com/server/current/vector-search/vector-search.html
Couchbase Server 7.6 adds Vector Search: an FTS index can carry a dense-vector field (cosine, dot-product
or L2 similarity) and answer k-nearest-neighbour queries over embeddings. Because the vector index lives
inside the same FTS index, one query can combine a knn clause with ordinary text and filter clauses — hybrid search — which is the retrieval step of a RAG pipeline: embed the user’s question, knn for the
closest documents, pass them to the model as context. For the equivalent on the document-database side see
MongoDB Text & Atlas Search.
The Analytics service
Analytics is an Enterprise-Edition service (and the engine behind Capella Columnar). It keeps its own column-oriented copy of chosen collections and answers long-running analytical SQL++ without adding load to the Data, Query or Index services.
Shadow datasets and links
-- Simple form: shadow one collection into an Analytics dataset.
ALTER COLLECTION `travel-sample`.inventory.hotel ENABLE ANALYTICS;
-- Explicit form, with a filter:
CREATE DATASET hotels ON `travel-sample`.inventory.hotel WHERE type = "hotel";
-- A secondary index inside the Analytics store:
CREATE ANALYTICS INDEX hotels_city ON hotels(city: string);
-- A link names a data source; "Local" is this cluster. A remote link federates queries:
CREATE LINK s3link TYPE `s3` WITH { "region": "eu-west-1", "accessKeyId": "..." };
-- https://docs.couchbase.com/server/current/analytics/introduction.html
A dataset (analytical collection) is a shadow of a source collection, kept in sync from the change feed
so it lags by seconds rather than being transactionally consistent. CREATE ANALYTICS INDEX builds a
secondary index within the Analytics store; CREATE LINK attaches a remote Couchbase cluster or object
store (S3, Azure Blob, GCS) so a single query can span operational data and archived data.
The Analytics SQL++ dialect vs. the Query service
Both speak SQL++, but Analytics is a separate implementation tuned for full scans and large joins: no
USE KEYS, no index hints, no transactions; joins are hash or merge joins chosen by its own optimizer
rather than nested-loop GSI lookups; a statement is expected to read most of a dataset. The Query service is
the opposite — point lookups and selective range scans through GSI. See
Analytics: Introduction.
The columnar engine and MPP execution
Datasets are stored column-major and compressed, so an aggregate over a few fields reads only those columns off disk. Execution is massively parallel (MPP): a coordinator splits the query into fragments run concurrently across all Analytics nodes and partitions, then merges the partial results.
SELECT h.country, COUNT(*) AS hotels, AVG(h.reviews[0].ratings.Overall) AS avg_rating FROM hotels AS h GROUP BY h.country ORDER BY hotels DESC; -- A GROUP BY over millions of documents that would be a heavy blocking sort on the -- Query service returns quickly here. https://docs.couchbase.com/server/current/analytics/introduction.html
When to reach for it
-
Ad-hoc reporting and dashboards over live operational data, with no ETL into a separate warehouse.
-
Large aggregations,
GROUP BYand multi-collection joins that would slow the Query service or need too many purpose-built GSIs. -
Federated queries across clusters or over object storage via
CREATE LINK.
Keep OLTP reads and writes on the Data and Query services and send analytical scans here. For the aggregate and windowing SQL these queries build on, see SQL Aggregate & Window Functions. Enterprise Analytics and Capella Columnar are documented in depth at the Analytics introduction.
The Eventing service
Eventing runs user-supplied JavaScript Functions against the Data service’s change feed. Each function is deployed against one source collection and reacts to mutations in it. See Eventing: Overview.
The OnUpdate / OnDelete handlers
// Function body. `doc` is the changed document; `meta` carries its id and metadata.
function OnUpdate(doc, meta) {
if (doc.type !== 'order' || doc.status !== 'PAID') return;
// Write a denormalised summary into another collection via a bucket binding.
summaries[meta.id] = { customer: doc.customerId, total: doc.total,
paidAt: new Date().toISOString() };
}
function OnDelete(meta, options) {
delete summaries[meta.id]; // cascade delete
}
// https://docs.couchbase.com/server/current/eventing/eventing-overview.html
OnUpdate fires on insert and update; OnDelete on delete and on expiry. Handlers should be short and
idempotent — they can be re-invoked after a rebalance or a restart.
Bindings: buckets, cURL, constants
-
Bucket binding — a keyspace mapped to a JavaScript variable (
summariesabove) in read-only or read-write mode. Access bypasses the SDK and is fast; a function must not write to its own source keyspace in a way that re-triggers itself. -
cURL binding — a pre-registered, allow-listed external endpoint callable with
curl()for notifications, webhooks, or enrichment from an API. -
Constant binding — an injected configuration value available as a global.
Timers
// Schedule work for a future time from inside a handler.
function OnUpdate(doc, meta) {
if (doc.type === 'reservation') {
createTimer(sendReminder, new Date(doc.checkinDate), meta.id, { id: meta.id });
}
}
function sendReminder(context) {
curl('POST', notifyApi, { body: { reservation: context.id } });
}
// https://docs.couchbase.com/server/current/eventing/eventing-overview.html
Timers are durable and survive restarts, so Eventing handles scheduled and deferred work, not only immediate reactions.
Feed boundary and deployment lifecycle
On deployment you choose a feed boundary: From now processes only mutations after deployment; From beginning first replays every existing document through the handler, then tails new changes. The lifecycle is deploy → (pause / resume) → undeploy; pausing keeps the processing checkpoint, so resume does not re-process what was already seen.
Typical uses, and DCP underneath
Common patterns are enrichment (fill a computed field), cascade delete, notifications, and maintaining a denormalised or aggregated view collection. All of these sit on the Database Change Protocol (DCP) — the ordered, per-vBucket stream of mutations with sequence numbers and rollback support that also feeds the Index service, Analytics shadowing, and XDCR. Eventing is the managed, in-cluster way to run code on that stream; an external consumer that needs the raw feed uses a DCP-based connector instead. For the same "act on every change" pattern in MongoDB, see MongoDB Change Streams.