Repository navigation
Add blog post: Browsing a Valkey keyspace safely #649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
stockholmux
merged 9 commits into
valkey-io:main
from
kaya-abdullah:blog/browsing-a-valkey-keyspace-safely
Sep 24, 2026
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
c4b18cb
blog: browsing a Valkey keyspace safely
kaya-abdullah 5e9298d
blog: correct the ACL grant and the SCAN and DBSIZE wording
kaya-abdullah e12d2f0
blog: address review feedback on the ACL grant, description and autho…
kaya-abdullah 08cb808
blog: hyphenate cursor-based and split two ACL bullets per line
kaya-abdullah b57000e
blog: rework the monitoring section and address review nits
kaya-abdullah 8900423
blog: require TLS with certificate verification for remote connections
kaya-abdullah 9e02589
blog: use snapshotting instead of photographing
kaya-abdullah 16fc8c2
blog: document cross-database key scope and diagnostic data exposure
kaya-abdullah a76fdc2
Updated publish date
stockholmux File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| --- | ||
| title: Abdullah Kaya | ||
| extra: | ||
| photo: '/assets/media/authors/kaya-abdullah.png' | ||
| github: kaya-abdullah | ||
| --- | ||
|
|
||
| Abdullah maintains LibreDB Studio, an open source database GUI that runs in the browser and connects to Valkey alongside the relational and document databases a team already runs. Most of that work is about presenting engines that behave nothing alike through one interface without pretending they are the same, which is where the Valkey key browser in this post came from. | ||
134 changes: 134 additions & 0 deletions
134
content/blog/2026-08-28-browsing-a-valkey-keyspace-safely.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| +++ | ||
| title = "Browsing a Valkey keyspace safely: SCAN, INFO, and a read-only ACL user" | ||
| date = 2026-09-24 01:01:01 | ||
| description = "Pointing a graphical client at a Valkey server that is taking traffic raises two questions: how to safely collect data from the server, and how to properly authorize the access. This post covers how LibreDB Studio answers the first and best practices for the second." | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| authors = ["kaya-abdullah"] | ||
|
|
||
| [taxonomies] | ||
| blog_type = ["How-to"] | ||
|
|
||
| [extra] | ||
| featured = false | ||
| +++ | ||
|
|
||
| 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](https://github.com/libredb/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`](https://hub.docker.com/r/valkey/valkey) container image with a default configuration, and every command in it you can run yourself. | ||
|
|
||
| ## Listing keys without blocking the server | ||
|
|
||
| [`KEYS`](https://valkey.io/commands/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`](https://valkey.io/commands/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: | ||
|
|
||
| ```text | ||
| 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`](https://valkey.io/commands/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: | ||
|
|
||
| ```bash | ||
| 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: | ||
|
|
||
| - `+@read` covers `SCAN`, [`TYPE`](https://valkey.io/commands/type/) and `DBSIZE` for the key explorer, and the value reads behind it. | ||
| - `-@dangerous` takes back the risky commands as a category instead of one at a time. | ||
| `KEYS` is in it, which is the command the first half of this post is about, and so are `SORT`, `FLUSHALL` and 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|get` and `+client|list` below keep working even though `@dangerous` lists those commands too. | ||
| - `+select` lets 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 `+select` this grant covers keys in every database on the server, not only the one a client happens to open on. | ||
| - `+info` is the overview: uptime, connected clients, `maxclients`, `used_memory`, and the keyspace hit and miss counters behind the cache hit ratio. | ||
| - `+slowlog|get` is the slow command list, read with [`SLOWLOG GET 10`](https://valkey.io/commands/slowlog-get/). | ||
| Entries include the arguments a slow command ran with, so treat this the way you would treat log access: whoever holds `studio` can see data that passed through those arguments. | ||
| - `+client|list` is the session list, which includes each connection's address, port, and authenticated username. | ||
| - `+ping` is 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`](https://valkey.io/commands/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: | ||
|
|
||
| ```text | ||
| 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`](https://valkey.io/commands/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 | ||
|
stockholmux marked this conversation as resolved.
|
||
|
|
||
| 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`](https://valkey.io/commands/info/) returns uptime, connected clients, memory usage, and the keyspace hit and miss counters behind a cache hit ratio. | ||
| [`CLIENT LIST`](https://valkey.io/commands/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 | ||
|
stockholmux marked this conversation as resolved.
|
||
|
|
||
| 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](https://github.com/libredb/libredb-studio/blob/main/docs/providers/redis.md) cover the rest of what the connection supports, and [libredb.org](https://libredb.org) has more on the tool itself. | ||
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.