Browsing a Valkey keyspace safely: SCAN, INFO, and a read-only ACL user
Valkey rarely runs on its own.
It often sits in front of a database, and when a page gets slow the answer is either a cache that is not being hit or a query that got worse.
Checking both usually means one terminal on valkey-cli and another on the database, and holding the two halves of the picture in your head.
That is the situation I want to walk through, using a graphical user interface (GUI) as the example. Reaching for one against a server that is serving traffic raises two questions, and neither of them is really about the tool. The first is whether listing keys blocks the server. The second is what the tool is allowed to do once it has connected. Valkey answers both, and the useful thing a client can do is stay out of the way of those answers.
The tool in the examples is LibreDB Studio, an open source database GUI I work on, which connects to Valkey alongside the relational database in the same window.
Everything below was measured against Valkey 9.1.1 from the valkey/valkey container image with a default configuration, and every command in it you can run yourself.
Listing keys without blocking the server
KEYS is the obvious way to find out what is in a keyspace and the wrong one on a server with traffic.
Do not run it against a server that is taking production traffic.
It walks the entire keyspace in a single call and blocks the server for the duration.
On a keyspace with millions of keys that is a stall every other client sees.
SCAN exists for this reason.
It is cursor-based, and other commands run in between the calls.
COUNT is a hint and not a batch size: a single call can come back with more keys than that, with fewer, or with none at all, and it is the cursor returning to zero rather than an empty reply that tells you the scan is over.
The guarantee is weaker, which is the point: a key present for the whole scan is returned at least once, but a scan that overlaps with writes samples a moving keyspace rather than snapshotting a still one.
LibreDB Studio's key explorer is built on that.
It issues SCAN with COUNT 100 and keeps following the cursor until it has collected 1000 keys or the scan finishes, whichever comes first.
It groups what it collected by the text before the first colon and shows the groups as rows:
user:* 2
session:* 1
queue:* 1
Two things about that display are worth stating, because a table of names invites the wrong reading.
user:* is a grouping derived from key names the scan happened to see, not an object on the server, so nothing can be addressed by it.
And the counts are the sample, not the keyspace.
The total key count shown elsewhere comes from DBSIZE, which is the count for the database you have selected on the node you are connected to.
The effect is that opening the explorer against a busy server costs a bounded number of SCAN calls.
To look inside one prefix you send SCAN 0 MATCH session:* COUNT 50 yourself rather than asking anything to enumerate the keyspace for you.
Connecting as a user that cannot write
The second question is authorization, and no client has a good answer to it. A read-only switch in an interface is a property of that interface. Anyone who can reach the server can open a connection that does not have the switch. The privileges have to come from Valkey.
An access control list (ACL) user does that. A client that authenticates with a username and password rather than a password alone reaches the server as that user, and the server enforces the rest. Here is a grant that covers everything the interface reads:
valkey-cli ACL SETUSER studio reset on '>your-password' '~*' '+@read' '-@dangerous' '+select' '+info' '+slowlog|get' '+client|list' '+ping'
Each piece of it maps to something on screen:
+@readcoversSCAN,TYPEandDBSIZEfor the key explorer, and the value reads behind it.-@dangeroustakes back the risky commands as a category instead of one at a time.KEYSis in it, which is the command the first half of this post is about, and so areSORT,FLUSHALLand the rest of the set Valkey itself marks as dangerous. Order matters here: a specific grant placed after a category revocation still applies, which is why+info,+slowlog|getand+client|listbelow keep working even though@dangerouslists those commands too.+selectlets the connection switch between the numbered databases a server keeps. Without it, a connection configured for anything past database 0 cannot reach it.~*is not scoped to one database, so combined with+selectthis grant covers keys in every database on the server, not only the one a client happens to open on.+infois the overview: uptime, connected clients,maxclients,used_memory, and the keyspace hit and miss counters behind the cache hit ratio.+slowlog|getis the slow command list, read withSLOWLOG GET 10. Entries include the arguments a slow command ran with, so treat this the way you would treat log access: whoever holdsstudiocan see data that passed through those arguments.+client|listis the session list, which includes each connection's address, port, and authenticated username.+pingis the connection check the client runs when it opens the connection.
reset at the front is doing more work than it looks like.
Without it the rules are added to whatever the user already had, so running this against an existing studio leaves every earlier permission in place and you get a user that reads the list above and still writes.
With it the line is the whole grant, which is the only form worth copying into a runbook.
One thing it does not cover: the password crosses the network on every AUTH.
Any connection that is not a local socket needs transport layer security (TLS) with certificate verification underneath it, not plain TCP: valkey-cli --tls --cacert <ca-file> on the command line, and the equivalent certificate-verification setting in a GUI.
Connected as studio, every panel fills in.
A write does not:
SET user:1 hacked
NOPERM User studio has no permissions to run the 'set' command
The refusal came from the server, and it reaches the query console as the error it is.
Nothing in the tool decided it, which is the property worth having: the same restriction holds for anyone who takes those credentials and connects with valkey-cli instead.
If you want writes, connect as a user that has them.
The point is that the choice is recorded in ACL GETUSER on the server rather than in a client side setting, and Valkey 9.1 makes the grant finer with database level ACLs.
What the monitoring views read
Valkey exposes its own operational state through a handful of read commands, and none of them need a client to make sense of them.
INFO returns uptime, connected clients, memory usage, and the keyspace hit and miss counters behind a cache hit ratio.
CLIENT LIST returns one line per open connection, including the ACL user it authenticated as.
SLOWLOG GET returns the commands that took the longest to run, timestamped.
DBSIZE returns the key count for the selected database.
Anyone with a terminal and the right ACL grant already has all of this; a GUI does not add access, only a place to read it.
LibreDB Studio's monitoring view is a thin layer over those same four commands:
| View | Command |
|---|---|
| Overview and metrics | INFO |
| Key count | DBSIZE |
| Sessions | CLIENT LIST |
| Slow commands | SLOWLOG GET 10 |
Each panel is the reply to one of those commands rendered as a table instead of a wall of text, nothing more.
If INFO does not publish a field, the panel behind it has nothing to show.
Anything on screen can be checked against valkey-cli in a few seconds, which is the right relationship between a graphical client and a server.
Next steps
Create the restricted user before you point anything at a server that matters.
It is one command, it survives whatever client someone reaches for next, and it is the only read-only access that holds.
Then run ACL GETUSER against the users your own tooling connects as, and see whether the answer is the one you expected.
The commands above are most of what LibreDB Studio reads from a Valkey server; the provider docs cover the rest of what the connection supports, and libredb.org has more on the tool itself.