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
- The client sends
INFO, optionally with a section name. - 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 forreplication, and so on). - It formats those values as
field:valuelines grouped under#section headers and returns the whole thing as one bulk string reply. - No keyspace scan happens; cost is proportional to the number of fields being formatted, not to how many keys exist, which is why
INFOstays fast even with millions of keys.
MONITOR
- The client sends
MONITOR. The server repliesOKand marks that connection as a monitor. - 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.
redis-cliprints each of those lines as it arrives, giving a live, ordered feed of everything the server does.- 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
INFOsection (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
INFOon a schedule (every 5–15 seconds is typical) rather than relying on someone remembering to run commands by hand during an incident. - Treat
MONITORas 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_memoryagainstmaxmemory,evicted_keys,rejected_connections, andblocked_clientsas core early-warning fields — they signal pressure before it becomes an outage. - Check
SLOWLOG GETperiodically to catch expensive commands you’d otherwise only see by coincidence whileMONITORhappened to be attached. - Use
DBSIZE, neverKEYS *, when you just need a count of keys. - Restrict who can run
MONITORin 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
- Run
INFO clientsagainst a fresh instance and note the value ofconnected_clients. Open a secondredis-clisession, leave it connected, and runINFO clientsagain from the first —connected_clientsshould go up by one. - Set three or four keys of your choosing, then run
MONITORin one terminal while sendingGET/SETcommands from a second terminal. Confirm every command you send appears in theMONITORfeed with a timestamp, then disconnect with Ctrl+C. - Run
INFO statsand findkeyspace_hitsandkeyspace_misses. Issue aGETfor a key that exists and aGETfor one that doesn’t, then runINFO statsagain — work out which counter moved for which lookup.
Summary
INFOreturns a point-in-time snapshot of server-maintained counters, organized into sections likeserver,memory,stats, andreplication; it’s cheap (O(1)) because it never scans the keyspace.MONITORstreams 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
INFOsection for scripts and dashboards, and reserve default/full output for interactive exploration. - Use
DBSIZEfor key counts andSCAN-based iteration instead ofKEYSin anything touching a nontrivial dataset. SLOWLOG GETcomplements both — it retroactively surfaces the specific commands that were slow, without needing aMONITORsession running at the exact right moment.
