Software
ArcadeDB
A database that keeps documents, graphs, vectors, and time series in one engine. Run the official server, or run it inside your Python process with uv.

What it is
One engine for several kinds of data.
ArcadeDB is a multi-model database. Documents, graphs, vectors, time series, key/value and full-text search live in one engine, on the same storage and the same write-ahead log. You do not run a separate system for each kind of data and write glue between them.
You can ask it questions in SQL or openCypher, and the official server also speaks Gremlin, GraphQL, the MongoDB query language and Redis commands. A write that touches documents, edges and vectors is one transaction.
You can run it two ways. As a server, other processes and machines reach it over HTTP or a database protocol. Embedded, it runs inside your own process with no service to start. It is the same engine in both.
Server
Run the official server.
The official server comes from arcadedb.com. You can install it from a binary download, with Docker, or on Kubernetes. With Docker it is one command.
docker run -d --name arcadedb -p 2480:2480 \
-e JAVA_OPTS="-Darcadedb.server.rootPassword=playwithdata" \
arcadedata/arcadedb:latest
Pick your own password. The user is root. Studio, the web interface for running queries and browsing your data, opens at http://localhost:2480. The HTTP API is on the same port, so you can create a database and query it with curl.
curl -u root:playwithdata -X POST http://localhost:2480/api/v1/server \
-d '{"command": "create database mydb"}'
curl -u root:playwithdata -X POST http://localhost:2480/api/v1/command/mydb \
-H "Content-Type: application/json" \
-d '{"language": "sql", "command": "CREATE DOCUMENT TYPE Person"}'
Besides HTTP, the server can speak the PostgreSQL wire protocol, Neo4j Bolt (so Neo4j drivers work), Redis, MongoDB and gRPC. Choose the server when more than one process or machine needs the data, or when you need replication, failover or TLS.
Embedded
Or run it inside Python.
We maintain a Python package that runs the same engine inside your process, with the Java runtime bundled. There is nothing else to install and no service to start.
uv add arcadedb-embedded
Schema and queries are SQL, or openCypher for graphs. A write needs a transaction. A read does not.
import arcadedb_embedded as arcadedb
with arcadedb.create_database("./mydb") as db:
db.command("sql", "CREATE DOCUMENT TYPE Person")
db.command("sql", "CREATE PROPERTY Person.name STRING")
with db.transaction():
db.command("sql", "INSERT INTO Person SET name = ?", "Alice")
for record in db.query("sql", "SELECT FROM Person"):
print(record.get("name"))
Use it for notebooks, tests, single-node services and agent tooling. Only one process can open an embedded database at a time. When a second process needs the data, start a server from the same package, with Studio.
with arcadedb.create_server("./databases", root_password="password123") as server:
db = server.create_database("mydb")
print("Studio:", server.get_studio_url())
Your code keeps its direct access and everything else connects over HTTP. That server stops when your script does. The on-disk format is the same as the official server’s, so you can export a database from one and import it into the other.
Graphs
Vertices, edges and traversals.
Graphs are property graphs. You declare vertex and edge types, then connect vertices that already exist.
with db.transaction():
db.command("sql", "CREATE VERTEX TYPE Person")
db.command("sql", "CREATE EDGE TYPE Knows")
db.command("sql", "INSERT INTO Person SET name = 'Alice'")
db.command("sql", "INSERT INTO Person SET name = 'Bob'")
db.command("sql", """
CREATE EDGE Knows
FROM (SELECT FROM Person WHERE name = 'Alice')
TO (SELECT FROM Person WHERE name = 'Bob')
""")
If either end matches nothing, no edge is created and there is no error, so check what the subqueries return. You can traverse with SQL or with openCypher.
result = db.query("opencypher", """
MATCH (p:Person {name: $name})-[:Knows]->(friend:Person)
RETURN friend.name
""", {"name": "Alice"})
An edge type is two-way by default. Declare it UNIDIRECTIONAL only when you never query it from the target. For a large graph, load it with GraphBatch instead of one statement at a time. In our measurements it loaded 50,000 vertices and about a million edges in roughly 7.5 seconds, against about 30 seconds one element at a time.
Vectors
Store embeddings and search them.
You bring the embeddings, from any model. A vector index is created in SQL, and the search is a SQL function, so it combines with filters and graph traversals in the same query.
from arcadedb_embedded import to_java_float_array
db.command("sql", "CREATE VERTEX TYPE Doc")
db.command("sql", "CREATE PROPERTY Doc.text STRING")
db.command("sql", "CREATE PROPERTY Doc.embedding ARRAY_OF_FLOATS")
db.command("sql", """
CREATE INDEX ON Doc (embedding)
LSM_VECTOR
METADATA {"dimensions": 3, "similarity": "COSINE"}
""")
rows = db.query(
"sql",
"SELECT vectorNeighbors('Doc[embedding]', ?, 2) AS res",
to_java_float_array([0.9, 0.1, 0.0]),
).to_list()
The index is built with JVector. The number of dimensions must match your embeddings. The search beam trades recall against speed, so set it for the recall you need. The docs also cover quantization, INT8 storage and sparse vectors.
Time series
Samples with tags and fields.
A time series type has a timestamp, tags for the low-cardinality labels, and numeric fields. You create it in SQL.
CREATE TIMESERIES TYPE Reading TIMESTAMP ts
TAGS (sensor STRING) FIELDS (value DOUBLE)
You can write samples with plain SQL, or in bulk over HTTP with InfluxDB line protocol, one sample per line.
curl -u root:playwithdata -X POST \
'http://localhost:2480/api/v1/ts/mydb/write?precision=s' \
-H 'Content-Type: text/plain' \
--data-binary 'Reading,sensor=s1 value=1.0 1700000000'
Queries are SQL, with ts.timeBucket() to group samples into buckets such as hours. If your main query is an hourly aggregate, create the type with COMPACTION_INTERVAL 1 HOURS.
Loading
Getting a lot of data in.
For documents and rows, db.insert_many(...) batches rows across the Java boundary, and db.insert_columns(...) takes a DataFrame or numpy arrays and is faster still. For graphs, use GraphBatch. Against a server, use POST /api/v1/batch/{db} for graphs and the line-protocol endpoint for time series.
IMPORT DATABASE loads CSV, XML, RDF, Neo4j exports and ArcadeDB backups, and EXPORT DATABASE writes one. Create indexes after a big load where you can.
Benchmarks
How fast it is.
We benchmark ArcadeDB against the engines people use for each kind of data. The numbers, the settings and the paper are on the ArcadeDB research page, and the cross-engine results are in DBBench.
Resources