Bucket Tutorial
A hands-on walkthrough of the Bucket API, from creating your first bucket to removing it.
Before You Start
Section titled “Before You Start”You need a running Kronotop cluster with at least one initialized shard. Connect with any RESP-compatible client on port 5484 (the default client port).
All examples in this tutorial use JSON input and output for readability. In production, BSON is recommended because it avoids conversion overhead and supports richer data types.
Session Setup
Section titled “Session Setup”Before working with documents, configure the session to use JSON:
SESSION.ATTRIBUTE SET input_type jsonSESSION.ATTRIBUTE SET reply_type jsonSESSION.ATTRIBUTE SET object_id_format hex| Attribute | Effect |
|---|---|
input_type | Controls whether documents you send are parsed as json or bson. |
reply_type | Controls whether documents returned by queries are encoded as json or bson. |
object_id_format | Controls whether ObjectIds are returned as hex strings or raw bytes. |
See Session Attributes for the full list of session settings.
Creating a Bucket
Section titled “Creating a Bucket”Create a bucket named users:
> BUCKET.CREATE usersOKVerify it exists:
> BUCKET.LIST1) "users"For idempotent scripts, use IF-NOT-EXISTS to avoid errors when the bucket already exists:
> BUCKET.CREATE users IF-NOT-EXISTSOKEvery bucket is created with a primary index on the _id field. This index is always in READY status
and cannot be dropped.
See BUCKET.CREATE for shard assignment and index creation at bucket creation time.
Data Modelling and Inserting Documents
Section titled “Data Modelling and Inserting Documents”Single Insert
Section titled “Single Insert”> BUCKET.INSERT users DOCS '{"name": "Alice", "age": 30, "status": "active"}'1) "6a23da8a87f4f93001bd8df6"Kronotop auto-generates an _id (ObjectId) for each document and returns it.
Batch Insert
Section titled “Batch Insert”Pass multiple documents after the DOCS keyword:
> BUCKET.INSERT users DOCS '{"name": "Bob", "age": 25, "status": "active"}' '{"name": "Carol", "age": 35, "status": "inactive"}'1) "6a23da9887f4f93001bd8df7"2) "6a23da9887f4f93001bd8df8"User-Provided _id
Section titled “User-Provided _id”Supply an ObjectId using extended JSON notation:
> BUCKET.INSERT users DOCS '{"_id": {"$oid": "507f1f77bcf86cd799439011"}, "name": "Dave", "age": 28}'1) "507f1f77bcf86cd799439011"If the _id already exists, the command returns a DUPLICATEKEY error. The _id field is immutable.
Schemaless Documents
Section titled “Schemaless Documents”Documents in the same bucket can have different fields:
> BUCKET.INSERT users DOCS '{"name": "Eve", "email": "eve@example.com"}'1) "6a23dab187f4f93001bd8df9"Nested Documents
Section titled “Nested Documents”> BUCKET.INSERT users DOCS '{ "name": "Frank", "age": 40, "address": {"city": "Istanbul", "zip": "34000"}}'1) "6a23dabd87f4f93001bd8dfa"Arrays
Section titled “Arrays”> BUCKET.INSERT users DOCS '{"name": "Grace", "age": 29, "tags": ["admin", "verified"]}'1) "6a23dac687f4f93001bd8dfb"Supported Data Types
Section titled “Supported Data Types”| Type | JSON Example | Notes |
|---|---|---|
| String | "Alice" | UTF-8 |
| Int32 | 25 | 32-bit integer |
| Int64 | 9223372036854775807 | 64-bit integer |
| Double | 19. | 64-bit floating point |
| Boolean | true, false | |
| Null | null | |
| DateTime | {"$date": "2026-06-01T10:00:00Z"} | Extended JSON |
| Timestamp | {"$timestamp": {"t": 1749031200, "i": 1}} | Extended JSON |
| Binary | {"$binary": {"base64": "SGVsbG8=", "subType": "00"}} | Extended JSON |
| ObjectId | {"$oid": ". | Extended JSON |
| Array | [1, 2, 3] | |
| Document | {"key": "value"} | Nested |
See BUCKET.INSERT for the full reference.
Querying Documents
Section titled “Querying Documents”Match All
Section titled “Match All”An empty filter returns every document in the bucket:
> BUCKET.QUERY users '{}'1# "cursor_id" => (integer) 12# "entries" => 1) {"_id": "507f1f77bcf86cd799439011", "name": "Dave", "age": 28} 2) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 3) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"} 4) {"_id": "6a23da9887f4f93001bd8df8", "name": "Carol", "age": 35, "status": "inactive"} 5) {"_id": "6a23dab187f4f93001bd8df9", "name": "Eve", "email": "eve@example.com"} 6) {"_id": "6a23dabd87f4f93001bd8dfa", "name": "Frank", "age": 40, "address": {"city": "Istanbul", "zip": "34000"}} 7) {"_id": "6a23dac687f4f93001bd8dfb", "name": "Grace", "age": 29, "tags": ["admin", "verified"]}Every response contains a cursor_id and an entries array. Each returned document includes the
server-injected _id field.
Exact Match
Section titled “Exact Match”A plain field value is an implicit $eq:
> BUCKET.QUERY users '{"name": "Alice"}'1# "cursor_id" => (integer) 22# "entries" => 1) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"}Query by _id
Section titled “Query by _id”> BUCKET.QUERY users '{"_id": {"$oid": "6a23da8a87f4f93001bd8df6"}}'1# "cursor_id" => (integer) 42# "entries" => 1) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"}See BUCKET.QUERY for the full reference.
Filtering with BQL
Section titled “Filtering with BQL”BQL (Bucket Query Language) is used by BUCKET.QUERY, BUCKET.DELETE, and BUCKET.UPDATE. All examples
below query the users bucket.
Comparison Operators
Section titled “Comparison Operators”| Operator | Description |
|---|---|
$eq | Equal to |
$ne | Not equal to |
$gt | Greater than |
$gte | Greater than or equal to |
$lt | Less than |
$lte | Less than or equal to |
Explicit comparison:
> BUCKET.QUERY users '{"age": {"$gt": 25}}'1# "cursor_id" => (integer) 52# "entries" => 1) {"_id": "507f1f77bcf86cd799439011", "name": "Dave", "age": 28} 2) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 3) {"_id": "6a23da9887f4f93001bd8df8", "name": "Carol", "age": 35, "status": "inactive"} 4) {"_id": "6a23dabd87f4f93001bd8dfa", "name": "Frank", "age": 40, "address": {"city": "Istanbul", "zip": "34000"}} 5) {"_id": "6a23dac687f4f93001bd8dfb", "name": "Grace", "age": 29, "tags": ["admin", "verified"]}Range shorthand: multiple operators on the same field are implicitly ANDed.
> BUCKET.QUERY users '{"age": {"$gte": 18, "$lt": 65}}'1# "cursor_id" => (integer) 62# "entries" => 1) {"_id": "507f1f77bcf86cd799439011", "name": "Dave", "age": 28} 2) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 3) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"} 4) {"_id": "6a23da9887f4f93001bd8df8", "name": "Carol", "age": 35, "status": "inactive"} 5) {"_id": "6a23dabd87f4f93001bd8dfa", "name": "Frank", "age": 40, "address": {"city": "Istanbul", "zip": "34000"}} 6) {"_id": "6a23dac687f4f93001bd8dfb", "name": "Grace", "age": 29, "tags": ["admin", "verified"]}Logical Operators
Section titled “Logical Operators”Implicit AND, multiple fields in the same object:
> BUCKET.QUERY users '{"status": "active", "age": {"$gte": 18}}'1# "cursor_id" => (integer) 72# "entries" => 1) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 2) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"}Explicit $and:
> BUCKET.QUERY users '{"$and": [{"status": "active"}, {"age": {"$gte": 18}}]}'1# "cursor_id" => (integer) 82# "entries" => 1) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 2) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"}$or:
> BUCKET.QUERY users '{"$or": [{"status": "active"}, {"status": "pending"}]}'1# "cursor_id" => (integer) 92# "entries" => 1) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 2) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"}$nor:
> BUCKET.QUERY users '{"$nor": [{"status": "inactive"}, {"status": "deleted"}]}'1# "cursor_id" => (integer) 102# "entries" => 1) {"_id": "507f1f77bcf86cd799439011", "name": "Dave", "age": 28} 2) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 3) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"} 4) {"_id": "6a23dab187f4f93001bd8df9", "name": "Eve", "email": "eve@example.com"} 5) {"_id": "6a23dabd87f4f93001bd8dfa", "name": "Frank", "age": 40, "address": {"city": "Istanbul", "zip": "34000"}} 6) {"_id": "6a23dac687f4f93001bd8dfb", "name": "Grace", "age": 29, "tags": ["admin", "verified"]}$not:
> BUCKET.QUERY users '{"age": {"$not": {"$gt": 100}}}'1# "cursor_id" => (integer) 112# "entries" => 1) {"_id": "507f1f77bcf86cd799439011", "name": "Dave", "age": 28} 2) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 3) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"} 4) {"_id": "6a23da9887f4f93001bd8df8", "name": "Carol", "age": 35, "status": "inactive"} 5) {"_id": "6a23dab187f4f93001bd8df9", "name": "Eve", "email": "eve@example.com"} 6) {"_id": "6a23dabd87f4f93001bd8dfa", "name": "Frank", "age": 40, "address": {"city": "Istanbul", "zip": "34000"}} 7) {"_id": "6a23dac687f4f93001bd8dfb", "name": "Grace", "age": 29, "tags": ["admin", "verified"]}Array Operators
Section titled “Array Operators”$in, match any value in a set:
> BUCKET.QUERY users '{"status": {"$in": ["active", "pending"]}}'1# "cursor_id" => (integer) 122# "entries" => 1) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 2) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"}$nin, match none of the values:
> BUCKET.QUERY users '{"status": {"$nin": ["deleted", "archived"]}}'1# "cursor_id" => (integer) 132# "entries" => 1) {"_id": "507f1f77bcf86cd799439011", "name": "Dave", "age": 28} 2) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 3) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"} 4) {"_id": "6a23da9887f4f93001bd8df8", "name": "Carol", "age": 35, "status": "inactive"} 5) {"_id": "6a23dab187f4f93001bd8df9", "name": "Eve", "email": "eve@example.com"} 6) {"_id": "6a23dabd87f4f93001bd8dfa", "name": "Frank", "age": 40, "address": {"city": "Istanbul", "zip": "34000"}} 7) {"_id": "6a23dac687f4f93001bd8dfb", "name": "Grace", "age": 29, "tags": ["admin", "verified"]}$all, array field contains all specified values:
> BUCKET.QUERY users '{"tags": {"$all": ["admin", "verified"]}}'1# "cursor_id" => (integer) 142# "entries" => 1) {"_id": "6a23dac687f4f93001bd8dfb", "name": "Grace", "age": 29, "tags": ["admin", "verified"]}$size, array has the specified length:
> BUCKET.QUERY users '{"tags": {"$size": 2}}'1# "cursor_id" => (integer) 152# "entries" => 1) {"_id": "6a23dac687f4f93001bd8dfb", "name": "Grace", "age": 29, "tags": ["admin", "verified"]}$elemMatch, an array element matches a compound condition:
> BUCKET.INSERT users DOCS '{"name": "Henry", "age": 31, "scores": [75, 85, 95]}'1) "6a23e0ac87f4f93001bd8dfd"Query it back with $elemMatch:
> BUCKET.QUERY users '{"scores": {"$elemMatch": {"$gte": 80, "$lt": 90}}}'1# "cursor_id" => (integer) 322# "entries" => 1) {"_id": "6a23e0ac87f4f93001bd8dfd", "name": "Henry", "age": 31, "scores": [75, 85, 95]}Field Operators
Section titled “Field Operators”$exists, field is present or absent:
> BUCKET.QUERY users '{"email": {"$exists": true}}'1# "cursor_id" => (integer) 172# "entries" => 1) {"_id": "6a23dab187f4f93001bd8df9", "name": "Eve", "email": "eve@example.com"}> BUCKET.QUERY users '{"deletedAt": {"$exists": false}}'1# "cursor_id" => (integer) 182# "entries" => 1) {"_id": "507f1f77bcf86cd799439011", "name": "Dave", "age": 28} 2) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 3) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"} 4) {"_id": "6a23da9887f4f93001bd8df8", "name": "Carol", "age": 35, "status": "inactive"} 5) {"_id": "6a23dab187f4f93001bd8df9", "name": "Eve", "email": "eve@example.com"} 6) {"_id": "6a23dabd87f4f93001bd8dfa", "name": "Frank", "age": 40, "address": {"city": "Istanbul", "zip": "34000"}} 7) {"_id": "6a23dac687f4f93001bd8dfb", "name": "Grace", "age": 29, "tags": ["admin", "verified"]}Nested Field Access
Section titled “Nested Field Access”Use dot notation:
> BUCKET.QUERY users '{"address.city": "Istanbul"}'1# "cursor_id" => (integer) 192# "entries" => 1) {"_id": "6a23dabd87f4f93001bd8dfa", "name": "Frank", "age": 40, "address": {"city": "Istanbul", "zip": "34000"}}Null Values
Section titled “Null Values”> BUCKET.QUERY users '{"middleName": null}'1# "cursor_id" => (integer) 202# "entries" => 1) {"_id": "507f1f77bcf86cd799439011", "name": "Dave", "age": 28} 2) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"} 3) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"} 4) {"_id": "6a23da9887f4f93001bd8df8", "name": "Carol", "age": 35, "status": "inactive"} 5) {"_id": "6a23dab187f4f93001bd8df9", "name": "Eve", "email": "eve@example.com"} 6) {"_id": "6a23dabd87f4f93001bd8dfa", "name": "Frank", "age": 40, "address": {"city": "Istanbul", "zip": "34000"}} 7) {"_id": "6a23dac687f4f93001bd8dfb", "name": "Grace", "age": 29, "tags": ["admin", "verified"]}See BQL Language Reference for the complete operator reference.
Sorting and Pagination
Section titled “Sorting and Pagination”Cursor Model
Section titled “Cursor Model”Every BUCKET.QUERY, BUCKET.DELETE, and BUCKET.UPDATE response includes a cursor_id. The cursor
tracks position in the result set so you can page through results with BUCKET.ADVANCE.
Basic Pagination
Section titled “Basic Pagination”> BUCKET.QUERY users '{}' LIMIT 21# "cursor_id" => (integer) 212# "entries" => 1) {"_id": "507f1f77bcf86cd799439011", "name": "Dave", "age": 28} 2) {"_id": "6a23da8a87f4f93001bd8df6", "name": "Alice", "age": 30, "status": "active"}> BUCKET.ADVANCE QUERY 211# "cursor_id" => (integer) 212# "entries" => 1) {"_id": "6a23da9887f4f93001bd8df7", "name": "Bob", "age": 25, "status": "active"} 2) {"_id": "6a23da9887f4f93001bd8df8", "name": "Carol", "age": 35, "status": "inactive"}An empty entries array signals that all matching documents have been returned.
Default Batch Size
Section titled “Default Batch Size”When LIMIT is omitted, the session’s limit attribute controls the batch size (default: 100). You can
change it with:
> SESSION.ATTRIBUTE SET limit 50OKClosing Cursors
Section titled “Closing Cursors”Cursors are stored in the session and consume resources while open. Close a cursor with BUCKET.CLOSE
when you are done:
> BUCKET.CLOSE QUERY 0OKListing Active Cursors
Section titled “Listing Active Cursors”> BUCKET.CURSORS1) QUERY -> 0 -> "{}"2) UPDATE -> (empty map)3) DELETE -> (empty map)Filter by operation type:
> BUCKET.CURSORS QUERY1) QUERY -> 0 -> "{}"Sorting with SORTBY
Section titled “Sorting with SORTBY”BUCKET.QUERY users '{"age": {"$gt": 25}}' SORTBY age ASC LIMIT 10SORTBY requires an index on the sort field, and the query plan must produce results already ordered by that
field. Here the query filters and sorts on the same indexed field, so the index scan provides the ordering.
Results are globally sorted across all BUCKET.ADVANCE calls. If the plan cannot provide the ordering, the
query is rejected at planning time. To sort by a different field than the filter, create a compound index
covering both fields, or use RESULTSORT for in-memory per-batch sorting.
This example needs the age index created in the Indexes section below. Indexes are built
asynchronously; SORTBY is rejected until the index reaches READY status. Check the status with
BUCKET.INDEX DESCRIBE.
See SORTBY for sorting details, BUCKET.ADVANCE, BUCKET.CLOSE, and BUCKET.CURSORS for the full reference.
Updating Documents
Section titled “Updating Documents”$set: Set Field Values
Section titled “$set: Set Field Values”> BUCKET.UPDATE users '{"name": "Alice"}' '{"$set": {"status": "inactive"}}'1# "cursor_id" => (integer) 222# "object_ids" => 1) "6a23da8a87f4f93001bd8df6"The response contains the ObjectIds of all updated documents.
$unset: Remove Fields
Section titled “$unset: Remove Fields”> BUCKET.UPDATE users '{"age": {"$gt": 30}}' '{"$unset": ["temporary_field", "deprecated_field"]}'1# "cursor_id" => (integer) 232# "object_ids" => 1) "6a23da9887f4f93001bd8df8" 2) "6a23dabd87f4f93001bd8dfa"Combining $set and $unset
Section titled “Combining $set and $unset”> BUCKET.UPDATE users '{}' '{"$set": {"version": 2}, "$unset": ["old_field"]}'1# "cursor_id" => (integer) 242# "object_ids" => 1) "507f1f77bcf86cd799439011" 2) "6a23da8a87f4f93001bd8df6" 3) "6a23da9887f4f93001bd8df7" 4) "6a23da9887f4f93001bd8df8" 5) "6a23dab187f4f93001bd8df9" 6) "6a23dabd87f4f93001bd8dfa" 7) "6a23dac687f4f93001bd8dfb"Upsert
Section titled “Upsert”When upsert is true, a new document is inserted if no documents match the filter:
> BUCKET.UPDATE users '{"name": "Helen"}' '{"$set": {"name": "Helen", "status": "active"}, "upsert": true}'1# "cursor_id" => (integer) 252# "object_ids" => 1) "6a23dd0287f4f93001bd8dfc"Array Filters
Section titled “Array Filters”Use the positional $[identifier] syntax with array_filters to update only the array elements that match
a condition. The filter is evaluated against each element. Henry’s scores array is [75, 85, 95]; set the
elements greater than or equal to 80 to 100:
> BUCKET.UPDATE users '{"name": "Henry"}' '{"$set": {"scores.$[elem]": 100}, "array_filters": [{"elem": {"$gte": 80}}]}'1# "cursor_id" => (integer) 262# "object_ids" => 1) "6a23e0ac87f4f93001bd8dfd"Henry’s scores array is now [75, 100, 100].
Ordered Batch Updates
Section titled “Ordered Batch Updates”Combine SORTBY and LIMIT to update documents in a specific order. SORTBY requires the query to produce
results already ordered by the sort field. When the filter field and the sort field are different, create a
compound index that covers both, with the filter field first:
> BUCKET.INDEX CREATE users '{ "$compound": [{ "name": "idx_status_created_at", "fields": [ {"selector": "status", "bson_type": "string"}, {"selector": "created_at", "bson_type": "datetime"} ] }]}'OKIndexes are built asynchronously. Wait until the index reaches READY status before running queries that
sort with it; check with BUCKET.INDEX DESCRIBE.
Insert a few pending documents with created_at timestamps:
> BUCKET.INSERT users DOCS '{ "name": "Ivy", "status": "pending", "created_at": {"$date": "2026-06-01T10:00:00Z"}}' '{ "name": "Jack", "status": "pending", "created_at": {"$date": "2026-06-02T10:00:00Z"}}'1) "6a23e10187f4f93001bd8dfd"2) "6a23e10187f4f93001bd8dfe"> BUCKET.INSERT users DOCS '{ "name": "Kate", "status": "pending", "created_at": {"$date": "2026-06-03T10:00:00Z"}}' '{ "name": "Liam", "status": "pending", "created_at": {"$date": "2026-06-04T10:00:00Z"}}'1) "6a23e10987f4f93001bd8dff"2) "6a23e10987f4f93001bd8e00"With an equality filter on status, the compound index provides natural ordering on created_at:
> BUCKET.UPDATE users '{"status": "pending"}' '{"$set": {"status": "active"}}' SORTBY created_at ASC LIMIT 21# "cursor_id" => (integer) 292# "object_ids" => 1) "6a23e10187f4f93001bd8dfd" 2) "6a23e10187f4f93001bd8dfe"The first batch updates the two oldest pending documents. Advance the cursor to update the next batch:
> BUCKET.ADVANCE UPDATE 291# "cursor_id" => (integer) 292# "object_ids" => 1) "6a23e10987f4f93001bd8dff" 2) "6a23e10987f4f93001bd8e00"See BUCKET.UPDATE for the full reference.
Deleting Documents
Section titled “Deleting Documents”Delete by Filter
Section titled “Delete by Filter”> BUCKET.DELETE users '{"status": "inactive"}'1# "cursor_id" => (integer) 272# "object_ids" => 1) "6a23da8a87f4f93001bd8df6" 2) "6a23da9887f4f93001bd8df8"Batched Deletes
Section titled “Batched Deletes”Use LIMIT and BUCKET.ADVANCE to delete in batches:
> BUCKET.DELETE users '{"status": "inactive"}' LIMIT 1001# "cursor_id" => (integer) 282# "object_ids" => 1) "6a23da8a87f4f93001bd8df6" 2) "6a23da9887f4f93001bd8df8"Advance the cursor:
> BUCKET.ADVANCE DELETE 0cursor_id -> (integer) 0object_ids -> [...] (next 100 deleted)Delete All Documents
Section titled “Delete All Documents”> BUCKET.DELETE users '{}'1# "cursor_id" => (integer) 282# "object_ids" => 1) "6a23da8a87f4f93001bd8df6" 2) "6a23da9887f4f93001bd8df8"Note: SORTBY is not supported on BUCKET.DELETE.
See BUCKET.DELETE for the full reference.
Indexes
Section titled “Indexes”An index is an accelerator. There is no semantic difference between indexed and non-indexed fields. A query returns the same results regardless of which indexes exist; indexes only affect performance.
Primary Index
Section titled “Primary Index”Every bucket has a primary index on _id. It is created automatically and cannot be dropped.
Creating Secondary Indexes
Section titled “Creating Secondary Indexes”> BUCKET.INDEX CREATE users '{"age": {"bson_type": "int32"}}'OKCreate multiple indexes at once:
> BUCKET.INDEX CREATE users '{"age": {"bson_type": "int32"}, "email": {"bson_type": "string"}}'OKIndexes at Bucket Creation Time
Section titled “Indexes at Bucket Creation Time”You can define indexes when creating a bucket:
> BUCKET.CREATE events INDEXES '{"timestamp": {"bson_type": "datetime"}}'OKMulti-Key Indexes
Section titled “Multi-Key Indexes”For array fields, set multi_key to true so each array element gets its own index entry:
> BUCKET.INDEX CREATE users '{"tags": {"bson_type": "string", "multi_key": true}}'OKCompound Indexes
Section titled “Compound Indexes”A compound index covers multiple fields in a defined order. Use it when queries consistently filter on the same combination of fields:
> BUCKET.INDEX CREATE products '{ "$compound": [{ "name": "idx_cat_price", "fields": [ {"selector": "category", "bson_type": "string"}, {"selector": "price", "bson_type": "double"} ] }]}'OKQuery using both fields. The compound index handles the entire filter in a single scan:
BUCKET.QUERY products '{"category": {"$eq": "electronics"}, "price": {"$gt": 100.0}}'The query engine matches filters left to right against the compound index fields. All fields before the last
must use equality; only the last matched field may use a range operator. At least two fields must match for
the compound index to activate. The exception is SORTBY: a single equality match is enough when the sort
field is the next field in the index, as in the ordered batch updates example above.
See Compound Indexes for the prefix rule, trade-offs, and constraints.
Index Lifecycle
Section titled “Index Lifecycle”Indexes are built asynchronously in the background. After creation, an index progresses through these states:
| Status | Description |
|---|---|
WAITING | Queued for building. |
BUILDING | Background task is populating entries. |
READY | Available for query planning. |
DROPPED | Marked for deletion and cleanup. |
FAILED | Build failed. |
Monitor progress with BUCKET.INDEX TASKS:
BUCKET.INDEX TASKS users "selector:age.bsonType:INT32"Check index details with BUCKET.INDEX DESCRIBE:
> BUCKET.INDEX DESCRIBE users "selector:age.bsonType:INT32"1# "index_type" => "single_field"2# "id" => (integer) 30487559502048408373# "selector" => "age"4# "bson_type" => "INT32"5# "status" => "READY"6# "collation" => 1# "locale" => (nil) 2# "strength" => (nil) 3# "case_level" => (nil) 4# "case_first" => (nil) 5# "numeric_ordering" => (nil) 6# "alternate" => (nil) 7# "backwards" => (nil) 8# "normalization" => (nil) 9# "max_variable" => (nil)7# "statistics" => 1# "cardinality" => (integer) 6Listing and Dropping Indexes
Section titled “Listing and Dropping Indexes”> BUCKET.INDEX LIST users1) "primary-index"2) "selector:age.bsonType:INT32"3) "selector:email.bsonType:STRING"4) "selector:tags.bsonType:STRING"> BUCKET.INDEX DROP users "selector:email.bsonType:STRING"OKThe primary index (primary-index) cannot be dropped.
Analyzing Statistics
Section titled “Analyzing Statistics”Trigger statistics analysis to help the query optimizer choose better plans:
> BUCKET.INDEX ANALYZE users "selector:age.bsonType:INT32"OKStrict Typing
Section titled “Strict Typing”Index types must be compatible with query predicate types. Given an index on age with type int32:
{"age": {"$eq": 25}} -- uses the index (int32 matches int32){"age": {"$eq": "25"}} -- does NOT use the index (string does not match int32)No implicit type coercion is performed between unrelated types. Numeric types (INT32, INT64, DOUBLE, DECIMAL128)
support lossless widening. See strict-types.md for details.
See BUCKET.INDEX for the full reference.
Inspecting Query Plans
Section titled “Inspecting Query Plans”Use BUCKET.EXPLAIN to see how a query will be executed without running it:
> BUCKET.EXPLAIN users '{"age": {"$gt": 25}}'1# "is_cached" => (false)2# "plan" => 1# "planner_version" => (integer) 1 2# "nodeType" => "IndexScan" 3# "id" => (integer) 2 4# "scanType" => "INDEX_SCAN" 5# "index" => "selector:age.bsonType:INT32" 6# "selector" => "age" 7# "operator" => "GT" 8# "operand" => "Param[ref=ParamRef[index=0]]"Key Node Types
Section titled “Key Node Types”| Node Type | Meaning |
|---|---|
FullScan | Scans all documents (no usable index). |
IndexScan | Point lookup on an index. |
RangeScan | Bounded range scan on an index. |
TransformWithResidualPredicate | Post-scan filter for conditions that could not be pushed into an index. |
Union | Combines results from multiple scans (logical OR). |
CompoundIndexScan | Single scan on a compound index using equality prefixes and optional range. |
Intersection | Combines results from multiple scans (logical AND). |
Mixed Indexed and Non-Indexed Filters
Section titled “Mixed Indexed and Non-Indexed Filters”When age is indexed but name is not:
> BUCKET.EXPLAIN users '{"$and": [{"age": {"$eq": 25}}, {"name": {"$eq": "Alice"}}]}'1# "is_cached" => (false)2# "plan" => 1# "planner_version" => (integer) 1 2# "nodeType" => "IndexScan" 3# "id" => (integer) 23 4# "scanType" => "INDEX_SCAN" 5# "index" => "selector:age.bsonType:INT32" 6# "selector" => "age" 7# "operator" => "EQ" 8# "operand" => "Param[ref=ParamRef[index=1]]" 9# "next" => 1# "planner_version" => (integer) 1 2# "nodeType" => "TransformWithResidualPredicate" 3# "id" => (integer) 26 4# "operation" => "FILTER" 5# "predicate" => 1# "type" => "AND" 2# "children" => 1) 1# "type" => "PREDICATE" 2# "selector" => "name" 3# "operator" => "EQ" 4# "operand" => "Param[ref=ParamRef[index=0]]"The plan uses an IndexScan on age followed by a TransformWithResidualPredicate that filters on name.
Plan Caching
Section titled “Plan Caching”Plans are cached by query shape, the structural fingerprint without literal values. Two queries with the same operators and field paths but different values share the same cached plan. The cache is invalidated when indexes are created or dropped.
> BUCKET.QUERY users '{"status": "active"}'...
> BUCKET.EXPLAIN users '{"status": "active"}'is_cached -> (boolean) trueplan -> ...Detecting Full Scans
Section titled “Detecting Full Scans”If BUCKET.EXPLAIN shows a FullScan node for a query you run frequently, create an index on the
filtered field.
See BUCKET.EXPLAIN for the full reference.
Transactions
Section titled “Transactions”By default, every command runs in auto-commit mode. Each operation is its own transaction.
For multi-step atomic operations, use explicit transactions:
> BEGINOK
> BUCKET.INSERT users DOCS '{"name": "Ivy", "age": 22}'1) "6835a1c0e4b0f72a3c000007"
> BUCKET.UPDATE users '{"name": "Alice"}' '{"$set": {"status": "inactive"}}'1# "cursor_id" => (integer) 302# "object_ids" => 1) "6835a1c0e4b0f72a3c000001"
> COMMITOKTo cancel, use ROLLBACK instead of COMMIT.
Within an explicit transaction, reads reflect prior writes from the same transaction (read-your-writes).
FoundationDB imposes two constraints on transactions:
| Constraint | Limit |
|---|---|
| Transaction size | 10 MB |
| Transaction duration | 5 seconds |
See Transactions for snapshot reads, size inspection, and advanced usage.
Namespaces
Section titled “Namespaces”Buckets live inside namespaces. The default namespace is global. All bucket commands operate within the
current session namespace.
Switch to a different namespace:
> NAMESPACE USE productionOKCheck the current namespace:
> NAMESPACE CURRENT"production"Any bucket created or queried after NAMESPACE USE belongs to that namespace:
> BUCKET.CREATE ordersOK
> BUCKET.LIST1) "orders"See Namespaces for hierarchical namespaces, creation, and removal.
Bucket Lifecycle
Section titled “Bucket Lifecycle”Removing a bucket is a two-phase process.
Phase 1: Soft Delete
Section titled “Phase 1: Soft Delete”BUCKET.REMOVE marks the bucket as logically removed. The bucket becomes inaccessible, and background workers
(index maintenance, replication) are signaled to stop.
> BUCKET.REMOVE usersOKPhase 2: Hard Delete
Section titled “Phase 2: Hard Delete”BUCKET.PURGE permanently deletes all bucket data, indexes, and metadata. Before proceeding, it enforces a
distributed sync barrier that verifies every alive cluster member has observed the removal event.
> BUCKET.PURGE usersOKIf the barrier is not yet satisfied, the command returns BARRIERNOTSATISFIED. Retry the command; one
retry is usually enough.
> BUCKET.PURGE users(error) BARRIERNOTSATISFIED Barrier not satisfied: not all shards observed version ...
> BUCKET.PURGE usersOKAfter purging, the bucket name can be reused.
See BUCKET.REMOVE and BUCKET.PURGE for the full reference.