Monitoring Redis (INFO, MONITOR)

Every production Redis deployment eventually needs an answer to two questions: “what is the server doing overall right now?” and “what commands, specifically, is it executing at this very second?” Redis answers the first question with the INFO command, which returns a large snapshot of internal statistics — memory usage, connected clients, replication state, keyspace hits and misses, and dozens of other counters. It answers the second with MONITOR, which streams every command the server processes, in real time, as it happens. Together they are the foundation of Redis observability, from a quick health check during an incident to wiring up a full metrics dashboard.

Overview: How Monitoring Works in Redis

Redis keeps a set of internal counters and state variables running continuously inside the server process — things like how many keys have expired, how much memory is allocated, how many clients are connected, and how many bytes have been read from and written to the network since startup. The INFO command doesn’t compute these values on demand by scanning the keyspace; it simply formats the counters Redis is already maintaining and returns them as one large bulk string, organized into named sections (Server, Clients, Memory, Persistence, Stats, Replication, CPU, Keyspace, and more). Because it’s reading pre-maintained counters rather than doing real work, INFO is cheap to call even under load, which is what makes it safe to poll every few seconds from a monitoring agent.

MONITOR works completely differently. Redis processes commands one at a time on a single main thread — this is why an individual command like INCR or HSET is always atomic, with nothing else able to interleave in the middle of it. When you run MONITOR from a client, that connection is put into a special mode: from then on, every command any client sends to the server — every GET, SET, EXPIRE, DEL, everything — is echoed to your monitoring connection with a timestamp, right as the main thread executes it. It is, quite literally, a live tap on the server’s single command-processing pipeline. That’s extremely useful for seeing exactly what an application is doing, but it also means every attached MONITOR client adds work to that single thread for every command the server handles, not just the ones you care about — so leaving it attached is not free, especially on a busy server.

Syntax

The general forms:

INFO [section]
MONITOR

Argument reference:

Argument Applies to Description
section INFO Optional. One of server, clients, memory, persistence, stats, replication, cpu, commandstats, latencystats, cluster, keyspace, or everything/all. Omit it to get the default set of sections (everything except commandstats and latencystats, which are opt-in because they can be large).
(none) MONITOR MONITOR takes no arguments. It puts the current connection into monitor mode until the client disconnects — in redis-cli that’s typically Ctrl+C.

Common INFO sections and what they tell you:

Section Key fields
server redis_version, process_id, tcp_port, uptime_in_seconds
clients connected_clients, blocked_clients, maxclients
memory used_memory, used_memory_human, maxmemory, mem_fragmentation_ratio
persistence rdb_last_save_time, rdb_changes_since_last_save, aof_enabled
stats total_connections_received, instantaneous_ops_per_sec, evicted_keys, keyspace_hits, keyspace_misses
replication role, connected_slaves, master_repl_offset
keyspace dbN:keys=…,expires=…,avg_ttl=… per logical database

The persistence section is where you confirm your chosen durability strategy is behaving as configured — whether you rely on RDB snapshots, an AOF write log, or both, since each trades compactness and restore speed against how much recent data you can afford to lose.

Examples

Example 1: Checking basic server info

INFO server

Output:

# Server
redis_version:7.4.0
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:5b3f8c9a1d2e3f40
redis_mode:standalone
os:Linux 6.8.0-generic x86_64
arch_bits:64
multiplexing_api:epoll
process_id:1
run_id:8f3a1c2b9e7d4f5a6b7c8d9e0f1a2b3c4d5e6f70
tcp_port:6379
server_time_usec:1755000000000000
uptime_in_seconds:120
uptime_in_days:0
hz:10
configured_hz:10
executable:/data/redis-server
config_file:
io_threads_active:0

The real reply has dozens more lines than shown here; the values for redis_version, run_id, process_id, and uptime_in_seconds will always be specific to your instance. Time complexity is O(1) — the server is formatting counters it already maintains, not doing any keyspace work.

Example 2: Watching memory and keyspace fill up

SET session:abc123 "active" EX 60
SET user:1001:visits 42
INFO keyspace

Output:

OK
OK
# Keyspace
db0:keys=2,expires=1,avg_ttl=0

keys=2 counts both keys that were just set. expires=1 counts only session:abc123, because it was set with EX 60; user:1001:visits has no TTL, so it won’t expire on its own. If you later ran a plain SET user:1001:visits 43 without KEEPTTL, note that it would also clear a TTL if one had existed — SET always removes any existing expiration unless KEEPTTL is given.

Example 3: Counting keys and inspecting connections

SET product:5001:price 19.99
SET product:5002:price 24.50
DBSIZE
CLIENT LIST
SLOWLOG GET

Output:

OK
OK
(integer) 2
id=7 addr=127.0.0.1:52134 laddr=127.0.0.1:6379 fd=8 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=26 qbuf-free=20448 argv-mem=10 multi-mem=0 tot-mem=20506 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-net-in=182 tot-net-out=256 events=r cmd=client|list user=default redir=-1 resp=2 lib-name= lib-ver=
(empty array)

DBSIZE (O(1)) reports the key count instantly because Redis tracks it as a running counter, not by iterating. CLIENT LIST (O(N) in the number of connections) shows one line per connected client — here just the redis-cli connection running the command itself; the exact id and addr will differ every time you connect. SLOWLOG GET (O(S) in the number of entries returned) comes back empty because nothing has been slow enough yet to log — the default threshold is 10,000 microseconds.

Example 4: Watching live traffic with MONITOR

This example is illustrative only — MONITOR never returns on its own, so it cannot be run inside an automated, one-shot command sequence. In a real terminal you’d run it in one window and generate traffic from another:

