# RushDB 3.0 vs 2.x: what changed and how to upgrade

> RushDB 3.0 replaces the Neo4j-based 2.x platform with a standalone Rust engine. What changed, what Cloud and self-hosted users do, and how to move data.

Canonical URL: https://rushdb.com/blog/rushdb-3-vs-2-upgrade-guide

Updated: 2026-11-11T20:44:18.022Z
Author: RushDB
Category: Guides

RushDB 2.x was an application platform on top of Neo4j. RushDB 3.0 is a database engine of its own: one Rust binary with its own storage, which RushDB Cloud runs and which you can self-host. The upgrade path depends on how you run RushDB. Cloud users upgrade the SDKs and the MCP server; Cloud already runs the 3.0 engine, so their data needs no action. Self-hosted 2.x users run the 3.0 engine and move their data, because 3.0 cannot read a Neo4j store.

## TL;DR

- On Cloud, your data already lives on the 3.0 engine. You install the 3.x SDKs and MCP server and check the behaviour-change table.
- The 2.x platform can't be self-hosted in 3.0. Self-hosters move to Cloud or run the engine and re-import in six steps.
- 3.x clients add keyword and hybrid search, named embedding indexes, and a direct mode that points the same SDK at your own engine.
- Bring-your-own Neo4j, async import runs, platform self-hosting and the `RUSHDB_*`/`NEO4J_*` platform settings are gone.

## What changed between RushDB 2.x and 3.0?

RushDB 3.0 replaces the 2.x application platform, a NestJS API on Neo4j with a SQL metadata store, with one Rust database binary that owns its storage. It adds SQL, Gremlin, Mongo-style and SQL/PGQ queries and an exact schema catalog, and the engine moves to Apache-2.0.

| | RushDB 2.x | RushDB 3.0 |
|---|---|---|
| Architecture | Application platform (NestJS API) with Core REST routes and managed services | One database engine binary, `rushdb`, with its own storage |
| Storage | Neo4j store; Neo4j with APOC Core was the required graph backend | LMPG occurrence layout in redb plus a segmented WAL, in one data directory |
| Metadata store | SQLite or PostgreSQL for users, workspaces, projects and tokens | None in the engine; users, projects and billing are RushDB Cloud |
| External services | Neo4j, a SQL database, optional OpenAI-compatible embedding and chat endpoints | None; storage, SQL, graph planner, text and vector search are linked into the binary |
| API | Core REST routes and SDKs | Protocol v1 over HTTP, gRPC and Bolt; PostgreSQL wire for SQL. Cloud keeps a REST API |
| Query languages | SearchQuery; raw Cypher only with your own Neo4j | SearchQuery, SQL, Gremlin, Mongo-style, SQL/PGQ; openCypher and ISO GQL as release candidates |
| `query.raw` | Returned a Neo4j driver result | Runs openCypher in the engine and returns rows keyed by column |
| Embeddings | Managed embedding indexes in the platform | Engine stores and searches vectors you supply; managed embeddings are a Cloud feature |
| Tenancy | Application-level project isolation | Engine-level tenants selected per request (5-byte key prefix) |
| Traversal depth | `hops` capped by `RUSHDB_MAX_TRAVERSAL_HOPS` (default 10) | `hops` without `max` means up to 10; "unbounded" no longer exists |
| Self-hosting | The whole platform: Docker Compose, Helm, `rushdb/platform` image | The engine only; dashboard and AI features are Cloud |
| Licence | Platform core and dashboard Elastic License 2.0; SDKs Apache-2.0 | Apache-2.0 |

