Redis

ArcadeDB Server supports a subset of the Redis protocol. Please open an issue or a discussion on GitHub to support more commands.

If you’re using ArcadeDB as embedded, please add the dependency to the arcadedb-redisw library. If you’re using Maven include this dependency in your pom.xml file.

<dependency>
    <groupId>com.arcadedb</groupId>
    <artifactId>arcadedb-redisw</artifactId>
    <version>26.10.1</version>
</dependency>

ArcadeDB Redis plugin works in 2 ways:

  • Manage transient (non-persistent) entries in the server. This is useful to manage user sessions and other records you don’t need to store in the database.

  • Manage persistent entries in the database. You can save and read any documents, vertices and edges from the underlying database.

Installation

To start the Redis plugin, enlist it in the server.plugins settings. To specify multiple plugins, use the comma , as separator. Example:

~/arcadedb $ bin/server.sh -Darcadedb.server.plugins="Redis:com.arcadedb.redis.RedisProtocolPlugin"

If you’re using MS Windows OS, replace server.sh with server.bat.

In case you’re running ArcadeDB with Docker, open the port 6379 and use -e to pass settings:

docker run --rm -p 2480:2480 -p 6379:6379 \
        --env ARCADEDB_SETTINGS="-Darcadedb.server.rootPassword=playwithdata \
           -Darcadedb.server.plugins=Redis:com.arcadedb.redis.RedisProtocolPlugin " \
           arcadedata/arcadedb:latest

The Server output will contain this line:

2018-10-09 18:47:58:395 INFO  <ArcadeDB_0> - Redis Protocol plugin started [ArcadeDBServer]

How it works

ArcadeDB works in 2 ways with the Redis protocol:

  • Transient commands, key/value pairs saved will be not saved in the database. This is perfect to store transient data, like user sessions.

  • Persistent commands, key/value pairs allows to store and retrieve ArcadeDB documents, vertices and edges

redis api

Transient (RAM Only) Commands

Below you can find the supported commands. The link takes you to the official Redis documentation. Please open an issue or a discussion on GitHub to support more commands.

The following commands do not take the bucket as a parameter because they work only in RAM on a shared (thread-safe) hashmap. This means all the stored values are reset when the server restarts.

INCR, DECR, INCRBY and INCRBYFLOAT are atomic, as they are in Redis: concurrent clients incrementing the same key all count, and no update is lost. The guarantee is per server, though — these values are not replicated, so in an HA cluster two clients incrementing through different nodes each update their own node’s copy. The same applies to SET with NX/XX: it is a genuine check-and-set on one server, not a cluster-wide lock.

The commands check their argument count, over the Redis wire protocol and in the redis query language alike, and answer wrong number of arguments for '<command>' command when it is wrong, as Redis does: INCR and DECR take the key only, INCRBY, DECRBY and INCRBYFLOAT take the key and the amount (there is no default amount of 1), and a surplus argument is refused, never read as an amount. Amounts and stored values of INCR, INCRBY, DECR and DECRBY must be plain 64-bit integers (+5, 05 and -0 are refused with value is not an integer or out of range). INCRBYFLOAT replies and stores the result as decimal text, as Redis does (0.1 plus 0.2 is 0.3, an integral result is 3, not 3.0), so INCR works on an integral result. In the redis query language GET therefore returns a string for a key written by INCRBYFLOAT.

Available transient commands (in alphabetic order):

  • DECR, Decrement a value by 1

  • DECRBY, Decrement a value by a specific amount (64-bit precision)

  • EXISTS, Check if key exists

  • GET, Return the value associated with a key

  • GETDEL, Remove and return the value associated with a key

  • INCR, Increment a value by 1

  • INCRBY, Increment a value by a specific amount (64-bit precision)

  • INCRBYFLOAT, Increment a value by a specific amount expresses as a float (64-bit precision)

  • SET, Set a value associated with a key. The value is binary-safe: any bytes, valid UTF-8 or not, are returned unchanged by GET. Keys and the arguments of the other commands are text. NX (only set if the key does not exist), XX (only set if the key already exists) and GET (return the previous value instead of OK) are supported. EX/PX/EXAT/PXAT/KEEPTTL are rejected with an error rather than silently ignored, because transient keys in ArcadeDB have no expiry.

Persistent Commands

The following commands act on persistent buckets in the database. Records (documents, vertices and edges) are always in form of JSON embedded in strings. The bucket name is mapped as the database name first, then type, the index or the record’s RID based on the use case. An index must exist on the property you used to retrieve the document, otherwise an error is returned.

For the sake of this tutorial, in a database MyDatabase, we’re going to create the account document type totally schemaless but for some indexed properties: id as a unique long, email as a unique string and the pair firstName and lastName both strings and indexed with a composite key:

CREATE DOCUMENT TYPE Account;

CREATE PROPERTY Account.id LONG;
CREATE INDEX ON Account (id) UNIQUE;

CREATE PROPERTY Account.email STRING;
CREATE INDEX ON Account (email) UNIQUE;

CREATE PROPERTY Account.firstName STRING;
CREATE PROPERTY Account.lastName STRING;
CREATE INDEX ON Account (firstName,lastName) UNIQUE;
You can run the following Redis commands, for example, using the redis-cli tool; under Debian / Ubuntu this is part of the redis-tools package.

Now you can create a new document with Redis protocol and the HSET Redis command:

HSET MyDatabase Account "{'id':123,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"
The database and the type are two separate arguments: HSET <database> <type> <json>. Writing them as one argument (HSET MyDatabase.Account <json>) makes the plugin look for a database named MyDatabase.Account and fails with a "database does not exist" error.

To retrieve the document inserted above by id (O(logN) complexity), you can use the HGET Redis command:

HGET MyDatabase.Account[id] 123
"{'@rid':'#1:0','@type':'Account','id':123,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"

To retrieve the same document by email (O(logN) complexity), you can use the HGET Redis command:

HGET MyDatabase.Account[email] "[email protected]"
"{'@rid':'#1:0','@type':'Account','id':123,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"

To retrieve the same document by the pair firstName and lastName (O(logN) complexity), we are going to use the composite key we created before:

HGET MyDatabase.Account[firstName,lastName] "[\"Jay\",\"Miner\"]"
"{'@rid':'#1:0','@type':'Account','id':123,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"

To retrieve the document inserted above by it RID (O(1) complexity), you can use the HGET Redis command:

HGET MyDatabase "#1:0"
"{'@rid':'#1:0','@type':'Account','id':123,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"

You can also get multiple record in one call by using the HMGET Redis command:

HMGET MyDatabase "#1:0" "#1:1" "#1:2"
"{'@rid':'#1:0','@type':'Account','id':123,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"
"{'@rid':'#1:1','@type':'Account','id':232,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"
"{'@rid':'#1:2','@type':'Account','id':12,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"

Or the same, but by a key:

HMGET MyDatabase.Account[id] 123 232 12
"{'@rid':'#1:0','@type':'Account','id':123,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"
"{'@rid':'#1:1','@type':'Account','id':232,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"
"{'@rid':'#1:2','@type':'Account','id':12,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"

Or by a composite key, using the same […​] JSON array form HGET takes. A key with no record keeps its place in the reply as a null entry, so the Nth reply is always the answer to the Nth key:

HMGET MyDatabase.Account[firstName,lastName] "[\"Jay\",\"Miner\"]" "[\"Nobody\",\"Here\"]"
"{'@rid':'#1:0','@type':'Account','id':123,'email':'[email protected]','firstName':'Jay','lastName':'Miner'}"
(nil)
A key is matched against the index exactly as you wrote it, then converted to the indexed property’s declared type. A key is therefore never guessed to be a number just because it looks like one: on a STRING property, 007 finds the record whose value is the three characters 007, not the one whose value is 7.