MONITOR

Output (streamed continuously until you press Ctrl+C):

OK
1755000012.123456 [0 127.0.0.1:52140] "SET" "product:5001:price" "19.99"
1755000012.234567 [0 127.0.0.1:52140] "GET" "product:5001:price"
1755000013.001122 [0 127.0.0.1:52142] "EXPIRE" "session:abc123" "60"

Each line is unix-timestamp.microseconds [db client-address] "COMMAND" "arg1" "arg2" ... — an exact, ordered record of what the single-threaded command pipeline just executed, from every connected client, not only the one you’re debugging.

How It Works, Step by Step

INFO

  1. The client sends INFO, optionally with a section name.
  2. The main thread — the same thread that executes every other command — assembles the requested sections by reading in-memory counters and structures it already tracks (allocator statistics for memory, the replication backlog offset for replication, and so on).
  3. It formats those values as field:value lines grouped under # section headers and returns the whole thing as one bulk string reply.
  4. No keyspace scan happens; cost is proportional to the number of fields being formatted, not to how many keys exist, which is why INFO stays fast even with millions of keys.

MONITOR

  1. The client sends MONITOR. The server replies OK and marks that connection as a monitor.
  2. From then on, every time the main thread is about to execute any command from any client, it also writes a formatted copy — timestamp, calling client’s address, and the full command with arguments — into the output buffer of every attached monitor connection.
  3. redis-cli prints each of those lines as it arrives, giving a live, ordered feed of everything the server does.
  4. The feed continues until the connection closes. There is no server-side filter — you get every command from every client, so filtering by key pattern or command type has to happen on your end, typically by piping through something like grep.

Common Mistakes

Mistake 1: Leaving MONITOR attached on a busy server

MONITOR is a fantastic scalpel and a poor permanent fixture. Because every attached monitor gets a copy of every command the single main thread executes, running MONITOR during a load test or in an open terminal that’s forgotten about adds real overhead to every request the server handles — not just the ones you’re interested in — and on a high-throughput server that overhead is measurable. The fix isn’t a different command, it’s discipline: attach it, capture a short window of traffic, and disconnect (Ctrl+C) the moment you have what you need, rather than leaving it running as a standing dashboard.

Mistake 2: Using KEYS to count or find keys instead of DBSIZE

SET a:1 "x"
SET a:2 "y"
KEYS a:*
DBSIZE

Output:

OK
OK
1) "a:1"
2) "a:2"
(integer) 2

KEYS is O(N) over the entire keyspace and blocks the single-threaded server for the whole scan — on a dataset with millions of keys that pause is long enough to stall every other client, which is why it’s considered dangerous in production. This example only works because the dataset is tiny. If you just need a total count, use DBSIZE (O(1), shown above) instead. If you need to iterate matching keys safely at scale, use the cursor-based SCAN command, which returns results incrementally without blocking.

Mistake 3: Assuming the default INFO output includes everything

SET demo:key "value"
INFO commandstats

Output:

OK
# Commandstats
cmdstat_set:calls=1,usec=12,usec_per_call=12.00,rejected_calls=0,failed_calls=0

Calling plain INFO with no section does not include commandstats or latencystats — they’re opt-in because they can grow large on a server that’s been running a long time with many distinct commands. A script that greps for cmdstat_ in default INFO output and finds nothing isn’t looking at a bug; it’s looking at a section it never requested. Always request the section explicitly when you need it.

Best Practices

  • Prefer a specific INFO section (e.g. INFO memory) over the full default output when polling programmatically — smaller payload, faster to parse, and easier to pull the one or two fields you actually track.
  • Wire up continuous monitoring with an exporter or agent that polls INFO on a schedule (every 5–15 seconds is typical) rather than relying on someone remembering to run commands by hand during an incident.
  • Treat MONITOR as a short-lived diagnostic tool, not a standing dashboard — attach it, capture what you need, and disconnect as soon as you have it.
  • Watch used_memory against maxmemory, evicted_keys, rejected_connections, and blocked_clients as core early-warning fields — they signal pressure before it becomes an outage.
  • Check SLOWLOG GET periodically to catch expensive commands you’d otherwise only see by coincidence while MONITOR happened to be attached.
  • Use DBSIZE, never KEYS *, when you just need a count of keys.
  • Restrict who can run MONITOR in production via Redis ACLs — it exposes the full content of every command, including any keys or values passed as arguments, which can include sensitive data.

Practice Exercises

  1. Run INFO clients against a fresh instance and note the value of connected_clients. Open a second redis-cli session, leave it connected, and run INFO clients again from the first — connected_clients should go up by one.
  2. Set three or four keys of your choosing, then run MONITOR in one terminal while sending GET/SET commands from a second terminal. Confirm every command you send appears in the MONITOR feed with a timestamp, then disconnect with Ctrl+C.
  3. Run INFO stats and find keyspace_hits and keyspace_misses. Issue a GET for a key that exists and a GET for one that doesn’t, then run INFO stats again — work out which counter moved for which lookup.

Summary

  • INFO returns a point-in-time snapshot of server-maintained counters, organized into sections like server, memory, stats, and replication; it’s cheap (O(1)) because it never scans the keyspace.
  • MONITOR streams a live, timestamped copy of every command the server executes, from every client, until you disconnect — powerful for debugging, but not free to leave attached on a busy server.
  • Use a specific INFO section for scripts and dashboards, and reserve default/full output for interactive exploration.
  • Use DBSIZE for key counts and SCAN-based iteration instead of KEYS in anything touching a nontrivial dataset.
  • SLOWLOG GET complements both — it retroactively surfaces the specific commands that were slow, without needing a MONITOR session running at the exact right moment.