Skip to content

Commit cc56a97

Browse files
author
Simon Prickett
authored
Merge pull request #6 from simonprickett/introduce-search
Replaces `SCAN` approach with the Search capability in Redis Stack
2 parents 1e69296 + 75fe71c commit cc56a97

File tree

5 files changed

+148
-105
lines changed

5 files changed

+148
-105
lines changed

README.md

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
## Overview
44

5-
This repository contains an example application that demonstrates how to capture images from a Raspberry Pi using the camera module, store them in [Redis Hashes](https://redis.io/docs/data-types/hashes/) and render them in a web application.
5+
This repository contains an example application that demonstrates how to capture images from a Raspberry Pi using the camera module, store them in [Redis Hashes](https://redis.io/docs/data-types/hashes/) and render them in a web application.
6+
7+
The project started by using an older Raspberry Pi camera module and the Redis `SCAN` command to retrieve all of the data and present it in the front end. [Release v0.1](https://github.com/simonprickett/redis-pi-camera/releases/tag/v0.0.1) has that code.
8+
9+
I enhanced the code to use the newer Raspberry Pi camera module with autofocus and also added configurable image expiry time in Redis and capture of Lux values from the camera. Check out [release v0.2](https://github.com/simonprickett/redis-pi-camera/releases/tag/v0.0.2) for that.
10+
11+
In my second live stream for this project, I replaced the `SCAN` command with the [Search capability of Redis Stack](https://redis.io/docs/stack/search/). [Release 0.3](https://github.com/simonprickett/redis-pi-camera/releases/tag/v0.0.3) contains the code from that stream.
612

713
Here's what the front end looks like when a few images have been captured by the Raspberry Pi...
814

@@ -12,9 +18,13 @@ And here's a Raspberry Pi with a camera attached:
1218

1319
![Raspberry Pi 3 with Camera Module attached](raspberry_pi_3_with_camera_module.jpg)
1420

15-
## Watch the Video
21+
## Watch the Videos
22+
23+
Watch the first video for a full walkthrough of this project. [Watch on YouTube](https://www.youtube.com/watch?v=OTDZIK55DX0).
1624

17-
Watch the video for a full walkthrough of this project. [Watch on YouTube](https://www.youtube.com/watch?v=OTDZIK55DX0).
25+
In the second episode I started to use Redis Stack's Search capability. [Watch on YouTube](https://www.youtube.com/watch?v=mcGL6Lk2IXU).
26+
27+
I'm planning a third episode once I can figure out what to do in it :)
1828

1929
## Components of the Demo
2030

@@ -27,9 +37,9 @@ Details of how each component works including how to configure and run it can be
2737

2838
## Redis
2939

30-
Both components need to be connected to the same Redis instance to talk to each other. Right now, this demo works on any Redis 5 or higher server. The enclosed Docker Compose file uses Redis Stack - this is to allow for future enhancements to the application to use Stack's Search capability.
40+
Both components need to be connected to the same Redis Stack instance to talk to each other.
3141

32-
If you want to use Docker, start Redis like this:
42+
If you want to use Docker, start Redis Stack like this:
3343

3444
```
3545
docker-compose up -d
@@ -53,9 +63,11 @@ See the RedisInsight section of this document if you're interested in a graphica
5363

5464
You'll need to make sure that both components of the application can connect to your Redis instance. See details in each component's README.
5565

56-
This project will also work with a free Redis Stack cloud instance from Redis (the company). To use this, [sign up here](https://redis.com/try-free/) and make a note of your Redis host, port and password. You'll need those to configure each component.
66+
This project will also work with a free Redis Stack cloud instance from Redis (the company). To use this, [sign up here](https://redis.com/try-free/) and make a note of your Redis host, port and password. You'll need those to configure each component. When using the free cloud instance, note that you get 30Mb space which will fill up with images quickly. You can manage this by setting a shorter image expiry time in the capture component's configuration.
67+
68+
## The Redis Data Model, Key Naming Strategy and Indexing
5769

58-
## The Redis Data Model and Key Naming Strategy
70+
### Data Type and Key Naming Strategy
5971

6072
The application stores each image plus associated metadata in its own [Redis Hash](https://redis.io/docs/data-types/hashes/). A Hash in Redis can be thought of as a flat map of name/value properties. Each Hash is stored in its own key in Redis.
6173

@@ -85,6 +97,77 @@ Here's a complete example, with the image data truncated for brevity:
8597
```
8698
With the camera that I used ([Raspberry Pi Camera Module 3](https://www.raspberrypi.com/products/camera-module-3/) capturing at 4608x2592 pixels - configurable in `capture.py`) you can expect each Hash to require around 1Mb of RAM in Redis.
8799

100+
### Indexing and Querying
101+
102+
As we saw in the initial live stream video, using `SCAN` allows us to retrieve all of the keys containing image data for display in the front end. This has a couple of problems:
103+
104+
* We can't do any meaningful filtering or searching on the server side in Redis.
105+
* `SCAN` is effectively a O(n) time complexity command, so the bigger the dataset the longer it's going to take / the more load it will put on the Redis server.
106+
107+
The Search capability of Redis Stack addresses both of these and gives us a flexible way to index and query our data. To use it, we first have to create and index with the [`FT.CREATE` command](https://redis.io/commands/ft.create/). Once created, Redis Stack will monitor changes to keys that match the index criteria and update the index automatically. We can then write rich queries using the `FT.SEARCH` command.
108+
109+
Here's our index creation command - you'll need to paste this into Redis CLI or RedisInsight and execute it before running the front end:
110+
111+
```
112+
FT.CREATE idx:images ON HASH PREFIX 1 image: SCHEMA mime_type TAG lux NUMERIC SORTABLE timestamp NUMERIC SORTABLE
113+
```
114+
115+
This command creates an index on keys in Redis whose key names begin `image:` and which are of type `HASH`. Where found, the values of fields named `timestamp` and `lux` are indexed as numeric values and the values of fields named `mime_type` are indexed as tags (exact match string values).
116+
117+
Here are some example queries that we can run against this index.
118+
119+
Find the 9 most recent images (most recent first), returning their timestamp, MIME type and lux values:
120+
121+
```
122+
FT.SEARCH idx:images "*" RETURN 3 timestamp mime_type lux SORTBY timestamp DESC LIMIT 0 9
123+
```
124+
125+
Truncated example response:
126+
127+
```
128+
1) "12"
129+
2) "image:1684427475"
130+
3) 1) "timestamp"
131+
2) "1684427475"
132+
3) "mime_type"
133+
4) "image/jpeg"
134+
5) "lux"
135+
6) "85"
136+
4) "image:1684427251"
137+
5) 1) "timestamp"
138+
2) "1684427251"
139+
3) "mime_type"
140+
4) "image/jpeg"
141+
5) "lux"
142+
6) "104"
143+
6) "image:1684427190"
144+
7) 1) "timestamp"
145+
2) "1684427190"
146+
3) "mime_type"
147+
4) "image/jpeg"
148+
5) "lux"
149+
6) "109"
150+
8) "image:1684427130"
151+
9) 1) "timestamp"
152+
2) "1684427130"
153+
3) "mime_type"
154+
4) "image/jpeg"
155+
5) "lux"
156+
6) "93"
157+
...
158+
```
159+
160+
Find the 9 most recent images with lux value between 100 and 120, again with the most recent image first:
161+
162+
```
163+
FT.SEARCH idx:images "@lux:[100 120]" RETURN 3 timestamp mime_type lux SORTBY timestamp DESC LIMIT 0 9
164+
```
165+
166+
For more information on how to write search queries, check out:
167+
168+
* [Search query syntax documentation](https://redis.io/docs/stack/search/reference/query_syntax/).
169+
* [RU203 - Querying, Indexing and Full-Text Search](https://university.redis.com/courses/ru203/): A free course at Redis University.
170+
88171
## (Optional, but Recommended): RedisInsight
89172

90173
RedisInsight is a free graphical management and database browsing tool for Redis. You don't need it to look at how the application stores data in Redis (you can use redis-cli if you prefer) but I'd recommend it as it's much easier to get an overall picture of the state of the database with a graphical tool. RedisInsight runs as a desktop application on your Mac, Windows or Linux machine.

pi/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ data_to_save["lux"] = int(image_metadata["Lux"])
8383

8484
First, we create the key name we're going to use when storing the Hash. It's `image:<timestamp>`.
8585

86-
`data_to_save` is a Python dictionary containing the name/value pairs to store in the Redis Hash. This needs to be a flat map of name/value pairs - nested structure isn't allowed in a Redis Hash. If you want more complex data structure, use the [Redis JSON data type](https://redis.io/docs/stack/json/) in Redis Stack.
86+
`data_to_save` is a Python dictionary containing the name/value pairs to store in the Redis Hash. This needs to be a flat map of name/value pairs - nested structure isn't allowed in a Redis Hash. If you want to model a more complex data structure, use the [JSON data type](https://redis.io/docs/stack/json/) in Redis Stack.
8787

8888
Hashes in Redis are schemaless, so if you add extra fields there's no need to change any database schema (if you're looking for one, it doesn't exist!). You'll just need to modify any application code that reads the Hashes to use new fields.
8989

9090
We store the bytes of the image, the timestamp and the MIME or media type of the image... so that any front end knows what encoding the data in `image_data` is in.
9191

92-
Saving the Hash to Redis is then simply a matter of running the [`HSET` command](https://redis.io/commands/hset/), passing it the key name and dict of name/value pairs to store. When saving this fata, we also want to set an expiry time for it which we do with the Redis [`EXPIRE` command](https://redis.io/commands/expire/). The time to live for each hash is a configurable number of seconds, read from the `IMAGE_EXPIRY` environment variable (see later for details).
92+
Saving the Hash to Redis is then simply a matter of running the [`HSET` command](https://redis.io/commands/hset/), passing it the key name and dict of name/value pairs to store. When saving this data, we also want to set an expiry time for it which we do with the Redis [`EXPIRE` command](https://redis.io/commands/expire/). The time to live for each Hash is a configurable number of seconds, read from the `IMAGE_EXPIRY` environment variable (see later for details).
9393

9494
This means that we want to send two commands to Redis. To save on network bandwidth, let's use a feature of the Redis protocol called a [pipeline](https://redis.io/docs/manual/pipelining/) and send both in the same network round trip:
9595

@@ -100,7 +100,7 @@ pipe.expire(redis_key, IMAGE_EXPIRY)
100100
pipe.execute()
101101
```
102102

103-
This sets up the `HSET` and `EXPIRE` commands in a pipeline, which is then sent to Redis using the `execute` function.
103+
This sets up the `HSET` and `EXPIRE` commands in a pipeline, which is then sent to Redis using the `execute` function. We don't need the results returned from Redis in this instance, but if we did then we can access them as a List returned by `execute`.
104104

105105
## Setup
106106

0 commit comments

Comments
 (0)