To delete the document inserted above by email, you can use the HDEL Redis command:

HDEL MyDatabase.Account[email] "[email protected]"
:1
The returning JSON could have a different ordering of the properties from the one you have inserted. This is because JSON doesn’t maintain the order of properties, but only of arrays ([]).

Available persistent commands (in alphabetic order):

  • HDEL, Delete one or more records by a key, a composite key or record’s id. HDEL <database> <rid> [<rid> …​] deletes those records and replies with how many it deleted

  • HEXISTS, Check if a key exists

  • HGET, Retrieve a record by a key, a composite key or record’s id

  • HMGET, Retrieve multiple records by a key, a composite key or record’s id

  • HSET, Create and update one or more records by a key, a composite key or record’s id

Available miscellaneous commands:

  • PING, Returns its argument (for testing server readiness or latency)

Settings

To change the host where the Redis protocol is listening, set the setting arcadedb.redis.host. A name that resolves to several local addresses, such as localhost (127.0.0.1 and ::1), is bound on every one of them. By default, is 0.0.0.0 which means listen to all the configured network interfaces. To change the default port (6379) set arcadedb.redis.port.

Redis via HTTP API

In addition to the native Redis wire protocol, ArcadeDB also supports Redis commands as a query language via the HTTP API. This allows you to execute Redis commands through the standard ArcadeDB HTTP /command endpoint by specifying language=redis.

This is useful when:

  • You want to use Redis commands without a Redis client library

  • You need to integrate Redis operations into existing HTTP-based workflows

  • You want to combine Redis commands with other ArcadeDB query languages in the same application

Basic Usage

To execute Redis commands via HTTP, send a POST request to the /command endpoint with language set to redis:

curl -X POST "http://localhost:2480/api/v1/command/MyDatabase" \
  -H "Content-Type: application/json" \
  -u root:password \
  -d '{"language": "redis", "command": "PING"}'

Response:

{"result": [{"value": "PONG"}]}

Supported Commands

All transient and persistent commands documented above are available via the HTTP API.

Examples of transient commands:

# SET a value
curl -X POST "http://localhost:2480/api/v1/command/MyDatabase" \
  -H "Content-Type: application/json" \
  -u root:password \
  -d '{"language": "redis", "command": "SET mykey myvalue"}'

# GET a value
curl -X POST "http://localhost:2480/api/v1/command/MyDatabase" \
  -H "Content-Type: application/json" \
  -u root:password \
  -d '{"language": "redis", "command": "GET mykey"}'

# INCREMENT a counter
curl -X POST "http://localhost:2480/api/v1/command/MyDatabase" \
  -H "Content-Type: application/json" \
  -u root:password \
  -d '{"language": "redis", "command": "INCR counter"}'

Examples of persistent commands:

# Store a document
curl -X POST "http://localhost:2480/api/v1/command/MyDatabase" \
  -H "Content-Type: application/json" \
  -u root:password \
  -d '{"language": "redis", "command": "HSET Account {\"id\":1,\"name\":\"John\",\"age\":30}"}'

# Retrieve by index
curl -X POST "http://localhost:2480/api/v1/command/MyDatabase" \
  -H "Content-Type: application/json" \
  -u root:password \
  -d '{"language": "redis", "command": "HGET Account[id] 1"}'

Batch Commands

You can execute multiple commands in a single request by separating them with newlines. Each command is executed sequentially and an array of results is returned.

curl -X POST "http://localhost:2480/api/v1/command/MyDatabase" \
  -H "Content-Type: application/json" \
  -u root:password \
  -d '{"language": "redis", "command": "SET key1 value1\nSET key2 value2\nGET key1\nGET key2"}'

Response:

{"result": [{"value": ["OK", "OK", "value1", "value2"]}]}

Comments are supported within batch commands using # or // prefixes:

