Redis Databases Wrapped in Sheets
Redis has relicensed away from open source!
The original BSD-3-Clause still applies to version 7.2.4
.
This demo has been tested with KeyDB and other servers that support the "Redis serialization protocol" (RESP).
KeyDB is a Redis-compatible in-memory data store. It is capable of storing sets, lists and other simple data structures.
SheetJS is a JavaScript library for reading and writing data from spreadsheets.
This demo defines a schema for storing Redis databases in spreadsheets. We'll explore how to use SheetJS and Redis NodeJS connector modules to pull data from XLSX files to a Redis database and to serialize a database to a workbook.
This demo was tested in the following environments:
Server | Connector Module | Date |
---|---|---|
KeyDB 6.3.4 | redis (4.6.13 ) | 2024-04-23 |
Redis 6.2.14 | redis (4.6.13 ) | 2024-04-23 |
Redis 7.2.4 | redis (4.6.13 ) | 2024-04-23 |
Valkey 7.2.5 | redis (4.6.13 ) | 2024-04-23 |
Overview​
Redis has 5 core data types: "String", List", "Set", "Sorted Set", and "Hash". Since the keys and values are limited to simple strings (and numbers), it is possible to store complete databases in a single worksheet.
Integration Details​
SheetJSRedis.mjs
exports the methods:
redis_to_ws
creates a SheetJS worksheet by querying a redis clientws_to_redis
creates an array of query objects from the SheetJS worksheet
Array of Arrays​
The shared data representation is an "array of arrays"1. Each array within the structure corresponds to one row.
The Redis to SheetJS converter generates an array of arrays of the data by
running queries to fetch data from the database. The SheetJS aoa_to_sheet
and
sheet_add_aoa
2 methods build up worksheets from arrays of arrays. Once the
worksheet is created, it can be added to a SheetJS workbook object3 and
exported using writeFile
4.
The SheetJS to Redis converter works in reverse. Workbook files are parsed with
the SheetJS readFile
method5 and the desired worksheet is pulled from the
workbook object. An array of arrays can be created with the sheet_to_json
6
utility function. The data structure can be scanned to generate Redis queries.
Appending Columns​
Since the data is column-oriented, the goal is to add the data starting on the first row of the column after the data.
To calculate the starting point for writing data, SheetJS provides encode_cell
and decode_range
utility functions for working with addresses and ranges7.
The following snippet takes an array of arrays of values and writes the values to a worksheet starting from the column after the worksheet range:
function add_aoa_to_next_column(worksheet, aoa) {
/* get range of worksheet */
const range = XLSX.utils.decode_range(worksheet["!ref"])
/* the origin to write new data will start in the column after the range */
const origin = XLSX.utils.encode_cell({
r: 0, // start on first row
c: range.e.c + 1 // column after end
});
/* add data */
XLSX.utils.sheet_add_aoa(worksheet, aoa, { origin });
}
Strings​
Strings can be stored in a unified String table. The first column holds keys and the second column holds values:
XXX| A | B |
---+---------+-------+
1 | Strings | |
2 | | |
3 | Hello | World |
4 | Sheet | JS |
The SheetJS array-of-arrays representation of the string table is an array of key/value pairs.
The pairs can be generated from Redis by querying for all of the keys using the
KEYS
8 method, testing if the corresponding value is a string using the
TYPE
9 method, and fetching string values using the GET
10 method:
const aoa = ["Strings"]; aoa.length = 2; // [ "Strings", empty ]
const keys = await client.KEYS("*");
for(let key of keys) {
const type = await client.TYPE(key);
if(type == "string") aoa.push([key, await client.GET(key)]);
}
Lists​
Lists are unidimensional and can be stored in their own columns.
XXX| C |
---+---------+
1 | List |
2 | List1 |
3 | List1V1 |
4 | List1V2 |
The SheetJS array-of-arrays representation of lists is a column of values.
Redis LRANGE
11 returns a simple array of values. sheet_add_aoa
interprets
the result as one row, so the data should be transposed.
The code transposes the result with values.map(v => [v])
.
const values = await client.LRANGE(key, 0, -1);
const aoa = [ ["List"], [key] ].concat(values.map(v => [v]));
Sets​
Sets are unidimensional and can be stored in their own columns.
XXX| D |
---+-------+
1 | Set |
2 | Set1 |
3 | Set1A |
4 | Set1B |
The SheetJS array-of-arrays representation of sets is a column of values.
Redis SMEMBERS
12 returns an array of values. sheet_add_aoa
interprets the
result as one row, so the data should be transposed.
The code transposes the result with values.map(v => [v])
.
const values = await client.SMEMBERS(key);
const aoa = [ ["Set"], [key] ].concat(values.map(v => [v]));
Sorted Sets​
Sorted Sets have an associated score which can be stored in the second column.
XXX| E | F |
---+---------+---+
1 | Sorted | |
2 | ZSet1 | |
3 | Key1 | 1 |
4 | Key2 | 2 |
The SheetJS array-of-arrays representation is an array of key/score pairs.
ZRANGE_WITHSCORES
13 returns an array of objects which can be reshaped.
const values = await client.ZRANGE_WITHSCORES(key, 0, -1);
const aoa = [ ["Sorted"], [key] ].concat(values.map(v => [v.value, v.score]));
Hashes​
Hashes are stored like the string table, with key and value columns in order.
XXX| G | H |
---+-------+-------+
1 | Hash | |
2 | Hash1 | |
3 | Key1 | Val1 |
4 | Key2 | Val2 |
The SheetJS array-of-arrays representation is an array of key/value pairs.
HGETALL
14 returns an object which can be converted using Object.entries
:
const values = await client.HGETALL(key);
const aoa = [ ["Hash"], [key] ].concat(Object.entries(values));
Complete Example​
The most recent version of the redis
node module does not work with most
versions of NodeJS. It is "ESM-only", requiring NodeJS 18 or later. As a result,
this demo also requires NodeJS version 18 or later.
- Set up and start a local Redis-compatible server.
Redis-compatible servers (click to show)
This demo was last tested on macOS.
KeyDB
KeyDB was installed with:
brew install keydb@6.3.4
The following command started the server process:
keydb-server --protected-mode no
Valkey
Valkey was installed with:
brew install valkey
The Valkey formula will create symlinks to redis-server
and other commands.
This conflicts with the main redis
package. redis
must be unlinked:
brew unlink redis
brew link valkey
The following command started the server process:
redis-server /usr/local/etc/redis.conf
Redis 6
Redis 6 was installed with:
brew install redis@6.2
The following command started the server process:
redis-server /usr/local/etc/redis.conf
Redis 7
Redis 7 was installed with:
brew install redis@7.2
The following command started the server process:
redis-server /usr/local/etc/redis.conf
When the demo was last tested, Redis 7.2.4 was installed. The output from the Redis server will display the version number:
69385:C 23 Apr 2024 13:24:48.520 * Redis version=7.2.4, bits=64, commit=00000000, modified=0, pid=69385, just started
Please raise an issue with Homebrew if a later version is installed.
- Download the following scripts:
curl -LO https://docs.sheetjs.com/nosql/SheetJSRedis.mjs
curl -LO https://docs.sheetjs.com/nosql/SheetJSRedisTest.mjs
- Install dependencies:
npm i --save https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz redis@4.6.13
- Run the test script:
node SheetJSRedisTest.mjs
Testing
Inspect the output and compare with the data in SheetJSRedisTest.mjs
.
Expected Output (click to show)
SET [ 'baz', '0' ]
SET [ 'foo', 'bar' ]
HSET [
'user:1000',
{
name: 'John Smith',
email: 'john.smith@example.com',
password: 's3cret',
visits: '1'
}
]
HSET [
'user:1001',
{
name: 'Mary Jones',
email: 'mjones@example.com',
password: 'hunter2'
}
]
SADD [ 'superpowers', [ 'flight', 'x-ray vision' ] ]
SADD [ 'birdpowers', [ 'flight', 'pecking' ] ]
RPUSH [ 'friends', [ 'sam', 'alice', 'bob' ] ]
ZADD [
'hackers',
[
{ value: 'Grace Hopper', score: 1906 },
{ value: 'Alan Turing', score: 1912 },
{ value: 'Claude Shannon', score: 1916 },
{ value: 'Alan Kay', score: 1940 },
{ value: 'Richard Stallman', score: 1953 },
{ value: 'Sophie Wilson', score: 1957 },
{ value: 'Yukihiro Matsumoto', score: 1965 },
{ value: 'Linus Torvalds', score: 1969 }
]
]
Open SheetJSRedis.xlsx
and verify the columns have the correct data. The sheet
should have the same structure as the screenshot at the top of this page.