This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

RedisJSON

JSON support for Redis

Discord Github

RedisJSON is a Redis module that provides JSON support in Redis. RedisJSON lets your store, update, and retrieve JSON values in Redis just as you would with any other Redis data type. RedisJSON also works seamlessly with RediSearch to let you index and query your JSON documents.

Primary features

  • Full support for the JSON standard
  • A JSONPath-like syntax for selecting elements inside documents
  • Documents stored as binary data in a tree structure, allowing fast access to sub-elements
  • Typed atomic operations for all JSON values types

Using RedisJSON

To learn how to use RedisJSON, it's best to start with the Redis CLI. The following examples assume that you're connected to a Redis server with RedisJSON enabled.

With redis-cli

To following along, start redis-cli.

The first RedisJSON command to try is JSON.SET, which sets a Redis key with a JSON value. All JSON values can be used, for example a string:

127.0.0.1:6379> JSON.SET foo $ '"bar"'
OK
127.0.0.1:6379> JSON.GET foo $
"[\"bar\"]"
127.0.0.1:6379> JSON.TYPE foo $
1) string

JSON.GET and JSON.TYPE do literally that regardless of the value's type, but you should really check out JSON.GET prettifying powers. Note how the commands are given the period character, i.e. .. This is the path to the value in the RedisJSON data type (in this case it just means the root). A couple more string operations:

127.0.0.1:6379> JSON.STRLEN foo $
1) (integer) 3
127.0.0.1:6379> JSON.STRAPPEND foo $ '"baz"'
1) (integer) 6
127.0.0.1:6379> JSON.GET foo $
"[\"barbaz\"]"

JSON.STRLEN tells you the length of the string, and you can append another string to it with JSON.STRAPPEND. Numbers can be incremented and multiplied:

127.0.0.1:6379> JSON.SET num $ 0
OK
127.0.0.1:6379> JSON.NUMINCRBY num $ 1
"[1]"
127.0.0.1:6379> JSON.NUMINCRBY num $ 1.5
"[2.5]"
127.0.0.1:6379> JSON.NUMINCRBY num $ -0.75
"[1.75]"
127.0.0.1:6379> JSON.NUMMULTBY num $ 24
"[42]"

Of course, a more interesting example would involve an array or maybe an object:

127.0.0.1:6379> JSON.SET amoreinterestingexample $ '[ true, { "answer": 42 }, null ]'
OK
127.0.0.1:6379> JSON.GET amoreinterestingexample $
"[[true,{\"answer\":42},null]]"
127.0.0.1:6379> JSON.GET amoreinterestingexample $[1].answer
"[42]"
127.0.0.1:6379> JSON.DEL amoreinterestingexample $[-1]
(integer) 1
127.0.0.1:6379> JSON.GET amoreinterestingexample $
"[[true,{\"answer\":42}]]"

The handy JSON.DEL command deletes anything you tell it to. Arrays can be manipulated with a dedicated subset of RedisJSON commands:

127.0.0.1:6379> JSON.SET arr $ []
OK
127.0.0.1:6379> JSON.ARRAPPEND arr $ 0
1) (integer) 1
127.0.0.1:6379> JSON.GET arr $
"[[0]]"
127.0.0.1:6379> JSON.ARRINSERT arr $ 0 -2 -1
1) (integer) 3
127.0.0.1:6379> JSON.GET arr $
"[[-2,-1,0]]"
127.0.0.1:6379> JSON.ARRTRIM arr $ 1 1
1) (integer) 1
127.0.0.1:6379> JSON.GET arr $
"[[-1]]"
127.0.0.1:6379> JSON.ARRPOP arr $
1) "-1"
127.0.0.1:6379> JSON.ARRPOP arr $
1) (nil)

And objects have their own commands too:

127.0.0.1:6379> JSON.SET obj $ '{"name":"Leonard Cohen","lastSeen":1478476800,"loggedOut": true}'
OK
127.0.0.1:6379> JSON.OBJLEN obj $
1) (integer) 3
127.0.0.1:6379> JSON.OBJKEYS obj $
1) 1) "name"
   2) "lastSeen"
   3) "loggedOut"

Python example

This code snippet shows how to use RedisJSON with raw Redis commands from Python with redis-py:

import redis
import json

data = {
    'foo': 'bar'
}

r = redis.Redis()
r.json().set('doc', '$', json.dumps(data))
reply = json.loads(r.json().get('doc', '$')[0])

Building on Ubuntu 20.04

The following packages are required to successfully build on Ubuntu 20.04:

sudo apt install build-essential llvm cmake libclang1 libclang-dev cargo

Then, run make or cargo build --release in the repository directory

Loading the module to Redis

Requirements:

We recommend you have Redis load the module during startup by adding the following to your redis.conf file:

loadmodule /path/to/module/target/release/librejson.so

On Mac OS, if this module has been built as a dynamic library use:

loadmodule /path/to/module/target/release/librejson.dylib

In the above lines replace /path/to/module/ with the actual path to the module's library.

Alternatively, you can have Redis load the module using the following command line argument syntax:

~/$ redis-server --loadmodule ./target/release/librejson.so

