profile: background-job-runner scratch namespace: compatcheck:9f31ac02
streams
-------
OK XADD ... audit * 1755500000000-0
OK XGROUP CREATE ... OK
OK XREADGROUP GROUP ... [["compatcheck:9f31ac02:audit",[[...]]]]
server and configuration
------------------------
SKIP (group skipped) pass --destructive and set COMPAT_ALLOW_DESTRUCTIVE=yes-really
summary
-------
OK 41
SKIP 2
Every managed Redis supports "Redis". They support different subsets of it, the published compatibility tables drift, and you find the gap when a deploy fails at 3am on a command you have used for two years.
So this repository does not contain a compatibility table. It contains a script
that asks the server. redis-compat-check.mjs takes the list of commands your
application actually issues, runs each one against a candidate host in a
throwaway key namespace, and prints what happened. Point it at Upstash, point it
at whatever you are considering, diff the two outputs. That is the entire
premise.
There is also a keyspace migrator, and a section arguing that a good number of readers should not migrate at all. The candidate used in the examples is a free Redis 7.2 instance.
- Two minutes to a verdict
- What Upstash is genuinely better at
- Stay on Upstash if any of these are true
- The comparison, such as it is
- Moving the keyspace
- freebase.cloud Redis, concretely
- Reaching the same instance from an AI client
- Caveats
git clone https://github.com/freebase-cloud/upstash-alternative
cd upstash-alternative/examples
npm install ioredis
export REDIS_URL='rediss://default:TOKEN@your-upstash-host:6379'
node redis-compat-check.mjsThe default profile is a background job runner — a list queue, a sorted-set
delayed queue, session hashes, an audit stream with consumer groups, a Lua rate
limiter, pub/sub invalidation. Swap in your own; the file is
examples/command-profile.json and the format is a list of argument arrays.
Then run the same thing against a candidate and diff the JSON. Details, flags
and the MONITOR recipe for building an accurate profile are in
examples/README.md.
The distinction the checker draws that matters most: UNSUPPORTED means the
server has never heard of the command, BLOCKED means it has and will not run
it. The first is a design constraint. The second is occasionally a support
ticket.
Skipping this section would make the rest of the page dishonest.
Per-request pricing. Upstash bills a pay-as-you-go plan by commands executed, with fixed-price monthly tiers as an alternative. If your traffic is spiky — a cron job, a webhook receiver, a side project with three users on Tuesday and four hundred on Thursday — paying per command is the correct shape for that workload, and paying for a resident instance is not. This is a real architectural advantage and not a marketing one.
The REST API. @upstash/redis speaks HTTP, which means it works in
environments that cannot hold a TCP socket: Cloudflare Workers, Vercel Edge
Functions, and anything else with a short-lived isolate and no connection pool.
If you are deploying to the edge, a plain RESP endpoint is not a substitute —
you would be adding a proxy to get back what you already had.
Global replication. Read replicas in multiple regions, with reads served from the nearest one. If your users are spread across continents and your latency budget is single-digit milliseconds, that is a hard problem Upstash has solved and most free tiers have not.
It is a serious product. Durable storage, TLS, HA options, compliance tiers. Treat the rest of this page as a comparison of one narrow thing — a Redis endpoint for development and small workloads — not as a claim that Upstash is replaceable in general.
- You deploy to edge runtimes. Workers, Edge Functions, Deno Deploy. The REST API is the reason you chose Upstash and the reason to keep it.
- Your traffic is genuinely bursty. Per-command billing on an idle service costs close to nothing. A migration to save that is a migration to save nothing.
- You need multi-region reads. Nothing in this repository replicates.
- You are using Upstash for QStash, Vector, Workflow or Search as well. Pulling one product out of a stack you otherwise like is how you end up maintaining two vendor relationships instead of one.
- You have a paid plan and a production SLA. freebase.cloud publishes no SLA, no uptime figure and no backup schedule, and this repository is not going to pretend otherwise. Its free tier is aimed at development, prototyping and small production workloads.
If none of those apply — if Upstash is a cache for a hobby project and the per-command meter is mostly a thing you check nervously once a month — the rest of this page is for you.
| Upstash Redis | freebase.cloud Redis | |
|---|---|---|
| Access | REST API and Redis protocol over TLS | Redis protocol on port 6379, and MCP over HTTPS |
| Version line | Redis protocol compatibility, tracked against recent releases | Redis 7.2.3 |
| Pricing model | Free plan, pay-as-you-go per command, fixed monthly tiers | Free tier |
| Edge / serverless runtimes | Yes, via HTTP client | No HTTP client; needs a TCP socket |
| Multi-region replication | Yes | No |
| AI assistant access | Via the REST API, if you write the integration | Native MCP endpoint, four tools |
| Positioning | Production, including at scale | Development, prototyping, small production |
Last verified: 2026-08-18. Plan structures move; check Upstash's pricing page before deciding anything on the strength of a row in a table on GitHub.
Note the row that decides most of these: if your code runs in a V8 isolate at the edge, the second column is not an option, and no amount of the rest matters.
REPLICAOF is not available to you on either side of a managed-to-managed
move, so the migration is a SCAN loop.
export SOURCE_REDIS_URL='rediss://default:TOKEN@source-host:6379'
export TARGET_REDIS_URL='redis://target-host:6379'
node examples/migrate-keyspace.mjs --match 'session:*' --dry-run
node examples/migrate-keyspace.mjs --match 'session:*'It uses DUMP/RESTORE where both ends agree on the serialisation format and
falls back to a per-type copy where they do not, carrying TTLs across in
milliseconds. Run it once per key pattern rather than once over * — you get
progress you can reason about, and you find out which patterns are large. The target only has to
speak RESP2, which a plain 6379 endpoint does.
What does not come across: stream consumer groups, their offsets, and
pending entry lists. Stream entries do. Recreate the groups with XGROUP CREATE after cutover, at the ID you want to resume from.
Rollback. The script never writes to the source, so undoing a migration is
changing REDIS_URL back and redeploying. Keys written to the new target after
cutover do not migrate themselves backwards; if you have run live traffic,
swap the two environment variables and run the script in reverse. To clean the
target instead, every key the script wrote is listed in migrated-keys.log.
The honest approach for a cache. If the keyspace is genuinely a cache, don't migrate it. Point the application at the new host with a cold cache, accept a spike in origin traffic for an hour, and skip this section entirely. Migrating a cache is work you are doing for no reason.
Redis 7.2.3, RESP2, port 6379, no shim in between.
redis-cli -h HOST -p 6379> SET job:9182:lock worker-3 EX 30 NX
OK
> ZADD retry:queue 1755500000 job:9182
(integer) 1
> XADD audit '*' event job.failed job 9182
"1755500123456-0"
ioredis, redis-py, go-redis, Lettuce and redis-cli connect with
the connection string and nothing else. Which is the point: the compatibility
checker in this repo runs against it unmodified, and so does yours.
Sign up at freebase.cloud, create a session, pick the Redis engine. No credit card, no cluster sizing form.
This is the part with no Upstash equivalent, and the reason this repository is where it is. The same keyspace is addressable over MCP, so an assistant can inspect and write to it directly rather than through code you wrote to expose it.
Settings → MCP → New Token, select the connection, copy the URL. With a
connection named cache you get cache_query, cache_store,
cache_list_tables and cache_annotate_table.
claude mcp add --transport http cache https://freebase.cloud/api/mcp/YOUR_TOKENCursor, in .cursor/mcp.json:
{ "mcpServers": { "cache": { "url": "https://freebase.cloud/api/mcp/YOUR_TOKEN" } } }Claude Desktop has no config-file path for remote servers — use Settings → Connectors → Add custom connector with the same URL, which the Claude setup walkthrough shows step by step. The token is a path segment rather than a header, which is why this works in clients that never implemented custom auth headers. It also means the URL is the credential: keep it out of committed config.
Worth doing early: annotate the keyspace. cache_annotate_table attaches a
description that the model reads before it writes, and "keys under session:
expire in one hour, never write to audit: without an XADD" is the difference
between a useful assistant and a confidently wrong one.
- Redis is memory. Persisted across reconnects, but size the keyspace like a cache tier with a durable store behind it. What the free tier is for: development, prototyping, and production workloads that stay small.
- No published SLA, uptime number or backup schedule. None is claimed here because none is verified. If that sentence is a problem for your use case, that is useful information and you should act on it.
KEYSstill blocks. It is in the default profile precisely so you find out whether a candidate server lets you shoot yourself.- The compatibility checker tells you about the command surface, not about
behaviour under load. A command that returns
OKon an empty database can still be the wrong choice at a million keys. - Treat the MCP endpoint as a write credential. Anything holding it can mutate the keyspace.
- Redis command reference
- RESP protocol specification
- Upstash Redis pricing and Redis compatibility
- Model Context Protocol
- Free Redis cloud instance
- How to connect Claude to Redis
freebase.cloud is an independent service and is not affiliated with Upstash, Inc. or Redis Ltd.