| name | migration |
| summary | Migrate data and schemas to Couchbase from relational databases (PostgreSQL, MySQL), MongoDB, or DynamoDB |
| description | Migrate data and schemas to Couchbase from relational databases (PostgreSQL, MySQL), MongoDB, or DynamoDB |
| allowed-tools | Bash |
| compatibility | cbimport/cbexport are included with Couchbase Server. cbsh can be used as an alternative for imports. No live cluster required for schema translation. |
| metadata | {"last_verified":"2026-05","min_server_version":"7.0","handoff":[{"condition":"user asks about data modeling decisions","skill":"server-data-modeling"},{"condition":"user asks about slow queries or index recommendations","skill":"server-query-optimizer"},{"condition":"user asks about SQL++ queries","type":"variant","skill":"server-querying-python"},{"condition":"user asks about Couchbase fundamentals or core concepts","skill":"getting-started"}]} |
Couchbase Migration
cbimport โ Bulk JSON Import
cbimport json is the primary tool for loading JSON data into Couchbase.
Formats
| Format | Description | Flag |
|---|
lines | One JSON document per line (JSONL/NDJSON) | --format lines |
list | A JSON array of documents | --format list |
sample | Couchbase sample bucket ZIP | --format sample |
Basic import
cbimport json \
-c couchbase://localhost -u Administrator -p "$CB_ADMIN_PASSWORD" \
-b myapp \
--scope-collection-exp "_default.orders" \
-d file:///data/orders.jsonl \
--format lines \
--generate-key "%id%" \
--threads 4 \
--errors-log /tmp/import-errors.log
cbimport json \
-c couchbase://localhost -u Administrator -p "$CB_ADMIN_PASSWORD" \
-b myapp \
--scope-collection-exp "_default.products" \
-d file:///data/products.json \
--format list \
--generate-key "product::%sku%" \
--threads 4
Key generation
--generate-key "%id%"
--generate-key "order::%customerId%::%orderId%"
--generate-key "doc::#MONO_INCR#"
--generate-key "#UUID#"
--generate-key "%address.country%::%id%"
Route to specific scope/collection
--scope-collection-exp "orders.history"
--scope-collection-exp "%type%.%subtype%"
Capella import
cbimport json \
-c couchbases://cb.xxxxx.cloud.couchbase.com \
-u db-user -p db-password \
--no-ssl-verify \
-b myapp \
--scope-collection-exp "_default.orders" \
-d file:///data/orders.jsonl \
--format lines \
--generate-key "%id%"
cbimport via cbsh (alternative)
cbsh --script "
cb-env bucket myapp
doc import /data/orders.jsonl --scope _default --collection orders --id-field id
"
cbexport โ Bulk JSON Export
cbexport json \
-c couchbase://localhost -u Administrator -p "$CB_ADMIN_PASSWORD" \
-b myapp \
--scope-collection-exp "_default.orders" \
-o /data/orders-export.jsonl \
--format lines \
--threads 4
cbq -e couchbase://localhost -u Administrator -p "$CB_ADMIN_PASSWORD" \
--script "SELECT * FROM \`myapp\`._default.orders WHERE status = 'completed'" \
| jq -c '.results[]' > /data/completed-orders.jsonl
Schema Translation
Relational โ Couchbase
Core rule: denormalize tables that are always JOINed together into a single document. Keep tables that are accessed independently as separate collections.
PostgreSQL example
SELECT o.id, o.created_at, c.name, c.email, oi.sku, oi.qty, oi.price
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN order_items oi ON oi.order_id = o.id
WHERE o.id = 1001;
{
"type": "order",
"orderId": "order::1001",
"createdAt": "2024-03-15T10:30:00Z",
"customer": {
"id": "customer::42",
"name": "Alice Smith",
"email": "alice@example.com"
},
"items": [
{ "sku": "WIDGET-42", "qty": 2, "price": 9.99 },
{ "sku": "GADGET-7", "qty": 1, "price": 24.99 }
],
"total": 44.97
}
Migration script (PostgreSQL โ JSONL):
psql -U postgres -d mydb -c "
COPY (
SELECT json_build_object(
'type', 'order',
'orderId', 'order::' || o.id,
'createdAt', o.created_at,
'customer', json_build_object('id', 'customer::' || c.id, 'name', c.name, 'email', c.email),
'items', (
SELECT json_agg(json_build_object('sku', oi.sku, 'qty', oi.qty, 'price', oi.price))
FROM order_items oi WHERE oi.order_id = o.id
),
'total', o.total
)
FROM orders o JOIN customers c ON o.customer_id = c.id
) TO '/tmp/orders.jsonl';
" && cbimport json -c couchbase://localhost -u Administrator -p "$CB_ADMIN_PASSWORD" \
-b myapp --scope-collection-exp "_default.orders" \
-d file:///tmp/orders.jsonl --format lines --generate-key "%orderId%"
MySQL โ Couchbase
MySQL migration follows the same denormalization pattern as PostgreSQL. Use mysqldump or a SELECT INTO OUTFILE to export, then transform to JSONL.
mysql -u root -p mydb -e "
SELECT JSON_OBJECT(
'type', 'order',
'orderId', CONCAT('order::', o.id),
'createdAt', DATE_FORMAT(o.created_at, '%Y-%m-%dT%H:%i:%sZ'),
'customer', JSON_OBJECT('id', CONCAT('customer::', c.id), 'name', c.name, 'email', c.email),
'items', (
SELECT JSON_ARRAYAGG(JSON_OBJECT('sku', oi.sku, 'qty', oi.qty, 'price', oi.price))
FROM order_items oi WHERE oi.order_id = o.id
),
'total', o.total
)
FROM orders o JOIN customers c ON o.customer_id = c.id
INTO OUTFILE '/tmp/orders.jsonl'
LINES TERMINATED BY '\n';
" && cbimport json -c couchbase://localhost -u Administrator -p "$CB_ADMIN_PASSWORD" \
-b myapp --scope-collection-exp "_default.orders" \
-d file:///tmp/orders.jsonl --format lines --generate-key "%orderId%"
If INTO OUTFILE is restricted, use mysql ... | python3 to stream:
mysql -u root -p mydb --batch --silent -e "
SELECT JSON_OBJECT('type','order','orderId',CONCAT('order::',o.id),'total',o.total)
FROM orders o
" > /tmp/orders.jsonl
MySQL โ Couchbase concept mapping:
| MySQL | Couchbase |
|---|
| Database / Schema | Bucket |
| Table | Collection (within a scope) |
| Row | Document |
| Primary key | Document key (META().id) |
| Foreign key | Embedded document or reference by key |
AUTO_INCREMENT | NEXT VALUE FOR sequence or UUID |
| Index | GSI index |
JOIN | SQL++ JOIN or denormalized embed |
| Stored procedure | Eventing Function or application logic |
| Triggers | Eventing Function (OnUpdate / OnDelete) |
| Replication (binlog) | DCP / Kafka connector |
MySQL-specific SQL โ SQL++ differences:
LIMIT x OFFSET y โ same syntax works, but use keyset pagination for large offsets
GROUP_CONCAT(...) โ ARRAY_AGG(...) or ARRAY_TO_STRING(ARRAY_AGG(...), ",")
IFNULL(x, y) โ IFNULL(x, y) (same) or NVL(x, y)
NOW() โ NOW_STR() (returns ISO 8601 string) or NOW_MILLIS() (epoch ms)
ENUM columns โ store as string; validate in application or Eventing
MongoDB โ Couchbase
MongoDB documents map almost directly โ the main differences are _id โ document key and ObjectId โ string.
mongoexport --db mydb --collection orders --out /tmp/orders.jsonl --jsonArray=false
jq -c '{id: ._id."$oid"} + del(._id) | . + {id: .id}' /tmp/orders.jsonl > /tmp/orders-cb.jsonl
cbimport json -c couchbase://localhost -u Administrator -p "$CB_ADMIN_PASSWORD" \
-b myapp --scope-collection-exp "_default.orders" \
-d file:///tmp/orders-cb.jsonl --format lines --generate-key "%id%"
MongoDB โ Couchbase concept mapping:
| MongoDB | Couchbase |
|---|
| Database | Bucket |
| Collection | Collection (within a scope) |
| Document | Document |
_id | Document key (META().id) |
| Index | GSI index |
$lookup (join) | SQL++ JOIN |
| Aggregation pipeline | SQL++ SELECT with GROUP BY / window functions |
| Change streams | DCP / Eventing / Kafka connector |
| Transactions | SDK transactions (same semantics) |
DynamoDB โ Couchbase
aws dynamodb scan --table-name Orders \
--output json | jq -c '.Items[] | with_entries(.value = .value[keys[0]])' \
> /tmp/dynamo-orders.jsonl
cbimport json -c couchbase://localhost -u Administrator -p "$CB_ADMIN_PASSWORD" \
-b myapp --scope-collection-exp "_default.orders" \
-d file:///tmp/dynamo-orders.jsonl --format lines --generate-key "%orderId%"
DynamoDB โ Couchbase concept mapping:
| DynamoDB | Couchbase |
|---|
| Table | Collection |
| Item | Document |
| Partition key + sort key | Document key (concatenate: pk::sk) |
| GSI | GSI (same name, different syntax) |
| Streams | DCP / Eventing |
| Transactions | SDK transactions |
| TTL attribute | Document TTL (meta().expiration) |
Application Code Migration
See references/code-migration.md for MongoDB, PostgreSQL, and DynamoDB driver โ Couchbase SDK translations in Node.js, Python, and Java.
Key SQL โ SQL++ differences:
NULL โ NULL or MISSING (absent field โ null)
IS NULL โ IS NULL OR IS MISSING (or IS NOT VALUED)
AUTO_INCREMENT โ Sequences (NEXT VALUE FOR) or UUID
JOIN ON fk = pk โ JOIN ON field = META(alias).id for key-based joins
LIMIT x OFFSET y โ use keyset pagination for large offsets
Post-Migration Checklist
cbsh --script "query 'SELECT COUNT(*) AS cnt FROM \`myapp\`._default.orders'"
cbsh --script "query 'SELECT * FROM \`myapp\`._default.orders LIMIT 3'"
cat /tmp/import-errors.log
cbsh --script "query 'CREATE INDEX idx_orders_status ON \`myapp\`._default.orders(status, createdAt)'"
cbsh --script "query 'UPDATE STATISTICS FOR \`myapp\`._default.orders INDEX ALL'"
cbsh --script "query 'EXPLAIN SELECT * FROM \`myapp\`._default.orders WHERE status = \"pending\"'"