Software

ArcadeDB

A database that keeps documents, graphs, vectors, and time series in one engine. Run the official server with one Docker command, and open Studio in your browser.

Illustration of a document, a triangle graph, vector arrows and a stepped time-series line in one frame around a rose circle at the center, standing for one engine that holds four kinds of data.

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 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, and it is the same engine in both. As a server, other processes and machines reach it over HTTP or a database protocol. Choose the server when more than one process or machine needs the data, when you need replication, failover or TLS, or when you work from another language. Embedded, it runs inside your own process with no service to start. Choose that for notebooks, tests, single-node services and agent tooling: ArcadeDB in Python is the package for it.

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. Without a tag Docker pulls latest, which is the development build; for production, pin a release tag from Docker Hub.

docker run -d --name arcadedb -p 2480:2480 \
  --env ARCADEDB_SETTINGS="-Darcadedb.server.rootPassword=playwithdata" \
  arcadedata/arcadedb

Pick your own password. The user is root. Pass server settings in ARCADEDB_SETTINGS, not JAVA_OPTS, which would drop the image's garbage-collector setting. 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 with curl.

curl -u root:playwithdata -H 'Content-Type: application/json' \
  -d '{"command": "create database mydb"}' \
  http://localhost:2480/api/v1/server

Query

Ask it in SQL or openCypher.

Schema and queries are SQL or openCypher. You send them to one HTTP endpoint, with the database name in the URL and the language in the body.

curl -u root:playwithdata -H 'Content-Type: application/json' \
  -d '{"language": "sql", "command": "CREATE DOCUMENT TYPE Note"}' \
  http://localhost:2480/api/v1/command/mydb

Graphs are property graphs. You declare vertex and edge types, then connect vertices that already exist. These statements are SQL, so send each one the same way.

CREATE VERTEX TYPE Person
CREATE EDGE TYPE Knows
INSERT INTO Person SET name = 'Alice'
INSERT INTO Person SET name = 'Bob'
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. An edge type is two-way by default. Declare it UNIDIRECTIONAL only when you never query it from the target. You can traverse with SQL or with openCypher, and a query can take parameters.

curl -u root:playwithdata -H 'Content-Type: application/json' \
  -d '{"language": "opencypher", "command": "MATCH (p:Person {name: $n})-[:Knows]->(f:Person) RETURN f.name", "params": {"n": "Alice"}}' \
  http://localhost:2480/api/v1/command/mydb

The answer is in the result array, here [{"f.name": "Bob"}].

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.

CREATE VERTEX TYPE Doc
CREATE PROPERTY Doc.text STRING
CREATE PROPERTY Doc.embedding ARRAY_OF_FLOATS
CREATE INDEX ON Doc (embedding) LSM_VECTOR
  METADATA {"dimensions": 3, "similarity": "COSINE"}
INSERT INTO Doc SET text = 'a', embedding = [0.9, 0.1, 0.0]

The search takes the index, the query vector and how many neighbors you want. expand turns the answer into rows, each a Doc with its distance.

SELECT expand(vector.neighbors('Doc[embedding]', [0.9, 0.1, 0.0], 2))

The number of dimensions must match your embeddings. The index is built with JVector. The search beam trades recall against speed, so set it for the recall you need. Over HTTP, pass a vector as a parameter with a colon (:q, with the value in params); $q does not work there. 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. The write answers 204 No Content, and precision takes ns (the default), us, ms or s.

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.

SELECT ts.timeBucket('1h', ts) AS hour, avg(value) AS avg_value, count(*) AS n
FROM Reading
GROUP BY hour
ORDER BY hour

If your main query is an hourly aggregate, create the type with COMPACTION_INTERVAL 1 HOURS.

Connect

Use the tools you already have.

Besides HTTP, the server speaks the PostgreSQL wire protocol and Neo4j Bolt, so existing drivers work. Each is a plugin that you turn on in ARCADEDB_SETTINGS. It also speaks Redis, MongoDB and gRPC; the docs have a page for each. For PostgreSQL, publish port 5432 and add the plugin.