Lastly, you can also use the [MODULE LOAD](/commands/module-load) command. Note, however, that MODULE LOAD is a dangerous command and may be blocked/deprecated in the future due to security considerations.

Once the module has been loaded successfully, the Redis log should have lines similar to:

...

1877:M 23 Dec 02:02:59.725 # <RedisJSON> JSON data type for Redis - v1.0.0 [encver 0]
1877:M 23 Dec 02:02:59.725 * Module 'RedisJSON' loaded from <redacted>/src/rejson.so
...

1 - Commands

Commands Overview

Overview

Supported JSON

RedisJSON aims to provide full support for ECMA-404 The JSON Data Interchange Standard.

The term JSON Value refers to any of the valid values. A Container is either a JSON Array or a JSON Object. A JSON Scalar is a JSON Number, a JSON String, or a literal (JSON False, JSON True, or JSON Null).

RedisJSON API

Details on module's commands can be filtered for a specific module or command, e.g., JSON. The details also include the syntax for the commands, where:

  • Command and subcommand names are in uppercase, for example JSON.SET or INDENT
  • Optional arguments are enclosed in square brackets, for example [index]
  • Additional optional arguments are indicated by three period characters, for example ...

Commands usually require a key's name as their first argument. The path is generally assumed to be the root if not specified.

The time complexity of the command does not include that of the path. The size - usually denoted N - of a value is:

  • 1 for scalar values
  • The sum of sizes of items in a container

2 - Search/Indexing JSON documents

Searching and indexing JSON documents

In addition to storing JSON documents, you can also index them using the RediSearch module. This enables full-text search capabilities and document retrieval based on their content. To use this feature, you must install two modules: RedisJSON and RediSearch.

Prerequisites

What do you need to start indexing JSON documents?

  • Redis 6.x or later
  • RedisJSON 2.0 or later
  • RediSearch 2.2 or later

How to index JSON documents

This section shows how to create an index.

You can now specify ON JSON to inform RediSearch that you want to index JSON documents.

For the SCHEMA, you can provide JSONPath expressions. The result of each JSON Path expression is indexed and associated with a logical name (attribute). Use the attribute name in the query.

Here is the basic syntax for indexing a JSON document:

FT.CREATE {index_name} ON JSON SCHEMA {json_path} AS {attribute} {type}

And here's a concrete example:

FT.CREATE userIdx ON JSON SCHEMA $.user.name AS name TEXT $.user.tag AS country TAG

Note: The attribute is optional in FT.CREATE, but FT.SEARCH and FT.AGGREGATE queries require attribute modifiers. You should also avoid using JSON Path expressions, which are not fully supported by the query parser.

Adding a JSON document to the index

As soon as the index is created, any pre-existing JSON document or any new JSON document, added or modified, is automatically indexed.

You can use any write command from the RedisJSON module (JSON.SET, JSON.ARRAPPEND, etc.).

This example uses the following JSON document:

{
  "user": {
    "name": "John Smith",
    "tag": "foo,bar",
    "hp": "1000",
    "dmg": "150"
  }
}

Use JSON.SET to store the document in the database:

    JSON.SET myDoc $ '{"user":{"name":"John Smith","tag":"foo,bar","hp":1000, "dmg":150}}'

Because indexing is synchronous, the document will be visible on the index as soon as the JSON.SET command returns. Any subsequent query that matches the indexed content will return the document.

Searching

To search for documents, use the FT.SEARCH command. You can search any attribute mentioned in the schema.

Following our example, find the user called John:

FT.SEARCH userIdx '@name:(John)'
1) (integer) 1
2) "myDoc"
3) 1) "$"
   2) "{\"user\":{\"name\":\"John Smith\",\"tag\":\"foo,bar\",\"hp\":1000,\"dmg\":150}}"

Indexing JSON arrays with tags

It is possible to index scalar string and boolean values in JSON arrays by using the wildcard operator in the JSON Path. For example if you were indexing blog posts you might have a field called tags which is an array of tags that apply to the blog post.

{
   "title":"Using RedisJson is Easy and Fun",
   "tags":["redis","json","redisjson"]
}

You can apply an index to the tags field by specifying the JSON Path $.tags.* in your schema creation:

FT.CREATE blog-idx ON JSON PREFIX 1 Blog: SCHEMA $.tags.* AS tags TAG

You would then set a blog post as you would any other JSON document:

JSON.SET Blog:1 . '{"title":"Using RedisJson is Easy and Fun", "tags":["redis","json","redisjson"]}'

And finally you can search using the typical tag searching syntax:

127.0.0.1:6379> FT.SEARCH blog-idx "@tags:{redis}"
1) (integer) 1
2) "Blog:1"
3) 1) "$"
   2) "{\"title\":\"Using RedisJson is Easy and Fun\",\"tags\":[\"redis\",\"json\",\"redisjson\"]}"

Field projection

FT.SEARCH returns the whole document by default.

You can also return only a specific attribute (name for example):

FT.SEARCH userIdx '@name:(John)' RETURN 1 name
1) (integer) 1
2) "myDoc"
3) 1) "name"
   2) "\"John Smith\""

Projecting using JSON Path expressions

The RETURN parameter also accepts a JSON Path expression which lets you extract any part of the JSON document.

The following example returns the result of the JSON Path expression $.user.hp.

