JusDB
Free DB Audit
NoSQL Databases

Monitoring Valkey: Prometheus Metrics, COMMANDLOG, JSON Logs and the Alerts That Matter

Set up Valkey 9.1 monitoring with redis_exporter and Prometheus: the INFO fields that matter, COMMANDLOG and JSON logs, and starter alert rules with the reasoning behind each.

JusDB Team
Published November 6, 2026
Updated November 10, 2026
19 min read

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.

TL;DR
  • Run oliver006/redis_exporter v1.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_thread and used_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+) and log-timestamp-format iso8601 at runtime with CONFIG 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

Valkey monitoring pipeline Step 1: redis_exporter reads INFO ALL, LATENCY and COMMANDLOG LEN from Valkey 9.1. Step 2: Prometheus scrapes the exporter on port 9121 over HTTP. Step 3: Prometheus evaluates alert rules and sends firing alerts to Alertmanager, which routes them. Separately, Valkey writes JSON log lines to stdout or a log file, and a log shipper sends them to the log store. Valkey 9.1 INFO, COMMANDLOG redis_exporter port 9121 Prometheus scrape and rules Alertmanager routes alerts Log shipper log-format json 1 INFO ALL, COMMANDLOG 2 HTTP scrape 3 rules fire stdout or logfile No built-in metrics endpoint: the exporter turns INFO fields into Prometheus series

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.

bash
# 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_ME

Interpretation: 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:

yaml
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:9121

Interpretation: 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

  1. 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
  2. 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
  3. Check that the exporter can reach Valkey

    redis_up must be 1 and redis_exporter_last_scrape_error must be 0.

    curl -s http://localhost:9121/metrics \ | grep -E '^redis_(up|exporter_last_scrape_error)'
  4. 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
  5. 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
  6. 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

SignalINFO field
Memory headroomused_memory, maxmemory
Fragmentationallocator_frag_ratio, mem_fragmentation_ratio
Evictionsevicted_keys
Cache hit ratiokeyspace_hits, keyspace_misses
Replication linkmaster_link_status (on the replica)
Replica lagslaveN:...offset=...,lag=... (on the primary)
Connectionsconnected_clients, maxclients, rejected_connections
Blocked clientsblocked_clients
Persistencerdb_last_bgsave_status, aof_last_write_status
Main thread loadused_active_time_main_thread (9.1+)

Exporter metric

Signalredis_exporter v1.97.0
Memory headroomredis_memory_used_bytes, redis_memory_max_bytes
Fragmentationredis_allocator_frag_ratio, redis_mem_fragmentation_ratio
Evictionsredis_evicted_keys_total
Cache hit ratioredis_keyspace_hits_total, redis_keyspace_misses_total
Replication linkredis_master_link_up
Replica lagredis_connected_slave_offset_bytes, redis_connected_slave_lag_seconds
Connectionsredis_connected_clients, redis_max_clients, redis_rejected_connections_total
Blocked clientsredis_blocked_clients
Persistenceredis_rdb_last_bgsave_status, redis_aof_last_write_status (1 = ok)
Main thread loadredis_active_time_main_thread_seconds_total

What it tells you

SignalMeaning
Memory headroomHow close you are to evictions or OOM errors on writes
FragmentationAllocator waste; only the allocator ratio is pure fragmentation
EvictionsKeys dropped because maxmemory was reached
Cache hit ratioWhether the cache is still saving backend reads
Replication linkWhether a replica is receiving the stream at all
Replica lagBytes behind, and seconds since the replica last acknowledged
ConnectionsPool leaks and refused clients
Blocked clientsClients parked on BLPOP-style commands; normal for queues
PersistenceWhether the last save or AOF write failed
Main thread loadReal 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.

bash
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:

yaml
# 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 * 1024

Interpretation: 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.

sql
# 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.

sql
# 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.

bash
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.

sql
# 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.

bash
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 typeThreshold settingDefaultLength setting
slowcommandlog-execution-slower-than10000 µs (10 ms)commandlog-slow-execution-max-len 128
large-requestcommandlog-request-larger-than1048576 bytescommandlog-large-request-max-len 128
large-replycommandlog-reply-larger-than1048576 bytescommandlog-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.

bash
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.

Large-reply logging has a cost with I/O threads

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

ops@valkey-01:~
$ valkey-cli COMMANDLOG LEN large-reply(integer) 128$ valkey-cli COMMANDLOG GET 1 large-reply1) 1) (integer) 4187 2) (integer) 1794297600 3) (integer) 5242880 4) 1) "HGETALL" 2) "session:index" 5) "10.0.4.17:51234" 6) "api-worker"

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:

bash
#!/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.

ini
# 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 yes

Interpretation: 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.

json
{"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.

yaml
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 for rides 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_total comes from the errorstats section, keyed by error prefix.
  • Memory near limit: > 0 inside the division drops instances without maxmemory, 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_seconds is 0 both when no certificate is loaded and once it has expired. The serial!="none" join drops instances without TLS and still fires on an expired certificate.
Test rules before they page

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_id for 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.

Share this article

Database engineering notes

Articles like this one, in your inbox. No spam, unsubscribe anytime.

JusDB Team

Official JusDB content team

Keep reading

Moving a Multi-Database Valkey Instance into a 9.x Cluster

Move a Valkey instance that uses SELECT 1 to 15 into a 9.x cluster: keep or remap databases, copy each one, verify counts per database, and keep a way back.

Redis18 minNov 10, 2026
Read

Valkey Keyspace Notifications in Production: Expired Events, hexpired and Cluster Mode

Run Valkey keyspace notifications in production: why expired and hexpired events lag the TTL, how to subscribe on every cluster node, and when a stream is safer.

Redis20 minNov 10, 2026
Read

Valkey GLIDE in Production: AZ-Affinity Reads and Migrating from Legacy Clients

Configure Valkey GLIDE AZ-affinity reads on servers and clients, learn what GLIDE 2.5.3 does not support, and migrate from redis-py, Jedis, ioredis, or go-redis one service at a time.

Redis20 minNov 10, 2026
Read