docker run --rm -p 2480:2480 -p 5432:5432 \
  --env ARCADEDB_SETTINGS="-Darcadedb.server.rootPassword=playwithdata \
  -Darcadedb.server.plugins=Postgres:com.arcadedb.postgres.PostgresProtocolPlugin" \
  arcadedata/arcadedb

Create the database as above, then connect with psql. See the PostgreSQL page.

export PGPASSWORD=playwithdata
psql -h localhost -p 5432 -U root -d mydb \
  -c "CREATE VERTEX TYPE Person" \
  -c "CREATE VERTEX Person SET name = 'Alice', age = 30" \
  -c "CREATE VERTEX Person SET name = 'Bob', age = 25"
psql -h localhost -p 5432 -U root -d mydb \
  -c "SELECT name, age FROM Person ORDER BY age DESC"
 name  | age
-------+-----
 Alice |  30
 Bob   |  25
(2 rows)

openCypher works over the same connection with a {cypher} prefix.

psql -h localhost -p 5432 -U root -d mydb \
  -c "{cypher}MATCH (p:Person) RETURN p.name AS name ORDER BY name"

For Bolt, publish port 7687 and add the Bolt plugin. The image does not expose 7687 by itself, so the -p is required. See the Bolt page.

docker run --rm -p 2480:2480 -p 7687:7687 \
  --env ARCADEDB_SETTINGS="-Darcadedb.server.rootPassword=playwithdata \
  -Darcadedb.server.plugins=Bolt:com.arcadedb.bolt.BoltProtocolPlugin" \
  arcadedata/arcadedb
from neo4j import GraphDatabase

driver = GraphDatabase.driver("bolt://localhost:7687", auth=("root", "playwithdata"))
with driver.session(database="mydb") as session:
    session.run("CREATE (:Person {name: 'Alice', age: 30})-[:KNOWS]->(:Person {name: 'Bob', age: 25})").consume()
    result = session.run(
        "MATCH (a:Person)-[:KNOWS]->(b:Person) WHERE a.age >= $min_age RETURN a.name AS a, b.name AS b",
        min_age=25,
    )
    for record in result:
        print(record["a"], "knows", record["b"])
driver.close()

That prints Alice knows Bob. To run both protocols in one server, list both plugins in -Darcadedb.server.plugins, separated by a comma:

-Darcadedb.server.plugins=Postgres:com.arcadedb.postgres.PostgresProtocolPlugin,Bolt:com.arcadedb.bolt.BoltProtocolPlugin

Operate

Import, back up, replicate.

IMPORT DATABASE loads CSV, XML, RDF, Neo4j exports and ArcadeDB backups, and EXPORT DATABASE writes one. This creates a database called openbeer from the docs' sample dataset.

curl -u root:playwithdata -H 'Content-Type: application/json' \
  -d '{"command": "import database openbeer https://github.com/ArcadeData/arcadedb-datasets/raw/main/orientdb/OpenBeer.gz"}' \
  http://localhost:2480/api/v1/server

Create indexes after a big load where you can. A file import resolves paths against the server root, so an export written to exports/ is read back with file://exports/....

A backup is a SQL command. Give it a bare file name. The file lands in /home/arcadedb/backups/<database>/ inside the container. See the SQL admin page.

curl -u root:playwithdata -H 'Content-Type: application/json' \
  -d '{"language": "sql", "command": "BACKUP DATABASE '"'"'mydb-backup.zip'"'"'"}' \
  http://localhost:2480/api/v1/command/mydb

For replication and failover, ArcadeDB's high availability uses Raft. The Quick Start in the docs starts a three-node cluster with three settings: arcadedb.ha.enabled, arcadedb.ha.serverList and a unique arcadedb.server.name. See high availability.

HumemAI

What we do.

We send fixes to the engine: issue reports with a Java reproducer and measurements, and merged pull requests for the vector index, query and storage speed, openCypher and the server.

We also maintain ArcadeDB in Python and run MMDBBench, an open benchmark of ArcadeDB against other engines. The research page covers the engine itself.