Sources: the [3.x migration page](https://docs.rushdb.com/migration/from-2x), the engine manual's [upgrading from 2.x](https://docs.rushdb.com/engine/operations/upgrading-from-2x), and the [2.x licensing page](https://docs.rushdb.com/2.x/rushdb-cloud/licensing-and-services/).

```mermaid
flowchart LR
  subgraph v3["RushDB 3.0"]
    direction TB
    SDK3["SDKs, MCP, psql, drivers"] -->|"protocol v1: HTTP, pgwire, gRPC, Bolt"| BIN["rushdb binary"]
    BIN --> DIR["One data directory: WAL and redb"]
  end
  subgraph v2["RushDB 2.x"]
    direction TB
    SDK2["SDKs, MCP"] -->|"Core REST"| API["NestJS platform API"]
    API --> NEO["Neo4j with APOC"]
    API --> META["SQLite or PostgreSQL metadata"]
    API -.->|"optional"| EMB["Embedding and chat endpoints"]
  end
```

3.0 also keeps an exact schema catalog on every commit (`describe.*`, `propertyValues`), which step 6 below uses to check a migration. The [RushDB 3.0 release notes](https://rushdb.com/blog/rushdb-3-0-release-notes) list what ships, and [Inside RushDB 3](https://rushdb.com/blog/lmpg-rust-architecture-deep-dive)  covers the storage layout.

## What do RushDB Cloud users need to do?

RushDB Cloud users upgrade the clients and leave the data alone. RushDB Cloud runs the 3.0 engine and Cloud projects already live on it; you install the 3.x JavaScript or Python SDK and the 3.x MCP server, then check the behaviour changes in the table below.

Upgrade the clients:

```bash
npm install @rushdb/javascript-sdk@latest
pip install -U rushdb
```

Code written against the 2.x SDK keeps working on Cloud, because the record, relationship, label, property, search and transaction APIs are unchanged. Six behaviours did change.

| Change | What to do |
|---|---|
| Untyped `records.detach` removes relationships of every type, in both directions | Pass `typeOrTypes` and `direction` when you mean one kind |
| Some writes are refused inside a transaction: delete by query, `detach`, `relationships.createMany`/`deleteMany`, upserts and merge imports, inline vectors, property deletes. Cloud answers `400` and rolls back | Run them outside the transaction |
| Reads inside a transaction see committed data, not staged writes | Use the record a staged write returns |
| `query.raw` runs openCypher in the engine and returns rows in `data.records` | Return named columns (`RETURN p.name AS name`) |
| `hops` without `max` means up to 10 hops | Set `max` for deeper chains |
| Project settings no longer report `customDB`/`managedDB` | Remove code that reads them |

Language-specific changes:

- TypeScript: under Node, an ESM `import RushDB from '@rushdb/javascript-sdk'` now gets the `RushDB` class as the default export. Drop any `.default` workaround. `records.findById([...ids])` now searches by id.
- Python: `records.find` raises `RushDBError` on a failed request instead of returning an empty result. `ImportsAPI` is removed; use `records.import_json` or `import_csv` in batches. Python 3.10+ is required, and `requests` is no longer installed.

The MCP server (`npx -y @rushdb/mcp-server`) works with `RUSHDB_API_KEY` as before. It adds `listTextIndexes`, `createTextIndex`, `deleteTextIndex`, `textSearch` and `smartSearch`, and `vectorSearch` takes `index`, `text` and `fusion`. `detachRelation` no longer takes `transactionId`.

## How do you migrate a self-hosted RushDB 2.x deployment?

The 2.x platform (Docker Compose, Helm, templates, the `rushdb/platform` image) has no 3.0 equivalent. You have two choices: move to RushDB Cloud, or run the 3.0 engine and move your data into it. The engine is what you self-host now; the dashboard, Cloud REST API with API keys, managed embeddings, natural-language search and relationship suggestions stay on Cloud.

Treat it as an application migration. Keep 2.x running while you export. In a disposable 3.0 database, validate the record ids you depend on, null and array handling, label choices, the fields that feed vector indexes, and tenant boundaries. One 2.x project usually becomes one tenant in a multitenant 3.0 server.

### Moving the data, step by step

1. **Create the server with its final tenancy mode.** It cannot change later.

   ```bash
   rushdb serve --data-dir /var/lib/rushdb --multitenant --init /var/lib/rushdb
   ```

   `--init` writes and validates `config.yml`, then exits ([CLI reference](https://docs.rushdb.com/engine/reference/cli-reference)). Start the server with the same flags without `--init`.

2. **Export the 2.x data as JSON**, one export per project. The upgrade docs don't name a 2.x export tool. One route that works with the SDK is to page through `records.find` and save the pages as JSON, which is how the 3.x docs move Cloud data to a [self-hosted engine](https://docs.rushdb.com/learn/tutorials/use-cases/cloud-vs-self-hosted).
3. **Ingest each project into its tenant**, about 500 documents per request. `ingest` turns nested JSON into records and relationships:

   ```bash
   curl -s -H "Authorization: Bearer $RUSHDB_TOKEN" \
     -H "X-RushDB-Tenant: project-42" -H 'Content-Type: application/json' \
     -d '{"protocolVersion":1,"context":{"scope":"project-42"},
          "operation":{"kind":"ingest","ingest":{"kind":"documents","label":"Customer",
          "documents":[{"name":"Acme","orders":[{"total":120}]}]}}}' \
     http://127.0.0.1:3000/protocol/v1/execute
   ```

4. **Re-create relationships not expressed by nesting** with the `linkRecords*` mutations, and declare the ordered indexes your queries need. 3.0 has no automatic value indexes.
5. **Re-create vector indexes and upload embeddings.** The engine does not call an embedding model.
6. **Compare per-label record counts** between 2.x and 3.0, then run your application's tests against 3.0 before cutover.

For step 6, the catalog gives per-label counts without a scan: `describe.schema` returns each label's `recordsCount`, per-property counts and relationship counts ([schema catalog](https://docs.rushdb.com/engine/data-model/schema-catalog)). From the [schema introspection example](https://github.com/rush-db/examples/tree/main/schema-introspection-loop) (made-up data; output excerpt, formatted with `jq`):

```json
{"protocolVersion":1,"operation":{"kind":"describe",
  "describe":{"kind":"schema","query":{"labels":["ORDER"]}}}}
```

```text
label ORDER: 18 records
  ORDER.amount (number): 18
  ORDER.refundedAt (datetime): 5
  ORDER.status (string): 18
CUSTOMER -PLACED-> ORDER: 18
```

### Pointing clients at the engine

SDK and MCP clients of a self-hosted 2.x deployment switch to direct mode against your engine:

```typescript
import RushDB from '@rushdb/javascript-sdk'

// A self-hosted engine
const engine = new RushDB(process.env.RUSHDB_TOKEN!, {
  direct: true,
  engineUrl: 'http://127.0.0.1:3000',
  scope: 'tenant-1' // only for an engine started with --multitenant
})
```

Cloud-only methods (`ai.search`, managed embeddings, relationship patterns) throw `CloudOnlyError` before sending anything. `direct: true` without `engineUrl` throws instead of falling back to Cloud. The MCP server's direct mode uses `RUSHDB_MODE=direct`, `RUSHDB_ENGINE_URL` and `RUSHDB_ENGINE_TOKEN` over stdio ([SDK docs](https://docs.rushdb.com/connect/sdks)).

Clients that talked to Neo4j directly port to the native protocol or a query language. SQL over the PostgreSQL wire and Gremlin are stable; openCypher over HTTP or Bolt is a release candidate.

## What was removed with the Neo4j platform?

Four things go away with the 2.x platform: self-hosting the whole platform, bring-your-own Neo4j for Cloud projects, async import runs, and the `RUSHDB_*` and `NEO4J_*` platform settings. Each has a 3.0 replacement, usually the engine itself or Cloud, listed in the table below.

| 2.x feature | In RushDB 3.0 |
|---|---|
| Self-hosting the platform (Compose, Helm, templates, `rushdb/platform`) | Self-host the engine; dashboard and AI features are Cloud |
| Bring-your-own Neo4j for a Cloud project | The engine stores every project |
| Async import runs (`/imports`, Python `ImportsAPI`) | `importJson`/`importCsv` in batches, or the engine's nested `ingest` |
| `RUSHDB_*` platform variables, `NEO4J_*` settings | The engine's configuration reference |

## How do upgrades between 3.x releases work?

Upgrades between 3.x releases are a binary swap: take a base backup, stop the server cleanly, replace `rushdb` and start it again. When a release changes the storage format, you export a graph snapshot with the old binary and restore it with the new one.

- Each release reads exactly one storage format, recorded in `rushdb.redb.layout.json`, and refuses any other at startup. On a mismatch, export a graph snapshot with the old binary (`--export`), restore it with the new one (`--restore`) into a new data directory, then re-declare indexes and vectors. 3.0 writes format 6; directories from pre-release builds on format 5 do not open and must be reloaded this way.
- Some releases rewrite rows in place at first start instead, before the node serves requests. 3.0.7 rewrites vector binding rows to a binary form (migration `0001-binary-vector-bindings`, no re-indexing). The rewrite is one-way, so take a backup before upgrading from 3.0.6 or earlier if you may need to go back ([3.0.7 changelog](https://github.com/rush-db/rushdb/blob/main/CHANGELOG.md)).
- `config.yml` carries a schema `version`; new settings absent from an existing file take their defaults.

## When should you stay on 2.x for now?

Stay on 2.x while you depend on something 3.0 does not offer self-hosted. That means the dashboard and managed embeddings on your own servers, or Cypher-heavy code you cannot test against a release-candidate Cypher profile (as of November 2026, 57% of RushDB's TCK profile passes, per the [query languages page](https://docs.rushdb.com/engine/languages)). The 2.x code stays available on the [`2.x` branch](https://github.com/rush-db/rushdb/tree/2.x) of the `rush-db/rushdb` repository and gets security fixes only, until 2027-10-28 ([repository README](https://github.com/rush-db/rushdb#versions)); new work happens on 3.x.

## FAQ

### Do I have to migrate my data if I use RushDB Cloud?

No. RushDB Cloud already runs the 3.0 engine and holds your projects there, so the work is in client code. Upgrade the SDK and the MCP server to 3.x, then go through the behaviour-change table, starting with code that writes inside transactions, because Cloud now answers `400` and rolls back for the refused writes, where 2.x accepted them.

### Can RushDB 3.0 open my 2.x data directory?

No. RushDB 3.0 does not read a 2.x data directory or a Neo4j store, and it does not accept 2.x Core REST requests. Export each 2.x project as JSON and ingest it into a 3.0 tenant in batches of about 500 documents, then re-create links, indexes and vectors.

### How long does re-importing a large 2.x project take?

It depends on how the data is split across tenants. In our capacity runs on one laptop-class node (storage format 5; 3.0 writes format 6), bulk ingest reached 12,600 records per second with 8 writers and 500-document batches over 200K records in 50 tenants. A single 10M-record tenant ingested at 3,592 records per second, about 46 minutes, because one tenant's commits are serialized.

### Can I keep using my own Neo4j instance?

No. Projects no longer connect to an external Neo4j database; the RushDB engine stores every project. If your code sends Cypher, it can run against the engine's openCypher support, which is a release candidate, over HTTP or over Bolt with the Neo4j drivers. On a multitenant server, the driver's session database selects the tenant (since 3.0.6).

## Sources

- [Migrate from RushDB 2.x (3.x docs)](https://docs.rushdb.com/migration/from-2x)
- [Upgrading from RushDB 2.x (engine manual)](https://docs.rushdb.com/engine/operations/upgrading-from-2x)
- [SDKs and direct mode](https://docs.rushdb.com/connect/sdks)
- [MCP server](https://docs.rushdb.com/connect/mcp)
- [Schema catalog](https://docs.rushdb.com/engine/data-model/schema-catalog)
- [Query languages and status](https://docs.rushdb.com/engine/languages)
- [RushDB 2.x licensing and services](https://docs.rushdb.com/2.x/rushdb-cloud/licensing-and-services/)
- [RushDB on GitHub](https://github.com/rush-db/rushdb)