FT.SEARCH userIdx '@name:(John)' RETURN 1 $.user.hp
1) (integer) 1
2) "myDoc"
3) 1) "$.user.hp"
   2) "1000"

Note that the property name is the JSON expression itself: 3) 1) "$.user.hp"

Using the AS option, it is also possible to alias the returned property.

FT.SEARCH userIdx '@name:(John)' RETURN 3 $.user.hp AS hitpoints
1) (integer) 1
2) "myDoc"
3) 1) "hitpoints"
   2) "1000"

Highlighting

You can highlight any attribute as soon as it is indexed using the TEXT type.

For FT.SEARCH, you have to explicitly set the attributes in the RETURN parameter and the HIGHLIGHT parameters.

FT.SEARCH userIdx '@name:(John)' RETURN 1 name HIGHLIGHT FIELDS 1 name TAGS '<b>' '</b>'
1) (integer) 1
2) "myDoc"
3) 1) "name"
   2) "\"<b>John</b> Smith\""

Aggregation with JSON Path expression

Aggregation is a powerful feature. You can use it to generate statistics or build facet queries. The LOAD parameter accepts JSON Path expressions. Any value (even not indexed) can be used in the pipeline.

This example loads two numeric values from the JSON document applying a simple operation.

FT.AGGREGATE userIdx '*' LOAD 6 $.user.hp AS hp $.user.dmg AS dmg APPLY '@hp-@dmg' AS points
1) (integer) 1
2) 1) "hp"
   2) "1000"
   3) "dmg"
   4) "150"
   5) "points"
   6) "850"

Current indexing limitations

JSON arrays can only be indexed in TAG identifiers.

It is only possible to index an array of strings or booleans in a TAG identifier. Other types (numeric, geo, null) are not supported.

It is not possible to index JSON objects.

To be indexed, a JSONPath expression must return a single scalar value (string or number).

If the JSONPath expression returns an object, it will be ignored.

However it is possible to index the strings in separated attributes.

Given the following document:

{
  "name": "Headquarters",
  "address": [
    "Suite 250",
    "Mountain View"
  ],
  "cp": "CA 94040"
}

Before you can index the array under the address key, you have to create two fields:

FT.CREATE orgIdx ON JSON SCHEMA $.address[0] AS a1 TEXT $.address[1] AS a2 TEXT
OK

You can now index the document:

JSON.SET org:1 $ '{"name": "Headquarters","address": ["Suite 250","Mountain View"],"cp": "CA 94040"}'
OK

You can now search in the address:

FT.SEARCH orgIdx "suite 250"
1) (integer) 1
2) "org:1"
3) 1) "$"
   2) "{\"name\":\"Headquarters\",\"address\":[\"Suite 250\",\"Mountain View\"],\"cp\":\"CA 94040\"}"

Index JSON strings and numbers as TEXT and NUMERIC

  • You can only index JSON strings as TEXT, TAG, or GEO (using the correct syntax).
  • You can only index JSON numbers as NUMERIC.
  • JSON booleans can only be indexed as TAG.
  • NULL values are ignored.

SORTABLE is not supported on TAG

FT.CREATE orgIdx ON JSON SCHEMA $.cp[0] AS cp TAG SORTABLE
(error) On JSON, cannot set tag field to sortable - cp

With hashes, you can use SORTABLE (as a side effect) to improve the performance of FT.AGGREGATE on TAGs. This is possible because the value in the hash is a string, such as "foo,bar".

With JSON, you can index an array of strings. Because there is no valid single textual representation of those values, there is no way for RediSearch to know how to sort the result.

3 - Path

RedisJSON JSONPath

Since no standard for path syntax exists, RedisJSON implements its own. RedisJSON's syntax is based on common best practices and intentionally resembles JSONPath.

RedisJSON currently supports two query syntaxes: JSONPath syntax and a legacy path syntax from the first version of RedisJSON.

RedisJSON decides which syntax to use depending on the first character of the path query. If the query starts with the character $, it uses JSONPath syntax. Otherwise, it defaults to legacy path syntax.

JSONPath support (RedisJSON v2)

RedisJSON 2.0 introduces JSONPath support. It follows the syntax described by Goessner in his article.

A JSONPath query can resolve to several locations in the JSON documents. In this case, the JSON commands apply the operation to every possible location. This is a major improvement over the legacy query, which only operates on the first path.

Notice that the structure of the command response often differs when using JSONPath. See the Commands page for more details.

The new syntax supports bracket notation, which allows the use of special characters like colon ":" or whitespace in key names.

Legacy Path syntax (RedisJSON v1)

The first version of RedisJSON had the following implementation. It is still supported in RedisJSON v2.

Paths always begin at the root of a RedisJSON value. The root is denoted by a period character (.). For paths that reference the root's children, it is optional to prefix the path with the root.

RedisJSON supports both dot notation and bracket notation for object key access. The following paths all refer to bar, which is a child of foo under the root:

  • .foo.bar
  • foo["bar"]
  • ['foo']["bar"]

To access an array element, enclose its index within a pair of square brackets. The index is 0-based, with 0 being the first element of the array, 1 being the next element, and so on. You can use negative offsets to access elements starting from the end of the array. For example, -1 is the last element in the array, -2 is the second to last element, and so on.

