Known limitations¶
A single page enumerating everything fastcached does not currently do, so a reader evaluating the project can see the gaps in one place.
Protocol gaps¶
watch(memcached text): streaming event subscription is not implemented. Recognised commands returnOKrather than starting the stream.- SASL authentication (memcached binary): the PLAIN mechanism is
implemented and authenticates against the
--requirepasssecret. When no secret is configured the opcodes replyAuthError(as before), so non-authing clients fall back to the plain path. Other mechanisms (CRAM-MD5, SCRAM) are not supported. shutdown(memcached text): not implemented. Stop the daemon via the OS (Ctrl-C,SIGTERM, systemd, Windows service control).stats conns(memcached text): returns an empty result. fastcached does not track an active-connection registry.stats sizes(memcached text): returns a single approximate bucket because the storage engine does not track per-entry sizes bucketed by size class.AUTH(Redis): supported when--requirepassis set (single shared secret, redis-style); with no secret configured it replies the redis-compatible "no password is set" error. Per-user ACLs beyond the single optional--auth-usernameare not supported.COMMAND DOCS(Redis): replies with an empty array.COMMAND,COMMAND COUNTandCOMMAND INFOare real introspection over the dispatch table; only the human-readable metadata is absent.- Redis set algebra:
SINTER,SUNION,SDIFF,SRANDMEMBER,SMOVEandSSCANare not implemented. The rest of the set family is. - Key enumeration: there is no
KEYS,SCAN,RANDOMKEY,DBSIZEorstats cachedumpon any protocol, so a client cannot list the keyspace. Use the/metricsendpoint for aggregate counts. SELECT db(Redis): accepted as a no-op for any index — the reply is always+OK, but fastcached is a single flat keyspace, so the index is ignored rather than selecting a distinct database.- Keyspace notifications for memcached and
0xFCwrites: not fired. A memcachedset/delete/incr, and a compile-cachestoreorcache-drop, dirties a RedisWATCH(the storage layer does that for every protocol), but publishes no__keyevent@0__:*frame. Neither protocol has a convention for keyspace events, and the event names belong to the Redis verbs that own them.
Keyspace notification timing¶
notify-keyspace-events accepts the full redis flag set, x (expired) and
e (evicted) included, and both events are published — but when an
expired event fires differs from redis, and a subscriber depending on it
should know how.
The rule: expired fires when the entry is reclaimed, not when its TTL
lapses. Everything below follows from that.
- An active expiry cycle sweeps on a timer, so a key that lapses and is
never touched again is still reclaimed and still reported — redis's
hzis the analogue. It runs everyactive_expiry_interval(1sby default), examinesactive_expiry_scanentries per shard per sweep (512), and resumes where the last sweep stopped rather than restarting at the front, so its cost per cycle is fixed rather than proportional to how much is cached. It backs off towards 30s while it finds nothing and returns to the base interval the moment it does.
The delay is therefore bounded, not zero. An expired event for an
untouched key arrives at the next sweep that reaches it, which on a cache
larger than one scan budget is several cycles rather than one. Setting
active_expiry_interval: 0s (or --expiry-interval=0s) turns the cycle
off and leaves expiry purely access-driven, which is what the daemon did
before it had one.
fastcached_expiry_cycles_total is flat when the cycle is disabled or
wedged — which otherwise looks exactly like nothing having expired — and
fastcached_expiry_keys_reclaimed_total counts what it found.
- A plain
SETover a lapsed key does not fireexpired. It overwrites the record without looking up the old one, so nothing is reclaimed — you getsetand nothing else. The verbs that do reclaim on the spot are the ones that must find the key first:DEL,ADD,EXPIRE/PERSIST,APPEND,INCR/DECR,REPLACE,GETSET, and a CAS. - With the default
ApproximateLRU recency, a read does not reclaim either. The in-memory read path is deliberately non-mutating so reads on one shard run concurrently, so aGETof a lapsed key returns a miss and leaves the entry for the sweep.--lru-recency strictreclaims on reads too, at the cost of serialising them per shard. - The persistent backend (
--storage) reclaims on a read later than in memory.GETand a meta no-op probe reject a lapsed record without erasing it: a read holds only a shared lock, and mutating the tree there would break the single-writer contract. Every write verb does erase and report, exactly as the in-memory tier does, so the difference is confined to reads and is bounded by one sweep. evictedis immediate in memory and absent on disk. In-memory eviction happens during the write that caused the memory pressure, so the event goes out with it. Under--storagenoevictedevent is emitted at all: that configuration is an in-memory tier mirroring a disk tier, and neither tier's eviction removes the key on its own — an L1 drop leaves it on disk, and a disk drop leaves it in the mirror, which serves reads without consulting disk. An event for a key the nextGETreturns would be worse than none.- When one call reclaims more keys at once than the notification buffer
holds — a
maxmemoryshrink on a large cache is the realistic case — the surplus events are dropped and counted infastcached_keyspace_reclaim_events_dropped_total. A non-zero value there is the difference between "nothing expired" and "you were not told".
Storage model differences from memcached¶
- No slab allocator. fastcached uses a flat LRU.
slabs *,lru *, andlru_crawler *commands are recognised as synthetic stubs and returnOKwithout changing state. stats items,stats slabs,stats sizestherefore return synthetic output describing a single virtual class rather than real per-class breakdowns.
Storage model differences from Redis¶
- No data-structure commands (lists, sets, hashes, sorted sets, streams, bitfields). fastcached's engine is key-value only.
- No scripting (EVAL). Pub/sub (
SUBSCRIBE,PSUBSCRIBE,PUBLISH, and the__keyspace@0__/__keyevent@0__channels) and transactions (MULTI/EXEC/WATCH/DISCARD) are implemented. - No replication or cluster commands.
Operational gaps¶
- No live event subscription (
watch). Observability is via thestatssnapshot and, when--metricsis set, a Prometheus/metricsendpoint (plus a/healthzliveness probe) served on a dedicated HTTP port. -
TLS termination is opt-in and only present in builds compiled with
-DFASTCACHED_ENABLE_TLS=ON(links OpenSSL). Enable at runtime with--tls --tls-cert <pem> --tls-key <pem>. The default build links no OpenSSL and--tlsthen exits with a clear error; run behind a TLS-terminating proxy in that case. Client-certificate (mutual TLS) authentication is not yet implemented. -
A
fastcache-compile-nodefleet has no rolling upgrade. A build accepts exactly one0xFCwire version, so the moment one end moves every peer on the old version is refused. Upgrading is a flag day: stop everything, upgrade every node and every client, start again. A mismatch never fails a build — the launcher treats an unusable cache as a miss — so the cost is silent, and the only signal is server-side counters. The procedure and the counters to watch are in Upgrading a fleet.
See also Compatibility with upstream for a per-protocol completeness scorecard.