Valkey does not expose a metrics endpoint. Everything you graph comes from INFO, COMMANDLOG, LATENCY and the server log, through an exporter you run beside it. A Redis-era setup gives misleading signals on Valkey 9.1: with I/O threads on, CPU sits near 100% on an idle server, and the slow log misses the 5 MB HGETALL reply that saturates the network. This guide covers what to collect on Valkey 9.1.3 (released 26 October 2026) with redis_exporter v1.97.0 (4 November 2026). It also covers COMMANDLOG, JSON logs, and starter alert rules with the reasoning behind each threshold.
- Run
oliver006/redis_exporterv1.97.0 or later with a dedicated ACL user. It supports Valkey 7.x to 9.x, including the 9.1 thread and TLS fields. - Judge thread load on 9.1 with
used_active_time_main_threadandused_active_time_io_thread_N, not CPU percent, because I/O threads busy-wait. COMMANDLOG(8.1+) has three logs: slow (default 10,000 µs), large-request and large-reply (default 1 MiB each), 128 entries each. The exporter exports only their lengths, so alert on the slow log's ID instead.- Set
log-format json(9.1+) andlog-timestamp-format iso8601at runtime withCONFIG SET; no restart is needed. - Every threshold below is a starting point. Baseline for two weeks before an alert pages anyone.
Scope and review date: November 2026. This post covers self-managed Valkey 9.1 primaries, replicas and cluster nodes, with notes for 8.1 and 9.0. Metric names come from redis_exporter v1.97.0 with its default redis namespace. Commands, defaults and metric names were checked against the Valkey documentation and the 9.1.3 and exporter source on 10 November 2026. They were not run in a lab, so test the rules on your own Prometheus first.
What does a Valkey monitoring pipeline need?
An exporter that turns INFO ALL, LATENCY, SLOWLOG and COMMANDLOG LEN into Prometheus series, Prometheus to scrape it and evaluate rules, and Alertmanager to route alerts. Logs travel separately. Valkey's own Monitoring Valkey with Prometheus post (28 September 2026) says the server "does not expose any metrics endpoint on its own" and uses the same exporter.
The redis_exporter README now describes itself as a "Prometheus exporter for Valkey metrics" that "Supports Valkey 7.x, 8.x, 9.x".
Valkey metrics and logs pipeline
Metrics are pulled through the exporter; logs leave as JSON lines on a separate path
Scrape every node: each primary, replica and cluster node has its own INFO, latency data and command log, so give each one its own scrape target.
How do you run redis_exporter against Valkey 9.1 with a least-privilege user?
Do not scrape as default. The README publishes an ACL line per server family; this is the one for Valkey 8.1 and later, which adds +commandlog|len. It also grants COMMAND INFO, so the exporter can probe for COMMANDLOG support without an ACL error on every scrape.
# Run inside valkey-cli as an admin user (the | characters would break a shell command line)
ACL SETUSER exporter on -@all +@connection +memory -readonly +strlen +config|get +xinfo +pfcount -quit +zcard +type +xlen -readwrite -command +command|info +client -wait +scard +llen +hlen +get +eval +slowlog +commandlog|len +cluster|info +cluster|slots +cluster|nodes -hello -echo +info +latency +scan -reset -auth -asking >CHANGE_MEInterpretation: OK means the user exists. The README warns that an unknown command makes the whole ACL SETUSER fail, so leave out +commandlog|len on servers older than 8.1. +get and +eval serve the --check-keys and --script features.
Run one exporter per node with REDIS_ADDR, REDIS_USER and REDIS_PASSWORD_FILE set (step 2 of the runbook below), and use rediss:// when the server only accepts TLS. Pin the image tag, because metric names can change between releases and break your rules. Latency histograms are collected by default; --exclude-latency-histogram-metrics exists only for servers older than Redis 7.
Or run one exporter for many nodes through its multi-target /scrape endpoint, as the README shows:
scrape_configs:
- job_name: valkey
metrics_path: /scrape
static_configs:
- targets:
- redis://10.0.1.11:6379
- redis://10.0.1.12:6379
- redis://10.0.1.13:6379
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: valkey-exporter.internal:9121Interpretation: in this mode one password serves every target, so all nodes need the same exporter user.
Setting up Valkey monitoring, step by step
Six steps; none of them restarts Valkey
-
Create the exporter user
Use the 8.1+ ACL line above, then confirm the user can run the commands the exporter needs.
valkey-cli ACL DRYRUN exporter \ COMMANDLOG LEN slow -
Start the exporter, pinned to a release
One container per node, or one exporter with the /scrape endpoint.
docker run -d -p 9121:9121 \ -e REDIS_ADDR=redis://10.0.1.11:6379 \ -e REDIS_USER=exporter \ -e REDIS_PASSWORD_FILE=/run/secrets/pwd.json \ oliver006/redis_exporter:v1.97.0 -
Check that the exporter can reach Valkey
redis_upmust be 1 andredis_exporter_last_scrape_errormust be 0.curl -s http://localhost:9121/metrics \ | grep -E '^redis_(up|exporter_last_scrape_error)' -
Turn on the latency monitor
It is off by default (threshold 0). 10 ms matches the default slow command threshold.
valkey-cli CONFIG SET latency-monitor-threshold 10 -
Switch the server log to JSON
Valkey 9.1 and later. Persist it with CONFIG REWRITE or your configuration management.
valkey-cli CONFIG SET log-format json \ log-timestamp-format iso8601 -
Load the alert rules without paging
Route every rule to a ticket queue for two weeks, then promote the ones that held up.
promtool check rules valkey-alerts.yml
Rollback point: every step is reversible at runtime. Stop the exporter, or CONFIG SET the old value back. Persist a setting only after it has run for a day.
Which INFO fields matter, and what does each one tell you?
Of the hundreds of exported series, about ten answer the first on-call questions. The INFO command reference defines each field; the exporter renames most of them.
The INFO fields that matter, three ways
Switch tabs to see the raw INFO field, the exporter v1.97.0 metric, and what the signal tells you
INFO field
| Signal | INFO field |
|---|---|
| Memory headroom | used_memory, maxmemory |
| Fragmentation | allocator_frag_ratio, mem_fragmentation_ratio |
| Evictions | evicted_keys |
| Cache hit ratio | keyspace_hits, keyspace_misses |
| Replication link | master_link_status (on the replica) |
| Replica lag | slaveN:...offset=...,lag=... (on the primary) |
| Connections | connected_clients, maxclients, rejected_connections |
| Blocked clients | blocked_clients |
| Persistence | rdb_last_bgsave_status, aof_last_write_status |
| Main thread load | used_active_time_main_thread (9.1+) |
Exporter metric
| Signal | redis_exporter v1.97.0 |
|---|---|
| Memory headroom | redis_memory_used_bytes, redis_memory_max_bytes |
| Fragmentation | redis_allocator_frag_ratio, redis_mem_fragmentation_ratio |
| Evictions | redis_evicted_keys_total |
| Cache hit ratio | redis_keyspace_hits_total, redis_keyspace_misses_total |
| Replication link | redis_master_link_up |
| Replica lag | redis_connected_slave_offset_bytes, redis_connected_slave_lag_seconds |
| Connections | redis_connected_clients, redis_max_clients, redis_rejected_connections_total |
| Blocked clients | redis_blocked_clients |
| Persistence | redis_rdb_last_bgsave_status, redis_aof_last_write_status (1 = ok) |
| Main thread load | redis_active_time_main_thread_seconds_total |
What it tells you
| Signal | Meaning |
|---|---|
| Memory headroom | How close you are to evictions or OOM errors on writes |
| Fragmentation | Allocator waste; only the allocator ratio is pure fragmentation |
| Evictions | Keys dropped because maxmemory was reached |
| Cache hit ratio | Whether the cache is still saving backend reads |
| Replication link | Whether a replica is receiving the stream at all |
| Replica lag | Bytes behind, and seconds since the replica last acknowledged |
| Connections | Pool leaks and refused clients |
| Blocked clients | Clients parked on BLPOP-style commands; normal for queues |
| Persistence | Whether the last save or AOF write failed |
| Main thread load | Real spare capacity; CPU percent misleads with I/O threads |
Naming: the exporter keeps Redis-era names (redis_ prefix, slave, master), and so does Valkey's INFO, which still prints role:master and slave0: lines in 9.1.3.
How do you read memory, eviction and fragmentation correctly?
Memory headroom is used_memory against maxmemory. When maxmemory is 0 (no limit), the ratio is meaningless, and your limit is the host or container memory instead. What happens at the limit depends on maxmemory-policy. A cache policy such as allkeys-lru evicts keys. With noeviction, writes fail with an OOM error and evicted_keys stays at zero, so alert on the error counter too.
valkey-cli INFO memory | grep -E '^(used_memory|used_memory_rss|maxmemory|maxmemory_policy|mem_fragmentation_ratio|mem_fragmentation_bytes|allocator_frag_ratio|allocator_frag_bytes):'Interpretation: the INFO reference defines mem_fragmentation_ratio as RSS divided by used_memory. It warns that this includes process overhead and that "when the total fragmentation bytes is low (few megabytes), a high ratio (e.g. 1.5 and above) is not an indication of an issue". allocator_frag_ratio is what the page calls "the true (external) fragmentation metric". If used_memory is well above RSS, the docs say part of the dataset has been swapped out, so expect latency.
Write fragmentation rules on both ratio and bytes, so a 50 MB cache cannot page you at night:
# Fragmentation that is both proportionally and absolutely large
expr: |
redis_allocator_frag_ratio > 1.4
and (redis_memory_used_rss_bytes - redis_memory_used_bytes) > 512 * 1024 * 1024Interpretation: both numbers are starting points. The byte floor stops alerts on small instances.
For a cache, put the hit ratio on a dashboard, not in a pager rule. A sudden drop after a deploy usually means a key naming change or a TTL bug.
# PromQL: hit ratio over 5 minutes, per instance
sum by (instance) (rate(redis_keyspace_hits_total[5m]))
/
(sum by (instance) (rate(redis_keyspace_hits_total[5m]))
+ sum by (instance) (rate(redis_keyspace_misses_total[5m])))Interpretation: 0.95 means 95% of lookups found the key; rate() handles the counter reset at restart.
How do you watch replication, clients and persistence?
On a replica, master_link_status is up or down, and the exporter turns it into redis_master_link_up (1 or 0). On the primary, each replica gets a slaveN: line with its acknowledged offset and lag. In the 9.1.3 source, lag is the number of seconds since that replica's last acknowledgement. Replicas acknowledge once a second from the replication cron, so lag is normally 0 or 1.
# PromQL: bytes each replica is behind, compared with the backlog size
(redis_master_repl_offset - on(instance) group_right() redis_connected_slave_offset_bytes)
> on(instance) group_left() (0.5 * redis_replication_backlog_bytes)Interpretation: this fires when a replica is more than half a backlog behind. The backlog (repl-backlog-size, 10 MB by default in 9.1.3) lets a reconnecting replica resume with a partial resync; beyond it, a disconnect forces a full sync. The 0.5 factor is a starting point.
For clients, compare connected_clients with maxclients; any increase in rejected_connections means clients were already refused. blocked_clients counts clients waiting on BLPOP-style commands. Queue workloads keep it high, so graph it and alert only on a change from baseline.
For persistence, the exporter maps ok to 1 and err to 0. A failed background save is the signal that matters most. With the default stop-writes-on-bgsave-error yes, the primary refuses writes after a failed save while RDB snapshots are configured, so treat it as an outage in progress.
valkey-cli INFO persistence | grep -E '^(rdb_last_bgsave_status|rdb_last_save_time|rdb_changes_since_last_save|aof_enabled|aof_last_write_status|aof_last_bgrewrite_status):'
valkey-cli INFO stats | grep -E '^latest_fork_usec:'Interpretation: rdb_last_save_time is the time of the last successful save, so time() - redis_rdb_last_save_timestamp_seconds is the age of your newest snapshot. latest_fork_usec is how long the last fork blocked the main thread; it grows with the dataset.
How do you measure command latency and thread load on Valkey 9.1?
Three sources cover latency inside the server. LATENCY HISTOGRAM keeps a per-command cumulative histogram (latency-tracking yes is the default). The exporter turns it into redis_commands_latencies_usec, with bucket boundaries in microseconds. The LATENCY HISTOGRAM reference says tracking covers 1 µs to roughly one second, with everything above counted as +Inf.
# PromQL: server-side p99 per command, in microseconds
histogram_quantile(0.99,
sum by (instance, cmd, le) (rate(redis_commands_latencies_usec_bucket[5m])))Interpretation: this is time spent inside Valkey only. Network round trips and client queueing are not in it, so a healthy server p99 with slow application calls points elsewhere.
The latency monitor records events (forks, AOF fsync, expiry cycles and others) slower than latency-monitor-threshold, which is 0 (off) by default. With it on, the exporter publishes redis_latency_spike_last and redis_latency_spike_duration_seconds per event_name. Reading those events during an incident is covered in our Valkey latency spike runbook.
Thread load changed in 9.1. With I/O threads enabled, threads busy-wait, so CPU time "appear[s] to be near 100% even if the server has spare capacity," in the words of the INFO reference. Valkey 9.1 added used_active_time_main_thread and one used_active_time_io_thread_N per I/O thread (PRs #2931 and #2463 in the 9.1.0-rc1 release notes). Both count seconds of real work.
valkey-cli INFO cpu | grep -E '^used_active_time_'Interpretation: you get one main-thread line and lines for I/O threads 1 to io-threads minus 1, because the configured count includes the main thread. The exporter publishes them as redis_active_time_main_thread_seconds_total and redis_active_time_io_thread_seconds_total{thread="N"}. rate() over either gives the fraction of one thread in use: 0.5 means half its capacity, the same example the INFO page uses.
How should you configure COMMANDLOG for monitoring?
Valkey 8.1 replaced the single slow log with COMMANDLOG (#1294 in the 8.1.0 release notes). It keeps three independent logs, each with its own threshold and length. The 9.1.3 defaults from valkey.conf:
| Log type | Threshold setting | Default | Length setting |
|---|---|---|---|
| slow | commandlog-execution-slower-than | 10000 µs (10 ms) | commandlog-slow-execution-max-len 128 |
| large-request | commandlog-request-larger-than | 1048576 bytes | commandlog-large-request-max-len 128 |
| large-reply | commandlog-reply-larger-than | 1048576 bytes | commandlog-large-reply-max-len 128 |
A negative threshold disables a log and 0 logs every command. The old slowlog-* settings are aliases, and SLOWLOG GET reads the same slow log. The slow log counts execution time only, "where the main thread is blocked", so a huge reply that is slow to send never appears in it; the large-reply log catches that.
valkey-cli CONFIG SET commandlog-execution-slower-than 5000
valkey-cli CONFIG SET commandlog-large-reply-max-len 512
valkey-cli CONFIG GET 'commandlog-*'Interpretation: all six settings can be changed at runtime. A longer log costs memory. Each node keeps its own log.
valkey.conf warns that any value other than -1 for commandlog-reply-larger-than "has performance implications when I/O threads are used". A 9.0.1 fix (#2652) made large replies countable under reply copy avoidance and caused a regression. 9.0.2 lets you recover it by setting the threshold to -1. Decide per cluster whether large-reply visibility is worth it.
Reading the large-reply command log
Illustrative output, shaped like valkey-cli on Valkey 9.1; the entry fields follow the COMMANDLOG GET reference
Length is not a rate: LEN stopped at 128 when the log filled, while the entry ID (4187) kept counting. The six fields are ID, Unix time, size in bytes, arguments, client address and client name, per the COMMANDLOG GET reference.
The exporter publishes redis_commandlog_slow_length, redis_commandlog_large_request_length and redis_commandlog_large_reply_length from COMMANDLOG LEN, plus the six settings as gauges. A length stays flat once the log is full, so it cannot show new entries. For the slow log, use redis_slowlog_last_id instead: the exporter reads the newest ID from SLOWLOG GET 1, and the reference says the ID "is never reset" until restart. The large-request and large-reply logs have no ID metric, so publish one yourself through the node_exporter textfile collector:
#!/bin/sh
# Run every minute from cron. Writes the newest COMMANDLOG entry ID per type.
# Needs a user with +commandlog|get; entries contain command arguments.
OUT=/var/lib/node_exporter/textfile/valkey_commandlog.prom
TMP="$OUT.$$"
: > "$TMP"
for t in slow large-request large-reply; do
id=$(VALKEYCLI_AUTH="$CMDLOG_PASS" valkey-cli --user cmdlog-reader \
COMMANDLOG GET 1 "$t" | head -n 1)
echo "valkey_commandlog_last_id{type=\"$t\"} ${id:-0}" >> "$TMP"
done
mv "$TMP" "$OUT"Interpretation: with output piped, valkey-cli prints one value per line, and the first line is the entry ID. delta(valkey_commandlog_last_id{type="large-reply"}[15m]) > 0 then fires on new entries. This script was not run; check the output format against your valkey-cli version. Treat the reader account as sensitive, because log entries carry keys and values.
In the 9.2 release candidates, commandlog-request-larger-than and commandlog-reply-larger-than accept memory units such as 10mb (#2648). That is 9.2 release candidate behaviour only; 9.1 takes plain bytes.
How do you switch Valkey to JSON logs and ship them?
log-format and log-timestamp-format arrived in 8.1 (#1022) with legacy and logfmt. The json value arrived in 9.1 (#1791 in the 9.1.0-rc1 notes). Both are runtime settings. The 9.1.3 source writes one object per line with five fields: pid, role, timestamp, level and message. role is primary, replica, sentinel or RDB/AOF (a fork child), and level is debug, info, notice or warning.
# valkey.conf (9.1 and later)
loglevel notice
log-format json
log-timestamp-format iso8601
logfile /var/log/valkey/valkey.log
hide-user-data-from-log yesInterpretation: iso8601 writes yyyy-mm-ddThh:mm:ss.sss±hh:mm, which log stores parse without a custom pattern. An empty logfile sends logs to standard output, which suits containers.
{"pid":1,"role":"primary","timestamp":"2026-11-10T09:14:03.512+00:00","level":"warning","message":"Background saving error"}Interpretation: this line is constructed from the 9.1.3 format string and the message text in rdb.c, not captured from a server. Messages worth a log-based alert include Background saving error, Connection with primary lost. and anything at level warning. A quick local filter: tail -F /var/log/valkey/valkey.log | jq -c 'select(.level == "warning")'.
Check what the logs contain before you centralise them. hide-user-data-from-log yes is the default. The 9.1.3 release notes fixed a case where error text still carried command arguments into the log (#4815), and 9.1.2 fixed command log redaction inside MULTI and scripts (#4323). Run 9.1.3 before you send logs to a shared system.
Which alert rules should you start with?
The rules below use exporter v1.97.0 names and the valkey job from the scrape config above. Every threshold is a starting point, marked in the comments. Page only for outages and data-loss risks; ticket the rest. Check the syntax against the Prometheus alerting rules documentation for your version.
groups:
- name: valkey
rules:
- alert: ValkeyDown
expr: redis_up == 0
for: 2m
labels: {severity: page}
- alert: ValkeyClusterStateNotOk
expr: redis_cluster_state == 0
for: 1m
labels: {severity: page}
- alert: ValkeyReplicaLinkDown
expr: redis_master_link_up == 0
for: 1m
labels: {severity: page}
- alert: ValkeyBgsaveFailed
expr: redis_rdb_last_bgsave_status == 0
labels: {severity: page}
- alert: ValkeyAofWriteFailed
expr: redis_aof_last_write_status == 0 and redis_aof_enabled == 1
labels: {severity: page}
- alert: ValkeyWritesRejectedOOM
expr: rate(redis_errors_total{err="OOM"}[5m]) > 0
for: 2m
labels: {severity: page}
- alert: ValkeyMemoryNearLimit # 0.90: starting point
expr: redis_memory_used_bytes / (redis_memory_max_bytes > 0) > 0.90
for: 10m
labels: {severity: ticket}
- alert: ValkeyEvicting # caches evict by design: tune or drop
expr: rate(redis_evicted_keys_total[10m]) > 0
for: 15m
labels: {severity: ticket}
- alert: ValkeyReplicaAckStale # 10 s: starting point; normal is 0 or 1
expr: redis_connected_slave_lag_seconds > 10
for: 2m
labels: {severity: ticket}
- alert: ValkeyClientsNearMax # 0.80: starting point
expr: redis_connected_clients / redis_max_clients > 0.80
for: 5m
labels: {severity: ticket}
- alert: ValkeyConnectionsRejected
expr: increase(redis_rejected_connections_total[5m]) > 0
labels: {severity: ticket}
- alert: ValkeyMainThreadBusy # 0.80 of one thread for 15 m: starting point
expr: rate(redis_active_time_main_thread_seconds_total[5m]) > 0.80
for: 15m
labels: {severity: ticket}
- alert: ValkeySlowCommandsBurst # 20 entries per 10 m: starting point
expr: delta(redis_slowlog_last_id[10m]) > 20
labels: {severity: ticket}
- alert: ValkeyTlsCertExpiring # 14 days: starting point
expr: |
redis_tls_server_cert_expires_in_seconds < 14 * 86400
and on(instance) redis_tls_certificate_info{certificate="server", serial!="none"}
labels: {severity: ticket}Interpretation: the reasoning, rule by rule:
- Down, cluster state, replica link: outages or lost redundancy; a short
forrides out a scrape blip. - Save and AOF failures: no
for, because the status stays at 0 until the next successful write, and the first failure is the one to act on. - OOM errors: with
noeviction, these are failed writes.redis_errors_totalcomes from theerrorstatssection, keyed by error prefix. - Memory near limit:
> 0inside the division drops instances withoutmaxmemory, which would otherwise divide by zero. - Replica acknowledgement: catches a stalled replica that is still connected; add the backlog rule above for lag in bytes.
- Main thread busy: commands queue once the main thread nears full use.
- TLS: in 9.1.3,
tls_server_cert_expires_in_secondsis 0 both when no certificate is loaded and once it has expired. Theserial!="none"join drops instances without TLS and still fires on an expired certificate.
promtool check rules catches syntax only. Write promtool test rules cases for the vector-matching rules (replica lag, TLS), which are the likeliest to match nothing.
Frequently asked questions
Does redis_exporter work with Valkey 9.1?
Yes. The redis_exporter README states support for Valkey 7.x, 8.x and 9.x, and release v1.97.0 (4 November 2026) reads Valkey-only data such as COMMANDLOG lengths, TLS certificate expiry, cluster databases and the 9.1 thread active-time counters. Metric names keep the redis_ prefix unless you set --namespace.
What is the difference between SLOWLOG and COMMANDLOG in Valkey?
COMMANDLOG, added in Valkey 8.1, holds three logs: slow commands, large requests and large replies. SLOWLOG still works and reads the same slow log, and the old slowlog-* settings are aliases. Only COMMANDLOG records commands with very large requests or replies, which execution time alone never reveals.
Why does Valkey show 100% CPU when it is not busy?
With I/O threads enabled, Valkey threads busy-wait for work, so CPU time looks close to 100% even with spare capacity. Valkey 9.1 added used_active_time_main_thread and used_active_time_io_thread_N to INFO. They count only time spent working, so their rate over time shows the real fraction of each thread in use.
Which Valkey version supports JSON logs?
Valkey 9.1. The log-format setting arrived in 8.1 with the legacy and logfmt values, and 9.1 added json. Each line is one JSON object with pid, role, timestamp, level and message. You can switch at runtime with CONFIG SET log-format json and pair it with log-timestamp-format iso8601.
What mem_fragmentation_ratio should trigger an alert?
Not the ratio on its own. The INFO reference says a high ratio with only a few megabytes of fragmentation is not an issue, and that the ratio includes process overhead. Alert on allocator_frag_ratio combined with a minimum number of wasted bytes, sized to your instance, and treat any threshold as a starting point to tune.
Key takeaways
- Run redis_exporter v1.97.0 with its own ACL user and a pinned tag; Valkey has no metrics endpoint.
- On 9.1, use the active-time counters for thread load, the latency histogram for server-side p99, and allocator fragmentation plus a byte floor for memory waste.
- COMMANDLOG lengths cap at the configured size, so alert on entry IDs:
redis_slowlog_last_idfor the slow log, and a small textfile script for the large-request and large-reply logs. - Set JSON logs (9.1) at runtime, and run 9.1.3 before shipping logs to a shared store.
- Page only on outages and failed persistence; route everything else to tickets until two weeks of baseline justify each threshold.
Set up Valkey monitoring with JusDB
JusDB's Valkey remote DBA team can review your exporter setup, your COMMANDLOG and latency monitor settings, and your alert rules against your topology and workload. We can also help you plan the baseline period and decide which alerts should page.
Bring your Valkey versions, your current Prometheus rules and a week of the series you already collect. Your team approves every configuration change.
Related reading
- A latency incident runbook for Valkey 9: what to do when these alerts fire.
- Valkey 9.1: database ACLs, Lua and I/O threading.
- Valkey performance tuning: memory, persistence and cluster configuration.
- Vendor documentation: Valkey latency monitoring and the INFO command reference.