JSON key names and path compatibility

By definition, a JSON key can be any valid JSON string. Paths, on the other hand, are traditionally based on JavaScript's (and Java's) variable naming conventions. Therefore, while it is possible to have RedisJSON store objects containing arbitrary key names, you can only access these keys via a path if they conform to these naming syntax rules:

  1. Names must begin with a letter, a dollar sign ($), or an underscore (_) character
  2. Names can contain letters, digits, dollar signs, and underscores
  3. Names are case-sensitive

Time complexity of path evaluation

The time complexity of searching (navigating to) an element in the path is calculated from:

  1. Child level - every level along the path adds an additional search
  2. Key search - O(N), where N is the number of keys in the parent object
  3. Array search - O(1)

This means that the overall time complexity of searching a path is O(N*M), where N is the depth and M is the number of parent object keys.

while this is acceptable for objects where N is small, access can be optimized for larger objects. This optimization is planned for a future version.

4 - Client Libraries

List of RedisJSON client libraries

RedisJSON has several client libraries, written by the module authors and community members - abstracting the API in different programming languages.

While it is possible and simple to use the raw Redis commands API, in most cases it's easier to just use a client library abstracting it.

Currently available Libraries

ProjectLanguageLicenseAuthorStarsPackage
node-redisNode.jsMITRedisnode-redis-starsnpm
iorejsonNode.jsMITEvan Huang @evanhuang8iorejson-starsnpm
redis-om-nodeNodeBSD-3-ClauseRedisredis-om-node-starsnpm
node_redis-rejsonNode.jsMITKyle Davis @stockholmuxnode_redis-rejson-starsnpm
redis-modules-sdkNode.jsBSD-3-ClauseDani Tseitlin @danitseitlinredis-modules-sdk-starsnpm
JedisJavaMITRedisJedis-starsmaven
JRedisJSONJavaBSD-2-ClauseRedisJRedisJSON-starsmaven
redis-modules-javaJavaApache-2.0Liming Deng @denglimingredis-modules-java-starsmaven
redis-pyPythonMITRedisredis-py-starspypi
redis-om-springJavaBSD-3-ClauseRedisredis-om-spring-stars
redis-om-pythonPythonBSD-3-ClauseRedisredis-om-python-starsPyPi
go-rejsonGoMITNitish Malhotra @nitishmgo-rejson-stars
rejonsonGoApache-2.0Daniel Krom @KromDanielrejonson-stars
rueidisGoApache-2.0Rueian @rueianrueidis-stars
NReJSON.NETMIT/Apache-2.0Tommy Hanks @tombatronNReJSON-starsnuget
redis-om-dotnet.NETBSD-3-ClauseRedisredis-om-dotnet-starsnuget
phpredis-jsonPHPMITRafa Campoy @averiasphpredis-json-starscomposer
redislabs-rejsonPHPMITMehmet Korkmaz @mkorkmazredislabs-rejson-starscomposer
rejson-rbRubyMITPavan Vachhani @vachhanihpavanrejson-rb-starsrubygems

5 - Performance

Performance benchmarks

To get an early sense of what RedisJSON is capable of, you can test it with redis-benchmark just like any other Redis command. However, in order to have more control over the tests, we'll use a a tool written in Go called ReJSONBenchmark that we expect to release in the near future.

The following figures were obtained from an AWS EC2 c4.8xlarge instance that ran both the Redis server as well the as the benchmarking tool. Connections to the server are via the networking stack. All tests are non-pipelined.

NOTE: The results below are measured using the preview version of RedisJSON, which is still very much unoptimized.

RedisJSON baseline

A smallish object

We test a JSON value that, while purely synthetic, is interesting. The test subject is /tests/files/pass-100.json, who weighs in at 380 bytes and is nested. We first test SETting it, then GETting it using several different paths:

ReJSONBenchmark pass-100.json

ReJSONBenchmark pass-100.json percentiles

A bigger array

Moving on to bigger values, we use the 1.4 kB array in /tests/files/pass-jsonsl-1.json:

ReJSONBenchmark pass-jsonsl-1.json

ReJSONBenchmark pass-jsonsl-1.json percentiles

A largish object

More of the same to wrap up, now we'll take on a behemoth of no less than 3.5 kB as given by /tests/files/pass-json-parser-0000.json:

ReJSONBenchmark pass-json-parser-0000.json

ReJSONBenchmark pass-json-parser-0000.json percentiles

Number operations

Last but not least, some adding and multiplying:

ReJSONBenchmark number operations

ReJSONBenchmark number operations percentiles

Baseline

To establish a baseline, we'll use the Redis PING command. First, lets see what redis-benchmark reports:

~$ redis/src/redis-benchmark -n 1000000 ping
====== ping ======
  1000000 requests completed in 7.11 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

99.99% <= 1 milliseconds
100.00% <= 1 milliseconds
140587.66 requests per second

ReJSONBenchmark's concurrency is configurable, so we'll test a few settings to find a good one. Here are the results, which indicate that 16 workers yield the best throughput:

ReJSONBenchmark PING

ReJSONBenchmark PING percentiles