# This is a comment
SET key1 value1
// This is also a comment
SET key2 value2

Transactions with MULTI/EXEC

Redis transactions are supported using the standard MULTI and EXEC commands. Commands between MULTI and EXEC are queued and executed atomically.

curl -X POST "http://localhost:2480/api/v1/command/MyDatabase" \
  -H "Content-Type: application/json" \
  -u root:password \
  -d '{"language": "redis", "command": "MULTI\nSET tx1 value1\nSET tx2 value2\nINCR counter\nEXEC"}'

Response (array of results from each command in the transaction):

{"result": [{"value": ["OK", "OK", 1]}]}

(Since v26.10.1: if the transaction hit an internal conflict and had to retry automatically, the reply could contain extra or duplicated entries instead of exactly one result per queued command. HSET and HDEL could be affected the same way on their own automatic retries. This is now fixed.)

RAM commands inside a transaction. The RAM commands are not stored in the database, so a transaction treats them in two different ways, depending on whether the caller acts on their reply:

  • SET follows the transaction. While a transaction is active (a MULTI/EXEC block, the transaction the HTTP command endpoint runs every request in, or one your application opened), the new value is visible to that transaction only, is published when the transaction commits, and is discarded if it rolls back. A block that fails, for example on a duplicated key, leaves no SET behind.

  • INCR, INCRBY, INCRBYFLOAT, DECR, DECRBY and GETDEL are applied atomically at the moment they run, as in Redis, so no two clients can ever receive the same counter value or claim the same key. When ArcadeDB retries a block automatically after a conflict, the retry reuses the value the first attempt obtained instead of applying the command a second time. If the block ultimately fails, the increment is not given back, just as a database sequence is not: the next caller gets the next value, leaving a gap. On a key the same transaction already SET, these commands work on the transaction’s own value and follow the transaction like the SET.

(Since v26.10.1. Before, an automatic retry applied these commands once per attempt, so one EXEC could advance a counter twice, and a SET in a block that failed was still applied.)

The DISCARD command can be used to abort a transaction:

curl -X POST "http://localhost:2480/api/v1/command/MyDatabase" \
  -H "Content-Type: application/json" \
  -u root:password \
  -d '{"language": "redis", "command": "MULTI\nSET discarded value\nDISCARD"}'

A batch can mix transaction blocks and standalone commands, and runs every line in order: a command outside a block runs on its own, the commands between MULTI and EXEC run as one atomic transaction, and the commands between MULTI and DISCARD are dropped. The reply holds one entry per command that ran, in batch order (the commands of a committed block included), so a batch with no DISCARD answers exactly one entry per command. A batch whose only content is a discarded block answers "OK".

curl -X POST "http://localhost:2480/api/v1/command/MyDatabase" \
  -H "Content-Type: application/json" \
  -u root:password \
  -d '{"language": "redis", "command": "SET a 1\nMULTI\nINCR a\nSET b x\nEXEC\nGET b"}'
{"result": [{"value": ["OK", 2, "OK", "x"]}]}

The whole batch is checked before any command runs. EXEC or DISCARD without MULTI, a nested MULTI and a MULTI never closed by EXEC or DISCARD are refused with an error, and none of the batch’s commands is executed.

Since v26.10.1. Before, the batch stopped at its first EXEC or DISCARD and every line after it was silently ignored, commands before MULTI were executed inside the transaction, and an EXEC without MULTI committed the commands before it. Batches that relied on any of these behaviors now either run their remaining lines or are refused.

Programmatic Access

You can also use Redis commands directly from Java code via the query engine:

try (ResultSet rs = database.query("redis", "PING")) {
    Result result = rs.next();
    String value = result.getProperty("value"); // Returns "PONG"
}

// SET and GET
database.command("redis", "SET mykey myvalue");
try (ResultSet rs = database.query("redis", "GET mykey")) {
    String value = rs.next().getProperty("value"); // Returns "myvalue"
}