Key‑Value Database with Valkey
The Key‑Value profile is for data that is part of application state and cannot be treated as a disposable result. Storage is durable and the commercial plan uses the db-* products.
Recommended operations
Use domain-specific prefixes and set a TTL only when the data should expire. NX is useful for conditional creation and short locks, but production locks need a unique token, an expiration, and safe release.
SET session:{user-id} <json> EX 3600
GET session:{user-id}
MSET profile:{user-id} <json> flags:{user-id} <json>
SET lock:{resource} <unique-token> NX EX 30Do not use KEYS * in production. Prefer SCAN with bounded work and keep value sizes under control.
Node.js and TypeScript
npm install iovalkeyimport Redis from 'iovalkey'
import { randomUUID } from 'node:crypto'
const source = process.env.VALKEY_URL
if (!source) throw new Error('VALKEY_URL is required')
const url = new URL(source)
const client = new Redis({
host: url.hostname,
port: Number(url.port),
username: decodeURIComponent(url.username),
password: decodeURIComponent(url.password),
tls: { servername: url.hostname },
})
await client.set('session:user-42', JSON.stringify({ role: 'admin' }), 'EX', 3600)
const session = await client.get('session:user-42')
const created = await client.set('lock:invoice-42', randomUUID(), 'NX', 'EX', 30)
console.log({ session, lockCreated: created === 'OK' })
await client.quit()Python
python -m pip install valkeyimport json
import os
import uuid
from urllib.parse import urlparse
from valkey import Valkey
url = urlparse(os.environ["VALKEY_URL"])
client = Valkey(
host=url.hostname, port=url.port, username=url.username, password=url.password,
ssl=True, ssl_check_hostname=True, decode_responses=True,
)
client.set("session:user-42", json.dumps({"role": "admin"}), ex=3600)
session = client.get("session:user-42")
token = str(uuid.uuid4())
lock_created = client.set("lock:invoice-42", token, nx=True, ex=30)
print({"session": session, "lock_created": lock_created})
client.close()Consistency and limits
- Use a transaction or script when related operations must be atomic.
- Make writes and retries idempotent; a lost network response does not reveal whether the server applied the operation.
- Do not treat the profile as a relational database: there are no joins, constraints, or ad hoc queries.
- Plan capacity for keys, values, application indexes, and growth margin.
- Use the Cache profile when data can be rebuilt; do not use Key‑Value only to obtain storage.
Recommended patterns
Use namespaces by domain, such as session:, lock:, and counter:, to make inspection and selective invalidation easier. Set size limits for serialized values and prefer native structures when they reduce duplication. For counters, INCRBY is atomic on the server; for related fields, a Lua script or transaction can keep the operation consistent.
Locks need a TTL and a unique token. When releasing a lock, compare the token on the server so a worker does not remove a lock that another worker acquired after expiration. Sessions should expire in line with the authentication lifecycle and have a clear TTL-renewal strategy. On retries, use idempotency keys so a repeated request does not create duplicate effects.
The Key‑Value profile is not a relational database: ad hoc queries, joins, and reports belong in the appropriate system. Back up data that truly matters at the application level and test restoration; instance persistence does not remove the need for a recovery policy.