Note how our benchmarking tool does slightly worse in PINGing - producing only 116K ops, compared to redis-cli's 140K.

The empty string

Another RedisJSON benchmark is that of setting and getting an empty string - a value that's only two bytes long (i.e. ""). Granted, that's not very useful, but it teaches us something about the basic performance of the module:

ReJSONBenchmark empty string

ReJSONBenchmark empty string percentiles

Comparison vs. server-side Lua scripting

We compare RedisJSON's performance with Redis' embedded Lua engine. For this purpose, we use the Lua scripts at /benchmarks/lua. These scripts provide RedisJSON's GET and SET functionality on values stored in JSON or MessagePack formats. Each of the different operations (set root, get root, set path and get path) is executed with each "engine" on objects of varying sizes.

Setting and getting the root

Storing raw JSON performs best in this test, but that isn't really surprising as all it does is serve unprocessed strings. While you can and should use Redis for caching opaque data, and JSON "blobs" are just one example, this does not allow any updates other than these of the entire value.

A more meaningful comparison therefore is between RedisJSON and the MessagePack variant, since both process the incoming JSON value before actually storing it. While the rates and latencies of these two behave in a very similar way, the absolute measurements suggest that RedisJSON's performance may be further improved.

VS. Lua set root

VS. Lua set root latency

VS. Lua get root

VS. Lua get root latency

Setting and getting parts of objects

This test shows why RedisJSON exists. Not only does it outperform the Lua variants, it retains constant rates and latencies regardless the object's overall size. There's no magic here - RedisJSON keeps the value deserialized so that accessing parts of it is a relatively inexpensive operation. In deep contrast are both raw JSON as well as MessagePack, which require decoding the entire object before anything can be done with it (a process that becomes more expensive the larger the object is).

VS. Lua set path to scalar

VS. Lua set path to scalar latency

VS. Lua get scalar from path

VS. Lua get scalar from path latency

Even more charts

These charts are more of the same but independent for each file (value):

VS. Lua pass-100.json rate

VS. Lua pass-100.json average latency

VS. Lua pass-jsonsl-1.json rate

VS. Lua pass-jsonsl-1.json average latency

VS. Lua pass-json-parser-0000.json rate

VS. Lua pass-json-parser-0000.json latency

VS. Lua pass-jsonsl-yahoo2.json rate

VS. Lua pass-jsonsl-yahoo2.json latency

VS. Lua pass-jsonsl-yelp.json rate

VS. Lua pass-jsonsl-yelp.json latency

Raw results

The following are the raw results from the benchmark in CSV format.

RedisJSON results

