Skip to content
recached-shPublic

About

Recached - A multi-threaded Rust cache that runs on your backend and inside the browser.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Recached

Recached

A multi-threaded Rust cache that runs on your backend and inside the browser.

Every core on the server, every tab in the browser — one engine.

Docs npm Rust Wasm Apache 2.0


Every caching solution forces a choice: server-side caches like Redis mean every frontend read is a network round-trip; client-side state like Zustand or SWR means two caches — one on the server and one in every client, with manual staleness code gluing them together. Recached removes the choice.

The same Rust cache engine runs natively on your server (RESP on port 6379) and as WebAssembly inside the browser. Common Redis clients work with Recached's documented command subset. Browser reads come from local WASM memory; the WebSocket is a sync path, not a read path.

Multi-threaded is the default, not a flag. Recached executes commands on every core, over a sharded keyspace, with no configuration. Rust's ownership model makes cross-thread state sharing checkable at build time, so threading the command path is a design decision rather than a risk to be opted into. You can verify the scaling directly by varying the worker count and nothing else — though note that scaling depends on your keys being spread, not concentrated on a few hot ones.

And the round-trip is the part no server-side cache can remove. However well a cache server is tuned, sharded and scaled, every frontend read still costs a network hop, because the hop is the architecture. That is the half of Recached with no equivalent.

Note

Recached is not a full Redis replacement. It covers the subset most applications actually need: strings, expiry, counters, all collection types, transactions, pub/sub, and observable keys. Best fit: reactive UIs, session caches, browser-side API response caching, and rate limiting.

Notably absent: Lua scripting (EVAL), blocking operations (BLPOP, BRPOP, LMOVE) and streams (XADD). Your Redis client will connect unchanged, but libraries built on those primitives — BullMQ, node-redlock, rate-limiter-flexible — ship Lua and will not run. RLCHECK/RLSET cover rate limiting natively instead. Run COMMAND COUNT against a live server for the exact surface (123 commands today).

→ Full documentation, use cases, API reference, and guides at recached.dev


Install

# Docker
docker run -p 6379:6379 -p 6380:6380 ghcr.io/recached-sh/recached:latest

# Homebrew (macOS) — the tap is this repo, and Homebrew 6+ wants third-party taps trusted
brew tap recached-sh/recached https://github.com/recached-sh/recached.git
brew trust recached-sh/recached   # Homebrew 6.0+ only; older versions do not have it
brew install recached && recached-server

# Cargo (from source — the crate is not on crates.io yet)
cargo install --git https://github.com/recached-sh/recached.git recached && recached-server
# Browser / Edge (npm)
npm install recached-edge
# Rust service (embedded client — reads come from local process memory)
cargo add --git https://github.com/recached-sh/recached.git recached-embed

Important

Install recached-edge@^0.3.4. Every published version from 0.1.3 to 0.3.0 shipped without wasm-pack's snippets/ directory and failed to import at all; 0.3.1 is the first release that installs from npm. See the changelog for details.


How it works

Your backend writes to the Recached server over RESP on port 6379. The server syncs over a WebSocket on port 6380 to the browser or edge runtime, where reads are served from local WebAssembly memory. Writes flow back the same way.

Any mutation on the server is pushed to all connected browser instances automatically. Any write from the browser is pushed to the server and fanned out to other clients. Reads always come from local WASM memory — no network hop.

A Rust service can be one of those connected clients too, via recached-embed — same engine, same sync protocol, holding its slice of the cache in its own heap instead of a tab's. Useful for config-shaped data read on every request: fare tables, feature flags, tenant settings, entitlement checks.


Quick look

Backend — a RESP client using the supported command subset, port 6379:

import Redis from 'ioredis';
const cache = new Redis('redis://127.0.0.1:6379');
await cache.set('inventory:item:99', '42');

Browser — WebAssembly, port 6380:

import { createCache } from 'recached-edge';

const cache = await createCache({
  persistence: true,                        // survives page refresh via IndexedDB
  connect: { url: 'ws://127.0.0.1:6380' }, // syncs with the server
});

cache.get('inventory:item:99'); // "42" — from local WASM memory, 0 ms

Both examples are plaintext, which is the default. Set RECACHED_TLS_CERT and RECACHED_TLS_KEY and the same ports serve TLS — connect with rediss:// and wss:// instead. Before exposing either port beyond localhost, work through recached.dev/server/security: a default server has no password, no TLS, and no restriction on which web pages may open the sync socket.

