Skip to content

Commit 4e895ca

Browse files
committed
Initial version of the RFC: OBSERVE command for enhanced observability in Valkey
Note: I am going to add more specs and data soon. Signed-off-by: Mateusz Warzyński <[email protected]>
1 parent c2e2cb4 commit 4e895ca

File tree

1 file changed

+282
-0
lines changed

1 file changed

+282
-0
lines changed

Observe.md

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
---
2+
RFC: 14
3+
Status: Proposed
4+
---
5+
6+
# `OBSERVE` command for enhanced observability in Valkey
7+
8+
## Abstract
9+
10+
Proposal describes a new OBSERVE command to enhance Valkey's observability capabilities.
11+
By enabling advanced time-series metrics, custom gathering pipelines, and in-server data aggregation, the `OBSERVE` command will provide Valkey users with first-class monitoring capabilities, offering granular insights into server behavior and performance.
12+
13+
## Motivation
14+
15+
Currently, Valkey’s observability relies on commands such as `MONITOR`, `SLOWLOG`, and `INFO`.
16+
17+
While these commands are useful, they also have limitations:
18+
- `MONITOR` streams every command, generating high data volume that may overload production environments.
19+
- `SLOWLOG` logs only commands exceeding a set execution time, omitting quick operations and general command patterns.
20+
- `INFO` provides server statistics but lacks detailed insights into specific commands and keys.
21+
22+
These commands lack the flexibility for in-depth, customizable observability that could be exposed directly within the valkey-server instance.
23+
This includes filtering for specific commands executions, sampling data, executing custom processing steps, and aggregating metrics over time windows.
24+
For example, it's questionable if the current feature set has the ability to expose [The Four Golden Signals](https://sre.google/sre-book/monitoring-distributed-systems/).
25+
26+
## Design
27+
28+
The proposed `OBSERVE` command suite brings observability as a core feature to Valkey. Through user-defined "observability pipelines," Valkey instances can produce detailed insights in a structured, efficient manner.
29+
These pipelines are customizable to support diverse use cases, providing users with foundational building blocks for monitoring without overwhelming server resources. This new functionality could be enhanced with integration with tools like Prometheus and Grafana for visualization or alerting, although its primary purpose is in-server analysis.
30+
31+
## Specification
32+
33+
The `OBSERVE` command set introduces the concept of observability pipelines — user-defined workflows for collecting, filtering, aggregating, and storing metrics.
34+
35+
### Commands
36+
37+
Here is the list of `OBSERVE` subcommands:
38+
39+
#### OBSERVE CREATE
40+
41+
Creates an observability pipeline with a specified configuration. Configuration details, specified in the next section, define steps such as filtering, partitioning, sampling, and aggregation.
42+
Pipeline and it's configuration is persisted in the runtime memory (i.e. user needs to re-create the pipeline after server restart).
43+
44+
Syntax:
45+
```bash
46+
OBSERVE CREATE <pipeline_name> <configuration>
47+
```
48+
49+
#### OBSERVE START
50+
51+
Starts data collection for the specified pipeline.
52+
53+
Syntax:
54+
```bash
55+
OBSERVE START <pipeline_name>
56+
```
57+
58+
#### OBSERVE STOP
59+
60+
Stops data collection for the specified pipeline.
61+
62+
Syntax:
63+
```bash
64+
OBSERVE STOP <pipeline_name>
65+
```
66+
67+
#### OBSERVE RETRIEVE
68+
69+
Retrieves collected data. (Alternatively, GET could potentially serve for this function, but further design discussion is needed.)
70+
71+
Syntax:
72+
```bash
73+
OBSERVE RETRIEVE <pipeline_name> <since_offset>
74+
```
75+
76+
#### OBSERVE LOADSTEPF
77+
78+
Allows defining custom processing steps using Lua, for cases where built-in steps do not meet needed requirements.
79+
80+
Syntax:
81+
```bash
82+
OBSERVE LOADSTEPF <step_name> <lua_code>
83+
```
84+
85+
### Configuration
86+
87+
Configuration of the `OBSERVE` feature is mainly done through specyfing pipelines. It's fully customizable such that we don't limit this feature to hardcoded observability characteristics.
88+
89+
#### Pipelines
90+
91+
Pipelines are configured as chains of data processing stages, including filtering, aggregation, and output buffering. Format is similar to the Unix piping.
92+
93+
Key stages in this pipeline model include:
94+
- `filter(f)`: Filters data units based on defined conditions (e.g., command type).
95+
- `partition(f)`: Partitions data units according to a function (e.g., by key prefix).
96+
- `sample(f)`: Samples data units at a specified rate.
97+
- `transform(f)`: Transforms each data unit with a specified function. It is append-only, so can only add data to the processed data unit.
98+
- `window(f)`: Aggregates data within defined time windows.
99+
- `reduce(f)`: Reduces data over a window via an aggregation function.
100+
- `output(f)`: Directs output to specified sinks.
101+
102+
103+
Example configuration syntax:
104+
```bash
105+
OBSERVE CREATE get_errors_pipeline "
106+
filter(filter_by_commands(['GET'])) |
107+
filter(filter_for_errors) |
108+
window(window_duration(1m)) |
109+
reduce(count) |
110+
output(output_timeseries_to_key('get_errors_count', max_length=1000))
111+
"
112+
```
113+
114+
#### Output
115+
116+
117+
The goal is to capture time-series metrics within the defined pipeline outputs, f.e. for the pipeline above it would be structured as follows:
118+
119+
```
120+
[<timestamp1, errors_count1>, <timestamp2, errors_count2>, ...] // capped at 1000 items
121+
```
122+
123+
It remains uncertain whether storing output data in a format compatible with direct retrieval via GET (or another existing command) will be feasible. Consequently, we might need to introduce an `OBSERVE RETRIEVE <pipeline_name> <since_offset>` command for clients polling results data. This command would provide:
124+
```
125+
{
126+
current_offset: <latest_returned_offset as a number>,
127+
data: [ ... result items ],
128+
lag_detected: <true or false> // true if `since_offset` points to data that’s been removed, signaling potential data loss.
129+
}
130+
```
131+
132+
Here, offset represents the sequence number of items produced by the pipeline, including any items removed due to buffer constraints. This approach allows clients to poll for results while adjusting their polling frequency based on the lag_detected flag. If lag_detected is true, clients would be advised to increase polling frequency to reduce data loss.
133+
134+
135+
### Authentication and Authorization
136+
137+
The introduction of our new Advanced Configuration Layer (ACL) category is a crucial step in enhancing security and control around the `OBSERVE` commands.
138+
For all deployed Valkey instances, it will be essential to ensure that only authorized personnel can configure and enable observability pipelines, as improper configuration can lead to performance drops.
139+
In light of this, part of the design involves creating a new ACL category specific for `OBSERVE` commands, allowing admins to fine-tune access controls and prevent unaccepted modifications.
140+
141+
The extent to which access will be granted for Lua step functions remains unclear. However, there is a need for some form of limitation to prevent observability steps from consuming excessive computational resources and avoiding unauthorized access to sensitive information stored within Valkey.
142+
143+
### Benchmarking
144+
145+
This is definitely something we have to do once we have a working prototype.
146+
147+
### Testing
148+
149+
Developing e2e tests with enough coverage is definitely something we have to do once we have a working prototype solution.
150+
151+
### Observability
152+
153+
Having a comprehensive observability capabilities is crucial for monitoring and analyzing Valkey performance.
154+
However, it's unclear whether developing a new custom observability layer for the observability pipelines is truly necessary.
155+
This issue arises from the idea that we likely shouldn't use the `OBSERVE` pipelines to observe themselves, as in case there is something wrong, we won't get valid data.
156+
This topic warrants further discussion, particularly within the context of the first iteration of this RFC.
157+
158+
Having said that, it may be that the initial version does not require built-in observability capabilities for observability pipelines to effectively observe and monitor the pipelines themselves.
159+
160+
## Examples
161+
162+
Below are examples of how the proposed `OBSERVE` command and pipeline configurations could be used to address various observability needs.
163+
164+
165+
1. **Counting Specific Commands Per Minute with Buffer Size**
166+
167+
*Use Case:* Count the number of `GET` commands executed per minute.
168+
169+
**Pipeline Creation:**
170+
171+
```valkey
172+
OBSERVE CREATE get_commands_per_minute "
173+
filter(filter_by_commands(['GET'])) |
174+
window(window_duration(1m)) |
175+
reduce(reduce_count) |
176+
output(output_timeseries_to_key('get_command_count', buffer_size=1440))
177+
"
178+
```
179+
180+
*Explanation:* This pipeline filters for `GET` commands, counts them per every minute, and stores the counts
181+
in a time-series key `get_command_count` with a buffer size of 1440 (e.g., one day's worth of minute-level data).
182+
183+
2. **Average Latency Per Time Window with Buffer**
184+
185+
*Use Case:* Monitor average latency of `SET` commands per minute.
186+
187+
**Pipeline Creation:**
188+
189+
```valkey
190+
OBSERVE CREATE set_latency_monitor "
191+
filter(filter_by_commands('SET')) |
192+
sample(sample_percentage(0.005)) |
193+
window(window_duration(1m)) |
194+
reduce(average_latency) |
195+
output(timeseries_to_key('set_average_latency', buffer_size=720))
196+
"
197+
```
198+
199+
*Explanation:* This pipeline filters for `SET` commands, extracts their latency, aggregates the average latency every
200+
minute, and stores it with a buffer size of 720 (e.g., 12 hours of minute-level data).
201+
202+
3. **Client Statistics**
203+
204+
*Use Case:* Gather command counts per client for `GET` and `SET` commands, sampled at 5%.
205+
206+
**Pipeline Creation:**
207+
208+
```shell
209+
OBSERVE CREATE client_stats_per_minute "
210+
filter(filter_by_commands(['GET', 'SET'])) |
211+
sample(sample_percentage(0.05)) |
212+
transform(transform_add_client_info) |
213+
window(window_duration(1m)) |
214+
reduce(count_by_client) |
215+
output(timeseries_to_key('client_stats', buffer_size=1440))
216+
"
217+
```
218+
219+
*Explanation:* This pipeline filters for `GET` and `SET` commands, samples 5% of them, extracts client information, c
220+
ounts commands per client every minute, and stores the data under `client_stats` with a buffer size of 1440.
221+
222+
4. **Error Tracking**
223+
224+
*Use Case:* Monitor the number of errors occurring per minute.
225+
226+
**Pipeline Creation:**
227+
228+
```shell
229+
OBSERVE CREATE error_tracking_pipeline "
230+
filter(filter_for_errors) |
231+
window(window_duration(1m)) |
232+
reduce(count) |
233+
output(timeseries_to_key('total_errors', buffer_size=1440))
234+
"
235+
```
236+
237+
*Explanation:* This pipeline filters for commands executions that ended with an 'error', counts them every minute, and stores the totals in `tota
238+
l_errors` with a buffer size of 1440.
239+
240+
5. **TTL Analysis**
241+
242+
*Use Case:* Analyze the average TTL of keys set with `SETEX` command per minute.
243+
244+
**Pipeline Creation:**
245+
246+
```shell
247+
OBSERVE CREATE ttl_analysis_pipeline "
248+
filter(filter_by_commands(['SETEX'])) |
249+
transform(transform_parse_ttl_as_int) |
250+
window(window_duration(1m)) |
251+
reduce(average_ttl) |
252+
output(timeseries_to_key('average_ttl', buffer_size=1440))
253+
"
254+
```
255+
256+
*Explanation:* This pipeline filters for `SETEX` commands, extracts the TTL values, calculates the average TTL every
257+
minute, and stores it in `average_ttl` with a buffer size of 1440.
258+
259+
6. **Distribution of Key and Value Sizes**
260+
261+
*Use Case:* Create a histogram of value sizes for `SET` commands.
262+
263+
**Pipeline Creation:**
264+
265+
```shell
266+
OBSERVE CREATE value_size_distribution "
267+
filter(command('SET')) |
268+
transform(transform_get_value_size) |
269+
window(window_duration(1m)) |
270+
reduce(histogram(key='value_size',buckets([0, 64, 256, 1024, 4096, 16384]))) |
271+
output(timeseries_to_key('value_size_distribution', buffer_size=1440))
272+
"
273+
```
274+
275+
*Explanation:* This pipeline filters for `SET` commands, extracts the size of the values, aggregates them into histog
276+
ram buckets every minute, and stores the distributions with a buffer size of 1440.
277+
278+
279+
## Appendix
280+
281+
- RFC is based on this GitHub issue: [OBSERVE command for enhanced observability in Valkey](https://github.com/valkey-io/valkey/issues/1167).
282+

0 commit comments

Comments
 (0)