title,concurrency,rate,average latency,50.00%-tile,90.00%-tile,95.00%-tile,99.00%-tile,99.50%-tile,100.00%-tile
[ping],1,22128.12,0.04,0.04,0.04,0.05,0.05,0.05,1.83
[ping],2,54641.13,0.04,0.03,0.05,0.05,0.06,0.07,2.14
[ping],4,76000.18,0.05,0.05,0.07,0.07,0.09,0.10,2.10
[ping],8,106750.99,0.07,0.07,0.10,0.11,0.14,0.16,2.99
[ping],12,111297.33,0.11,0.10,0.15,0.16,0.20,0.22,6.81
[ping],16,116292.19,0.14,0.13,0.19,0.21,0.27,0.33,7.50
[ping],20,110622.82,0.18,0.17,0.24,0.27,0.38,0.47,12.21
[ping],24,107468.51,0.22,0.20,0.31,0.38,0.58,0.71,13.86
[ping],28,102827.35,0.27,0.25,0.38,0.44,0.66,0.79,12.87
[ping],32,105733.51,0.30,0.28,0.42,0.50,0.79,0.97,10.56
[ping],36,102046.43,0.35,0.33,0.48,0.56,0.90,1.13,14.66
JSON.SET {key} . {empty string size: 2 B},16,80276.63,0.20,0.18,0.28,0.32,0.41,0.45,6.48
JSON.GET {key} .,16,92191.23,0.17,0.16,0.24,0.27,0.34,0.38,9.80
JSON.SET {key} . {pass-100.json size: 380 B},16,41512.77,0.38,0.35,0.50,0.62,0.81,0.86,9.56
JSON.GET {key} .,16,48374.10,0.33,0.29,0.47,0.56,0.72,0.79,9.36
JSON.GET {key} sclr,16,94801.23,0.17,0.15,0.24,0.27,0.35,0.39,13.21
JSON.SET {key} sclr 1,16,82032.08,0.19,0.18,0.27,0.31,0.40,0.44,8.97
JSON.GET {key} sub_doc,16,81633.51,0.19,0.18,0.27,0.32,0.43,0.49,9.88
JSON.GET {key} sub_doc.sclr,16,95052.35,0.17,0.15,0.24,0.27,0.35,0.39,7.39
JSON.GET {key} array_of_docs,16,68223.05,0.23,0.22,0.29,0.31,0.44,0.50,8.84
JSON.GET {key} array_of_docs[1],16,76390.57,0.21,0.19,0.30,0.34,0.44,0.49,9.99
JSON.GET {key} array_of_docs[1].sclr,16,90202.13,0.18,0.16,0.25,0.29,0.36,0.39,7.87
JSON.SET {key} . {pass-jsonsl-1.json size: 1.4 kB},16,16117.11,0.99,0.91,1.22,1.55,2.17,2.35,9.27
JSON.GET {key} .,16,15193.51,1.05,0.94,1.41,1.75,2.33,2.42,7.19
JSON.GET {key} [0],16,78198.90,0.20,0.19,0.29,0.33,0.42,0.47,10.87
"JSON.SET {key} [0] ""foo""",16,80156.90,0.20,0.18,0.28,0.32,0.40,0.44,12.03
JSON.GET {key} [7],16,99013.98,0.16,0.15,0.23,0.26,0.34,0.38,7.67
JSON.GET {key} [8].zero,16,90562.19,0.17,0.16,0.25,0.28,0.35,0.38,7.03
JSON.SET {key} . {pass-json-parser-0000.json size: 3.5 kB},16,14239.25,1.12,1.06,1.21,1.48,2.35,2.59,11.91
JSON.GET {key} .,16,8366.31,1.91,1.86,2.00,2.04,2.92,3.51,12.92
"JSON.GET {key} [""web-app""].servlet",16,9339.90,1.71,1.68,1.74,1.78,2.68,3.26,10.47
"JSON.GET {key} [""web-app""].servlet[0]",16,13374.88,1.19,1.07,1.54,1.95,2.69,2.82,12.15
"JSON.GET {key} [""web-app""].servlet[0][""servlet-name""]",16,81267.36,0.20,0.18,0.28,0.31,0.38,0.42,9.67
"JSON.SET {key} [""web-app""].servlet[0][""servlet-name""] ""bar""",16,79955.04,0.20,0.18,0.27,0.33,0.42,0.46,6.72
JSON.SET {key} . {pass-jsonsl.yahoo2-json size: 18 kB},16,3394.07,4.71,4.62,4.72,4.79,7.35,9.03,17.78
JSON.GET {key} .,16,891.46,17.92,17.33,17.56,20.12,31.77,42.87,66.64
JSON.SET {key} ResultSet.totalResultsAvailable 1,16,75513.03,0.21,0.19,0.30,0.34,0.42,0.46,9.21
JSON.GET {key} ResultSet.totalResultsAvailable,16,91202.84,0.17,0.16,0.24,0.28,0.35,0.38,5.30
JSON.SET {key} . {pass-jsonsl-yelp.json size: 40 kB},16,1624.86,9.84,9.67,9.86,9.94,15.86,19.36,31.94
JSON.GET {key} .,16,442.55,36.08,35.62,37.78,38.14,55.23,81.33,88.40
JSON.SET {key} message.code 1,16,77677.25,0.20,0.19,0.28,0.33,0.42,0.45,11.07
JSON.GET {key} message.code,16,89206.61,0.18,0.16,0.25,0.28,0.36,0.39,8.60
[JSON.SET num . 0],16,84498.21,0.19,0.17,0.26,0.30,0.39,0.43,8.08
[JSON.NUMINCRBY num . 1],16,78640.20,0.20,0.18,0.28,0.33,0.44,0.48,11.05
[JSON.NUMMULTBY num . 2],16,77170.85,0.21,0.19,0.28,0.33,0.43,0.47,6.85

Lua using cjson

json-set-root.lua empty string,16,86817.84,0.18,0.17,0.26,0.31,0.39,0.42,9.36
json-get-root.lua,16,90795.08,0.17,0.16,0.25,0.28,0.36,0.39,8.75
json-set-root.lua pass-100.json,16,84190.26,0.19,0.17,0.27,0.30,0.38,0.41,12.00
json-get-root.lua,16,87170.45,0.18,0.17,0.26,0.29,0.38,0.45,9.81
json-get-path.lua sclr,16,54556.80,0.29,0.28,0.35,0.38,0.57,0.64,7.53
json-set-path.lua sclr 1,16,35907.30,0.44,0.42,0.53,0.67,0.93,1.00,8.57
json-get-path.lua sub_doc,16,51158.84,0.31,0.30,0.36,0.39,0.50,0.62,7.22
json-get-path.lua sub_doc sclr,16,51054.47,0.31,0.29,0.39,0.47,0.66,0.74,7.43
json-get-path.lua array_of_docs,16,39103.77,0.41,0.37,0.57,0.68,0.87,0.94,8.02
json-get-path.lua array_of_docs 1,16,45811.31,0.35,0.32,0.45,0.56,0.77,0.83,8.17
json-get-path.lua array_of_docs 1 sclr,16,47346.83,0.34,0.31,0.44,0.54,0.72,0.79,8.07
json-set-root.lua pass-jsonsl-1.json,16,82100.90,0.19,0.18,0.28,0.31,0.39,0.43,12.43
json-get-root.lua,16,77922.14,0.20,0.18,0.30,0.34,0.66,0.86,8.71
json-get-path.lua 0,16,38162.83,0.42,0.40,0.49,0.59,0.88,0.96,6.16
"json-set-path.lua 0 ""foo""",16,21205.52,0.75,0.70,0.84,1.07,1.60,1.74,5.77
json-get-path.lua 7,16,37254.89,0.43,0.39,0.55,0.69,0.92,0.98,10.24
json-get-path.lua 8 zero,16,33772.43,0.47,0.43,0.63,0.77,1.01,1.09,7.89
json-set-root.lua pass-json-parser-0000.json,16,76314.18,0.21,0.19,0.29,0.33,0.41,0.44,8.16
json-get-root.lua,16,65177.87,0.24,0.21,0.35,0.42,0.89,1.01,9.02
json-get-path.lua web-app servlet,16,15938.62,1.00,0.88,1.45,1.71,2.11,2.20,8.07
json-get-path.lua web-app servlet 0,16,19469.27,0.82,0.78,0.90,1.07,1.67,1.84,7.59
json-get-path.lua web-app servlet 0 servlet-name,16,24694.26,0.65,0.63,0.71,0.74,1.07,1.31,8.60
"json-set-path.lua web-app servlet 0 servlet-name ""bar""",16,16555.74,0.96,0.92,1.05,1.25,1.98,2.20,9.08
json-set-root.lua pass-jsonsl-yahoo2.json,16,47544.65,0.33,0.31,0.41,0.47,0.59,0.64,10.52
json-get-root.lua,16,25369.92,0.63,0.57,0.91,1.05,1.37,1.56,9.95
json-set-path.lua ResultSet totalResultsAvailable 1,16,5077.32,3.15,3.09,3.20,3.24,5.12,6.26,14.98
json-get-path.lua ResultSet totalResultsAvailable,16,7652.56,2.09,2.05,2.13,2.17,3.23,3.95,9.65
json-set-root.lua pass-jsonsl-yelp.json,16,29575.20,0.54,0.52,0.64,0.75,0.94,1.00,12.66
json-get-root.lua,16,18424.29,0.87,0.84,1.25,1.40,1.82,1.95,7.35
json-set-path.lua message code 1,16,2251.07,7.10,6.98,7.14,7.22,11.00,12.79,21.14
json-get-path.lua message code,16,3380.72,4.73,4.44,5.03,6.82,10.28,11.06,14.93

