Couchbase Scopes and Collections: Logical Isolation Without Bucket Sprawl
Couchbase scopes and collections offer logical isolation inside a bucket, but memory, flush, and replicas stay bucket-wide. Learn how N1QL keyspaces, per-collection indexes, and RBAC paths work, and when to choose collections over separate buckets.
24 Jun 2026, 05:29 UTC

The bucket sprawl problem
If you have been running Couchbase for a while, you have probably felt the pull toward a new bucket for every new service or domain. It feels clean: separate memory quotas, separate flush buttons, separate backup definitions. Then you end up with forty buckets, each carrying its own operational overhead, and your cluster manager starts asking why replication and capacity planning suddenly look so complicated.
Couchbase Server 7.0 made scopes and collections generally available, and they offer a different answer. The short version: collections give you logical isolation and a cleaner lifecycle inside a bucket, but they do not give you operational isolation. Memory, replicas, flush, and node placement remain bucket-wide. That distinction is the whole decision.
What the three-part keyspace actually changes
Before 7.0, a N1QL query addressed a bucket as a two-part path: bucket-name. With scopes and collections, the canonical form is a three-part keyspace: bucket.scope.collection.
If you omit the scope and collection, Couchbase maps the old two-part path to the _default scope and _default collection for backward compatibility. That means existing queries keep working, but it also means you can accidentally read from _default when you meant to target a new collection.
A collection is a logical boundary, not a new bucket. Documents inside it share the bucket's memory quota, replica count, and storage engine configuration. That makes collections cheap to create and cheap to reason about for domain modeling, but the blast radius of a memory spike or a heavy compaction is still the entire bucket.
Indexes and query planning move with the collection
Indexes are defined per collection. A global secondary index is created on a specific bucket.scope.collection keyspace, and the query planner selects it based on the path in your FROM clause. If your index lives on sales.orders_2024 but your query reads from sales.orders, the planner will not use it.
This sounds obvious, but it changes index governance. You can no longer think of "the index on the bucket." You have to keep index definitions and query keyspaces in sync, and you should verify that sync with EXPLAIN rather than assuming it.
Worked example: create, index, and verify
Assume a bucket named app-data and a server version of 7.x. Use the Web Console to create a scope named catalog and a collection named products inside that bucket. Then run the following in the Query Workbench (or via cbq) with an account that holds the Query role on that keyspace:
CREATE INDEX `idx_products_category`
ON `app-data`.`catalog`.`products`(`category`);
If you need to undo this state change, drop the index:
DROP INDEX `idx_products_category`
ON `app-data`.`catalog`.`products`;
Next, run your query and inspect the plan:
EXPLAIN SELECT name, price
FROM `app-data`.`catalog`.`products`
WHERE category = "electronics"
LIMIT 10;
Check that the plan references idx_products_category and resolves the three-part keyspace you expect. If the plan shows a primary index scan or a different keyspace, your index and query paths have drifted apart.
RBAC and cross-cutting features are path-sensitive
Access control follows the same path logic. Roles and privileges can be scoped to a scope or collection, so a least-privilege service account should be granted on the exact keyspace it uses—for example, app-data.catalog.products—rather than receiving broad bucket-level access.
Features that cross bucket boundaries are more version-sensitive. XDCR (Couchbase's cross-data-cluster replication) filtering, backup and restore selection, Analytics dataset mapping, and SDK keyspace APIs around scopes and collections changed across 7.x releases. A replication filter that worked on a two-part path in 7.0 may need adjustment for a three-part path in a later 7.x release. Validate the exact server and SDK versions you run before assuming a feature behaves the way the documentation for a different minor version describes.
Collections vs. separate buckets: the decision rule
Here is the practical trade-off:
| Choose collections when… | Choose separate buckets when… |
|---|---|
| You want logical separation of domains inside one application. | You need distinct memory quotas per workload. |
| You want easier lifecycle management and shared capacity. | You need independent flush or different service settings. |
| You want path-sensitive RBAC without multiplying cluster objects. | You need stronger operational isolation or separate node placement. |
Collections win when the pain point is conceptual clutter. Buckets win when the pain point is resource contention or operational independence.
Limitations worth planning around
Do not assume per-collection limits are fixed. Couchbase documents scopes-per-bucket, collections-per-scope, and indexes-per-collection limits per release, and those numbers have changed across versions. Check the release notes for your exact server build.
Older SDKs, connectors, and query tools may only understand the two-part keyspace. Test the whole data path—ingest, query, backup, replication—before assuming every client can address a scope and collection.
TTL (time-to-live) and flush semantics are primarily bucket- and document-oriented. Do not assume a per-collection maximum TTL or a per-collection flush exists in your version without checking.
Migrating off the _default collection is a coordinated change. Queries, indexes, RBAC, replication filters, and backup definitions must move together. Plan a cutover, not a rename.
How to check your result
Start by confirming your server version from the console or node list, then compare scopes and collections behavior against the release notes for that exact version. Create a test scope and collection, add a secondary index, and run EXPLAIN to confirm the planner uses it. If you use XDCR or backup, replicate or back up that test scope and confirm filtering and selection behave as intended on both source and target.
Collections are not a smaller bucket. They are a namespace with cheaper lifecycle and tighter access control. Use them for logical clarity, and reach for a new bucket only when you truly need operational isolation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.