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 ? / * glob, or by full regular expression

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 min / max bound over numeric or RFC-3339 date fields

geo distance / bounding-box / polygon

documents whose geo-point falls inside a region

bool / conjunction / disjunction

combine sub-queries with must, should, must_not

query-string

the mini-language above: + / -, field:, ~ fuzziness, *, phrases

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.

-- 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 BY and 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 (summaries above) 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.