Lua using cmsgpack

msgpack-set-root.lua empty string,16,82592.66,0.19,0.18,0.27,0.31,0.38,0.42,10.18
msgpack-get-root.lua,16,89561.41,0.18,0.16,0.25,0.29,0.37,0.40,9.52
msgpack-set-root.lua pass-100.json,16,44326.47,0.36,0.34,0.43,0.54,0.78,0.86,6.45
msgpack-get-root.lua,16,41036.58,0.39,0.36,0.51,0.62,0.84,0.91,7.21
msgpack-get-path.lua sclr,16,55845.56,0.28,0.26,0.36,0.44,0.64,0.70,11.29
msgpack-set-path.lua sclr 1,16,43608.26,0.37,0.34,0.47,0.58,0.78,0.85,10.27
msgpack-get-path.lua sub_doc,16,50153.07,0.32,0.29,0.41,0.50,0.69,0.75,8.56
msgpack-get-path.lua sub_doc sclr,16,54016.35,0.29,0.27,0.38,0.46,0.62,0.67,6.38
msgpack-get-path.lua array_of_docs,16,45394.79,0.35,0.32,0.45,0.56,0.78,0.85,11.88
msgpack-get-path.lua array_of_docs 1,16,48336.48,0.33,0.30,0.42,0.52,0.71,0.76,7.69
msgpack-get-path.lua array_of_docs 1 sclr,16,53689.41,0.30,0.27,0.38,0.46,0.64,0.69,11.16
msgpack-set-root.lua pass-jsonsl-1.json,16,28956.94,0.55,0.51,0.65,0.82,1.17,1.26,8.39
msgpack-get-root.lua,16,26045.44,0.61,0.58,0.68,0.83,1.28,1.42,8.56
"msgpack-set-path.lua 0 ""foo""",16,29813.56,0.53,0.49,0.67,0.83,1.15,1.22,6.82
msgpack-get-path.lua 0,16,44827.58,0.36,0.32,0.48,0.58,0.76,0.81,9.19
msgpack-get-path.lua 7,16,47529.14,0.33,0.31,0.42,0.53,0.73,0.79,7.47
msgpack-get-path.lua 8 zero,16,44442.72,0.36,0.33,0.45,0.56,0.77,0.85,8.11
msgpack-set-root.lua pass-json-parser-0000.json,16,19585.82,0.81,0.78,0.85,1.05,1.66,1.86,4.33
msgpack-get-root.lua,16,19014.08,0.84,0.73,1.23,1.45,1.76,1.84,13.52
msgpack-get-path.lua web-app servlet,16,18992.61,0.84,0.73,1.23,1.45,1.75,1.82,8.19
msgpack-get-path.lua web-app servlet 0,16,24328.78,0.66,0.64,0.73,0.77,1.15,1.34,8.81
msgpack-get-path.lua web-app servlet 0 servlet-name,16,31012.81,0.51,0.49,0.57,0.65,1.02,1.13,8.11
"msgpack-set-path.lua web-app servlet 0 servlet-name ""bar""",16,20388.54,0.78,0.73,0.88,1.08,1.63,1.78,7.22
msgpack-set-root.lua pass-jsonsl-yahoo2.json,16,5597.60,2.85,2.81,2.89,2.94,4.57,5.59,10.19
msgpack-get-root.lua,16,6585.01,2.43,2.39,2.52,2.66,3.76,4.80,10.59
msgpack-set-path.lua ResultSet totalResultsAvailable 1,16,6666.95,2.40,2.35,2.43,2.47,3.78,4.59,12.08
msgpack-get-path.lua ResultSet totalResultsAvailable,16,10733.03,1.49,1.45,1.60,1.66,2.36,2.93,13.15
msgpack-set-root-lua pass-jsonsl-yelp.json,16,2291.53,6.97,6.87,7.01,7.12,10.54,12.89,21.75
msgpack-get-root.lua,16,2889.59,5.53,5.45,5.71,5.86,8.80,10.48,25.55
msgpack-set-path.lua message code 1,16,2847.85,5.61,5.44,5.56,6.01,10.58,11.90,16.91
msgpack-get-path.lua message code,16,5030.95,3.18,3.07,3.24,3.57,6.08,6.92,12.44