6379 and 6380 are defaults, not fixtures — set RECACHED_PORT and RECACHED_WS_PORT (plus RECACHED_METRICS_PORT) to move them, which is also what running two instances on one host takes.

The browser half also runs alone. Drop connect and recached-edge never opens a socket — the same engine runs in WASM as a standalone client cache with TTLs, counters, JSON documents, glob queries, IndexedDB persistence and cross-tab sync, with no Recached server and no backend changes:

const cache = await createCache({ persistence: true, broadcastChannel: 'my-app' });
cache.setJSON('user:42', user, 60); // expires on its own, survives a refresh

What you give up is what needs a peer: pub/sub, live queries and cross-device sync. See use cases: no server at all.


Benchmarks

Recached's measured performance claim is narrow: command execution scales across worker threads. The project publishes no cross-project performance comparison, and these numbers are not one — they say nothing about how any other cache performs on this or any host.

The scaling run below changed only RECACHED_WORKER_THREADS. One binary, one workload, one fixed four-core CPU set — Intel i5-9400F, powersave governor, PIN=1 SERVER_CPUS=0-3 BENCH_CPUS=4-5, no persistence. Measured 2026-09-13 with redis-benchmark 8.10.1 (-n 1000000 -c 50 -d 64 -r 100000 -P 16):

Worker threads 1 2 4
GET 819,672 1,689,189 1,658,375
SET 316,857 580,720 769,823
INCR 330,688 602,047 761,615
Total, keys spread over 100k 1,467,217 2,871,956 3,189,812
Change from one thread baseline +96% +117%

Scaling requires your writes to be spread across keys. redis-benchmark's collection tests (LPUSH, SADD, HSET, ZADD) push every operation into a single key, and that workload does not scale — it regresses about 27%, from 1,550,566 req/s on one thread to 1,134,772 on four, because one key lives on one shard and extra workers only add contention. Which half describes your deployment depends on whether you have hot keys. See the benchmark guide for both tables.

One thread is the baseline the ratio is measured against, not a recommended deployment. This is evidence for parallel command execution on this build and host, nothing wider. Run scripts/bench-scaling.sh against the commit you plan to deploy.

On the same host, a server-side write reaches a subscribed browser over WebSocket in 151 µs at p50 (p99 698 µs) — measured with the project's own harness, since no RESP benchmark can see that path. Browser reads are a local WebAssembly memory lookup and never leave the tab.

Recached's product distinction remains the browser engine: browser reads use local WebAssembly memory and avoid a server round trip.

Maturity

Being honest about where things stand:

  • The cache server is a release candidate for cache workloads. It includes persistence, ordered primary/replica replication, TLS, hardened parsers, metrics, and load/chaos tests. It has not completed broad production validation or an independent security audit. Treat it as a cache, not a system of record.

  • The sync layer (browser sync, live queries, offline outbox, scoped auth) is beta — the invariants are specified and tested end-to-end, but the code is young and hasn't had real-world miles or third-party security review yet. Don't put the WebSocket port on the public internet for multi-tenant data without reading Sync Scopes first.

  • The embedded Rust client (recached-embed) is brand new and unpublished — it works end-to-end against a live server and is covered by a live test suite, but it has no production miles and is not on crates.io.

The road to 1.0 is hardening, not features. Bug reports from production-like use are the most valuable contribution right now.


Contributing

Bug reports, PRs, and feedback are all welcome.

  1. Fork the repo and create a branch: git checkout -b feat/my-feature
  2. Make your changes — server logic lives in server-native/, WASM bindings in wasm-edge/
  3. Run cargo test --workspace before opening a PR
  4. Open a pull request with a clear description

Open an issue before large features or architectural changes. Areas where contributions are especially welcome:

  • Benchmarks — run scripts/benchmark.sh on multi-core server hardware and share the results
  • Client examples — React, Vue, or SvelteKit demos using recached-edge
  • Bug reports — edge cases in the RESP parser, TTL eviction, pub/sub delivery, or WebSocket sync

See recached.dev/roadmap for what's planned.

Reach out: [email protected]

License

Apache License 2.0 — © 2026 ThinkGrid Labs

About

Recached - A multi-threaded Rust cache that runs on your backend and inside the browser.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages