Primary store
Keep the read a lookup
"All orders for this customer, still open." "How many items in this cart." These are the reads an application does a thousand times a second, and in a relational database each one is a query with a planner behind it. kevy can keep the answer ready instead.
Why this fits
A key-value store is usually rejected for application data with a single objection: but I need to look things up by something other than the key. That objection is correct, and it is what secondary indexes are for.
An index is declared, not built. You name the key pattern and the field; the write path keeps it current. A filtered list becomes a lookup again — no planner, no scan, no query.
A view goes further and keeps an aggregate current on write, so a count or a total is read rather than computed. This is what most applications are actually asking their ORM for, and it is why their database is busy.
How
# your data, written the way you would anyway
HSET order:1001 customer 881 status open total 4400
HSET order:1002 customer 881 status paid total 8400
HSET order:1003 customer 902 status open total 1200
# one index per field you want to look up by
IDX.CREATE idx:cust ON PREFIX order: FIELD customer TYPE i64 KIND range
IDX.CREATE idx:status ON PREFIX order: FIELD status TYPE str KIND range
# the read that would have been a query
IDX.QUERY idx:cust EQ 881
-> 1) "0" # cursor
2) 1) "order:1001" 2) "881"
3) "order:1002" 4) "881"
# two conditions at once
IDX.QUERY COMPOSE AND idx:cust EQ 881 idx:status EQ open
-> 1) "0"
2) 1) 1) "order:1001"
# a VIEW keeps the answer ready on the WRITE path, so the read
# never recomputes it. (the parens are separate arguments.)
VIEW.CREATE v:open881 QUERY ( AND idx:cust EQ 881 idx:status EQ open ) ORDER BY idx:cust
VIEW.QUERY v:open881
-> 1) "0"
2) 1) "order:1001" 2) "881"
Indexes and views are paid for on every write, not on read — that is the trade, and it is the right one for read-heavy serving and the wrong one for a write-heavy log. There are no joins, and there will not be: an index answers "which keys match these fields", not "join these two collections". If your read genuinely needs a join, keep it in Postgres. We wrote down what every relational workload costs here, including the ones where the answer is do not move it.