6 - RedisJSON RAM Usage

Debugging memory consumption

Every key in Redis takes memory and requires at least the amount of RAM to store the key name, as well as some per-key overhead that Redis uses. On top of that, the value in the key also requires RAM.

RedisJSON stores JSON values as binary data after deserializing them. This representation is often more expensive, size-wize, than the serialized form. The RedisJSON data type uses at least 24 bytes (on 64-bit architectures) for every value, as can be seen by sampling an empty string with the [JSON.DEBUG MEMORY](/commands/json.debug-memory/) command:

127.0.0.1:6379> JSON.SET emptystring . '""'
OK
127.0.0.1:6379> JSON.DEBUG MEMORY emptystring
(integer) 24

This RAM requirement is the same for all scalar values, but strings require additional space depending on their actual length. For example, a 3-character string will use 3 additional bytes:

127.0.0.1:6379> JSON.SET foo . '"bar"'
OK
127.0.0.1:6379> JSON.DEBUG MEMORY foo
(integer) 27

Empty containers take up 32 bytes to set up:

127.0.0.1:6379> JSON.SET arr . '[]'
OK
127.0.0.1:6379> JSON.DEBUG MEMORY arr
(integer) 32
127.0.0.1:6379> JSON.SET obj . '{}'
OK
127.0.0.1:6379> JSON.DEBUG MEMORY obj
(integer) 32

The actual size of a container is the sum of sizes of all items in it on top of its own overhead. To avoid expensive memory reallocations, containers' capacity is scaled by multiples of 2 until a treshold size is reached, from which they grow by fixed chunks.

A container with a single scalar is made up of 32 and 24 bytes, respectively:

127.0.0.1:6379> JSON.SET arr . '[""]'
OK
127.0.0.1:6379> JSON.DEBUG MEMORY arr
(integer) 56

A container with two scalars requires 40 bytes for the container (each pointer to an entry in the container is 8 bytes), and 2 * 24 bytes for the values themselves:

127.0.0.1:6379> JSON.SET arr . '["", ""]'
OK
127.0.0.1:6379> JSON.DEBUG MEMORY arr
(integer) 88

A 3-item (each 24 bytes) container will be allocated with capacity for 4 items, i.e. 56 bytes:

127.0.0.1:6379> JSON.SET arr . '["", "", ""]'
OK
127.0.0.1:6379> JSON.DEBUG MEMORY arr
(integer) 128

The next item will not require an allocation in the container, so usage will increase only by that scalar's requirement, but another value will scale the container again:

127.0.0.1:6379> JSON.SET arr . '["", "", "", ""]'
OK
127.0.0.1:6379> JSON.DEBUG MEMORY arr
(integer) 152
127.0.0.1:6379> JSON.SET arr . '["", "", "", "", ""]'
OK
127.0.0.1:6379> JSON.DEBUG MEMORY arr
(integer) 208

This table gives the size (in bytes) of a few of the test files on disk and when stored using RedisJSON. The MessagePack column is for reference purposes and reflects the length of the value when stored using MessagePack.

FileFilesizeRedisJSONMessagePack
/tests/files/pass-100.json3801079140
/tests/files/pass-jsonsl-1.json14413666753
/tests/files/pass-json-parser-0000.json346872092393
/tests/files/pass-jsonsl-yahoo2.json184463746916869
/tests/files/pass-jsonsl-yelp.json394917534135469

Note: In the current version, deleting values from containers does not free the container's allocated memory.

7 - Developer notes

Notes on debugging, testing and documentation

Debugging

Compile after settting the environment variable DEBUG, e.g. export DEBUG=1, to include the debugging information.

Testing

Python is required for RedisJSON's module test. Install it with apt-get install python. You'll also need to have redis-py installed. The easiest way to get it is using pip and running pip install redis.

The module's test can be run against an "embedded" disposable Redis instance, or against an instance you provide to it. The "embedded" mode requires having the redis-server executable in your PATH. To run the tests, run the following in the project's directory:

$ # use a disposable Redis instance for testing the module
$ make test

You can override the spawning of the embedded server by specifying a Redis port via the REDIS_PORT environment variable, e.g.:

$ # use an existing local Redis instance for testing the module
$ REDIS_PORT=6379 make test