0.24.1 (2026-10-21)
0.24.0 (2026-10-18)
- decorators: a custom key= key carries the decorated function's identity (LAB-10003) (#583)
- arrow: return writable pandas frames from single-batch Arrow reads (LAB-10013) (#582) (73e57c2)
- decorators: a custom key= key carries the decorated function's identity (LAB-10003) (#583) (087d4af)
- arrow: zero-copy File-backend reads for ArrowSerializer instances and Arrow/polars results (LAB-9999) (#581) (5d4e106)
0.23.0 (2026-10-18)
- decorators: with nothing configured, @cache caches in L1 instead of dialling localhost Redis (LAB-10002) (#580)
- monitoring: removes
cachekit.monitoring.pool_monitor(PoolMonitor,PoolStats,CachedPoolMetrics),FeatureOrchestrator.set_pool_manager()and itspool_monitorproperty, andStructuredLogger.connection_pool_utilization()withHIGH_UTILIZATION_THRESHOLD. No deprecation aliases are provided. Nothing in the package ever calledset_pool_manager()or the acquire/release hooks; pool statistics remain available throughcachekit.health, which is unchanged. - logging: CachekitConfig.log_buffer_size, CachekitConfig.log_batch_size and CachekitConfig.log_flush_interval are removed, along with their env vars CACHEKIT_LOG_BUFFER_SIZE, CACHEKIT_LOG_BATCH_SIZE and CACHEKIT_LOG_FLUSH_INTERVAL. They configured the background log writer thread, which is deleted: structured log entries are now written synchronously. Passing one of these fields to the constructor now raises ValidationError. A still-exported env var is ignored, so startup is unaffected.
- config: refuse a malformed deployment UUID at load without quoting it (LAB-8772) (#570)
- config: refuse an invalid assignment to the loaded settings and write nothing (LAB-8739) (#567)
- decorators: share one call between concurrent misses on a key, run in its own task (LAB-7441) (#547)
- redis: code that reads redis-py's exception off the error stops seeing it.
BackendError.original_exceptionand__cause__for a redis-py failure are now aRedisClientError, not redis-py's exception. Code that checkedisinstance(err.original_exception, redis.exceptions.AuthenticationError)should checkissubclass(err.original_exception.exc_type, ...). ARedisBackenderror carried nooriginal_exceptionbefore; it now carries the sameRedisClientError.repr(RedisBackendConfig(...))andstr(...)no longer showredis_url; the attribute is unchanged. - observability: the tracing surface, which never traced, is removed:
MonitoringConfig.enable_tracing(passing it raisesTypeError), theenable_tracingkey ofDecoratorConfig.to_dict(),create_cache_wrapper(enable_tracing=),FeatureOrchestrator.create_span,FeatureOrchestrator.set_span_attributes,FeatureOrchestrator.record_exception,handle_cache_error(span=), andStructuredLogger.set_trace_id/clear_trace_id. A failed sync client creation or L2 GET no longer records a secondcache_operations_totalsample withoperation="exception"; it records one failure sample, as the async paths do. - config:
master_key=on@cache,@cache.secure,DecoratorConfig.secure(),EncryptionConfig,CacheSerializationHandlerandvalidate_encryption_configraisesTypeErrorfor abytes,bytearrayormemoryviewkey: passkey.hex(). OnlyEncryptionWrappertakes raw key bytes. With encryption on, such a key already failed, withAttributeError. With encryption unset, the decorator raisedConfigurationErrorfor it, and now raisesTypeError. With encryption off (encryption=False,EncryptionConfig(enabled=False)) it was accepted, and failed only when an encrypted entry was read; it now raises where it is passed. On Python 3.14,CacheSerializationHandlerread a bytes key holding ASCII hex digits as that hex; it now raises too. - decorators:
@cache(config=..., encryption=...)raisesConfigurationErrorwhen the config is encrypted (DecoratorConfig.secure(...), or a config built withencryption=EncryptionConfig(enabled=True, ...)), and@cache(config=DecoratorConfig.io(...), backend=...)raisesConfigurationError. A keyword that names noDecoratorConfigfield raisesConfigurationError, notTypeError, from@cache, from every preset but@cache.local, from theDecoratorConfigpreset classmethods and besideconfig=; so doesencryption=on@cache.secureandDecoratorConfig.secure().EncryptionWrapper(master_key=...)raisesEncryptionError, andprevious_master_keys=raisesKeyringConfigurationError, for a raw key that is not exactly 32 bytes: passbytes.fromhex(hex_key), neverhex_key.encode(). - serializers:
EncryptionWrappernow raisesConfigurationErrorat construction when its serializer's class does not declarecross_sdk_compatible = True.AutoSerializerand unmarked custom serializers are refused. This is a breaking change for code that builds the wrapper directly aroundAutoSerializer. - cachekitio: a subdomain of the two API hosts now needs
CACHEKIT_ALLOW_CUSTOM_HOST=true. A custom host with non-ASCII characters must be given in its ASCII (xn--) form. - decorators:
DecoratorConfig().backendisUNSET, notNone.UNSETis falsy, soif config.backend:reads as before, andto_dict()still reports an unset backend asNone. Code that testsconfig.backend is Noneon an unset backend must testis UNSETinstead. Type checkers now seeUNSETin the field's type, so narrowing withis not Noneand then using the value as a backend is flagged; narrow withif config.backend:oris not UNSET.DecoratorConfig(..., backend=None)and every preset withbackend=Nonenow mean L1-only instead of "resolve the default backend". To keep the old behaviour, omitbackendfrom the config.DecoratorConfig.secure(..., backend=None)and an interop config withbackend=None, which decorated before, now raiseConfigurationErrorat decoration. - encryption: encrypted entries written by earlier releases through a prefixing backend (
MemcachedBackendwith akey_prefix, the tenant-scoped Redis backend from env auto-detection orRedisBackendProvider, or a custom backend with a non-emptykey_prefix) no longer authenticate. By default each one is read once as a miss, counted asauth_tamper, recomputed and overwritten. Withfail_closed=True, every read of one raisesDecryptionAuthenticationErroruntil it expires or is deleted. Delete those entries, or move the cache to a fresh key space (a newnamespaceor Memcachedkey_prefix), rather than wait out the TTL. During a rolling upgrade, old and new processes read each other's writes as failed authentication, so give the new release a fresh key space or stop the old processes first. There is no fallback to the earlier AAD. Unaffected: aRedisBackendyou construct yourself,FileBackend,CachekitIOBackend, and interop mode.
- backends: declare with_timeout's decorated shape and restore the pool timeout after overlapping windows (LAB-4489) (#558) (93d4d93)
- backends: drop with_timeout windows a forked child inherits (LAB-4489) (#559) (70ffd59)
- cachekitio: hold a bytes api_key only wrapped when a later check raises (LAB-8288) (#529) (707aad2)
- cachekitio: keep the API key off a failed request's exception chain (LAB-8304) (#539) (6919e13)
- cachekitio: keep the API key off an interrupt raised mid-request (LAB-8339) (#541) (966f25f)
- cachekitio: re-lease a client that close_http_clients() closed (LAB-8032) (#533) (f2bf102)
- cachekitio: send X-CacheKit-TTL alone and ceil sub-second TTLs (LAB-8206) (#520) (ead1a09)
- cachekitio: validate the host the HTTP client connects to (LAB-8207) (#519) (f38fd55)
- circuit-breaker: count a probe's outcome only toward the cycle that admitted it (LAB-5374) (#565) (a2c5f2c)
- config: refuse a bytes master_key where a hex string is taken (LAB-8281) (#526) (22d330b)
- config: refuse a malformed deployment UUID at load without quoting it (LAB-8772) (#570) (277aac3)
- config: refuse an invalid assignment to the loaded settings and write nothing (LAB-8739) (#567) (29c6a59)
- decorators: count the async backed miss in cache_info() (LAB-8298) (#538) (904e4bb)
- decorators: keep backend=None L1-only on reused decorators and in DecoratorConfig (LAB-8059) (#515) (da70f3a)
- decorators: refuse an out-of-model interop return on uncached calls (LAB-5375) (#530) (2e1fba5)
- decorators: reject keywords a preset does not take, and raw keys that are not 32 bytes (LAB-8223) (#524) (9b3eb30)
- decorators: restore the stats context on every sync exit, interrupts included (LAB-8741) (#568) (d2dc83a)
- decorators: start no SWR refresh thread in a child forked without at-fork hooks (LAB-9129) (#573) (dc69e28)
- decorators: time each latency sample over the operation it names (LAB-7148) (#554) (e1a4f8f)
- decorators: with nothing configured, @cache caches in L1 instead of dialling localhost Redis (LAB-10002) (#580) (e0cbde5)
- deps: raise the data extra's pandas floor to 2.2.2 (LAB-10026) (#585) (c65e842)
- encryption: bind the backend key prefix into the AES-GCM AAD (LAB-8115) (#518) (30201ee)
- encryption: refuse bare Arrow after decrypt and a non-bool compressed AAD token (LAB-8182) (#517) (256f01d)
- encryption: refuse master_key= repeated in previous keys at decoration (LAB-8701) (#563) (97bc0b6)
- file: expire a File-backend entry at its expiry second (LAB-8073) (#514) (9312ba7)
- fork: start no thread where cachekit is first imported after a fork from C of a threaded parent (LAB-9128) (#579) (f79463d)
- interop: decode non-string map keys; run interop-mode 1.3.0 and decode-bounds 1.2.0 vectors (LAB-8307) (#537) (819460b)
- logging: honour sub-percent sampling rates and gate the remaining sinks in a hookless child (LAB-9155) (#574) (a1e5d97)
- logging: write structured entries synchronously so a forked child keeps them (LAB-9130) (#572) (e06cc4c)
- observability: remove the no-op tracing surface (LAB-6369) (#546) (ba6368b)
- redis: clear an interrupt's frames on its way out of a Redis operation (LAB-8396) (#564) (09c2b8a)
- redis: free a failed op's frames on LOADING/WRONGPASS, and log a release failure after the lock block raised (LAB-8383) (#551) (67006b5)
- redis: keep the Redis password off a failed operation's traceback (LAB-8338) (#542) (84ec76c)
- serializers: EncryptionWrapper refuses a serializer that is not cross-SDK (LAB-8195) (#521) (fc45785)
- serializers: raise SerializationError when numpy or pandas cannot load on decode (LAB-8131) (#534) (7aeb26c)
- serializers: skip the envelope probe on decrypted StandardSerializer reads (LAB-8371) (#544) (1e779a0)
- tests: draw redis_proc ports below the Kubernetes NodePort range (LAB-8072) (#516) (78d1fa6)
- cachekitio: free a failed request's frames without the cyclic GC (LAB-8378) (#552) (c0b5cab)
- decorators: build the L2 cache handler once per backend; class-based backpressure permit (LAB-7112) (#553) (25bbabe)
- decorators: share one call between concurrent misses on a key, run in its own task (LAB-7441) (#547) (b8967d3)
- file: keep entry count and bytes in counters instead of two directory scans per set (LAB-7107) (#512) (b1cfc89)
- file: narrow the FileBackend lock to the rename commit and resolve cache_dir once (LAB-7076) (#535) (6ad08ae)
- ir: ratchet four stale perf-ir budgets to main's cost (LAB-8629) (#556) (1c049a1)
- serializers: load numpy, pandas and pyarrow on first use, not at import cachekit (LAB-7093) (#509) (9533b55)
- serializers: skip the standard object_hook's .get() calls on non-marker maps (LAB-7113) (#508) (3db2941)
- telemetry: skip disabled cache-op logging and bind each Prometheus series once (LAB-7067) (#555) (fef0bb1)
- monitoring: remove the unwired PoolMonitor and StructuredLogger.connection_pool_utilization (LAB-9448) (7dcba73)
0.22.0 (2026-10-04)
- cachekitio: replace httpx with urllib3 for CachekitIO requests (LAB-7931) (#502)
- decorators: refuse an encrypting serializer at decoration (LAB-5320) (#467)
- cachekitio: replace httpx with urllib3 for CachekitIO requests (LAB-7931) (#502) (3fe6772)
- cachekitio: send a cachekit-py/<version> User-Agent (LAB-7052) (#479) (9dc474a)
- invalidation: announce each invalidation on a Redis pub/sub channel (LAB-652) (#470) (5733626)
- invalidation: detect a silent Redis peer on the listener with TCP_USER_TIMEOUT (LAB-7586) (#499) (986e4ff)
- invalidation: opt-in listener evicts L1 on other processes' invalidations (LAB-652) (#472) (1ae8f12)
- l1: a forked child starts with an empty L1 (LAB-652) (#471) (5097c12)
- arrow: reject dict values that pyarrow stores as the wrong column (LAB-7490) (#459) (5cec332)
- cachekitio: build the sync client on HTTP/1.1 with a 32-connection pool (LAB-7062) (#489) (80dbf5c)
- cachekitio: never reuse a parent's pooled connection after fork (LAB-7146) (#468) (15c1351)
- cachekitio: parse X-CacheKit-Fresh-For with the spec grammar (LAB-7838) (#504) (6cb5f4d)
- cachekitio: retry a write once on 503 with a short Retry-After (LAB-7686) (#477) (77a16a5)
- circuit-breaker: stop the decorated function's exceptions tripping the breaker (LAB-5319) (#485) (af48926)
- decorators: degrade on async lock errors instead of raising or polling (LAB-5346) (#473) (6f901f6)
- decorators: refuse an encrypting serializer at decoration (LAB-5320) (#467) (ef4a7f2)
- invalidation: delete the pre-0.20.0 twin on no-args invalidation (LAB-5308) (#464) (f8365d3)
- invalidation: recommend --py-call-uwsgi-fork-hooks, never --py-call-osafterfork, for uWSGI (LAB-7855) (#491) (3e192f7)
- invalidation: throttle the listener's reconnect WARNING to one a minute (LAB-7638) (#482) (b3a6c8a)
- l1: bound one entry to an eighth of the L1 budget (LAB-7823) (#487) (35b6791)
- metrics: size the duration and size histogram buckets for ms and bytes (LAB-7587) (#476) (f1a52bd)
- object-cache: repair L1-only caches in a forked child (LAB-7856) (#494) (0a78131)
- redaction: digest keys holding an unpaired surrogate instead of raising (LAB-7624) (#497) (000b110)
- redis: decide on hiredis before redis imports so 3.14t keeps the GIL off (LAB-7077) (#465) (c19ab6b)
- swr: release refresh slots stranded on stopped event loops (LAB-7295) (#488) (b41c626)
- cachekitio: fan out whole-function invalidation deletes, 16 at a time (LAB-7070) (#478) (72026b1)
- cachekitio: keep idle connections 390 s with TCP keepalive 60/10/3 (LAB-7061) (#483) (c695bf2)
- cachekitio: skip the uncontended double-check read and release the miss lock in the background (LAB-7064) (#480) (f4b8dfe)
- decorators: decide refresh_ttl_on_get from Fresh-For, refresh in the background (LAB-7074) (#435) (cc9bf65)
- metrics: recover the perf-ir drift on the L2 and miss paths (LAB-7801) (#481) (d200c3a)
- serializers: memoize CK frame headers and pack AutoSerializer values without rebuilding the tree (LAB-7069) (#441) (c2e907a)
0.21.0 (2026-10-03)
- reliability: removed the cachekit.reliability.profiles module, with ReliabilityProfile, ProfileConfig, PROFILE_CONFIGS, get_profile_config, get_profile_description, recommend_profile, HIGH_THROUGHPUT_THRESHOLD_RPS and LOW_THROUGHPUT_THRESHOLD_RPS; cachekit.reliability no longer re-exports ReliabilityProfile, ProfileConfig, get_profile_config or recommend_profile. No cachekit API read these values, so no runtime behaviour changes.
- encryption: a master key with no stated encryption intent fails at construction (LAB-4739) (#411)
- config: CachekitConfig.l1_enabled, CachekitConfig.enable_prometheus_metrics, CachekitConfig.backend_provider_class and CachekitIOBackendConfig.max_retries are removed, along with their env vars CACHEKIT_L1_ENABLED, CACHEKIT_ENABLE_PROMETHEUS_METRICS, CACHEKIT_BACKEND_PROVIDER_CLASS and CACHEKIT_MAX_RETRIES. Passing one of these fields to the constructor now raises ValidationError. A still-exported env var is ignored, so startup is unaffected.
- interop: an interop namespace or operation containing
..now raisesConfigurationErrorat decoration time, on every backend; the manualgenerate_interop_keyhelper raisesInteropError. Rename the segment; its keys become a full cache miss.
- encryption: a master key with no stated encryption intent fails at construction (LAB-4739) (#411) (2baad1b)
- l1-only: back off a failed background refresh before retrying it (LAB-7442) (#462) (b6f3d46)
- build: remove build-pgo target and unused PGO profiles (LAB-7151) (#426) (a8c8f16)
- cachekitio: DELETE never reports existence, so a 404 raises (LAB-5580) (#447) (e56a139)
- cachekitio: key the async client per running event loop (LAB-7102) (#433) (c6a04b9)
- cachekitio: reject the empty cache key client-side (LAB-6550) (#397) (9c2c2be)
- config: drop raw-input locals from RedactingSettings frames before raising (LAB-4766) (#386) (0722b08)
- config: honour an explicit redis_url kwarg when REDIS_URL is set (LAB-6831) (#418) (74d12c1)
- config: intent presets accept overrides of the fields they set (LAB-5361) (#390) (3d02435)
- config: redact config validation errors raised through the core schema (LAB-5213) (#404) (25baba1)
- config: remove four config fields nothing reads (LAB-4765) (#394) (4307cd5)
- decorators: async return value the cache write cannot serialize no longer counts toward the circuit breaker (LAB-7461) (#442) (e410aa7)
- decorators: key and name the registry set by a str namespace's exact value (LAB-6197) (#388) (74937d5)
- decorators: key-generation failures no longer count toward the circuit breaker (LAB-5376) (#381) (67732fb)
- decorators: L1-only encryption refusal names the full backend resolution order (LAB-6738) (#413) (704db62)
- decorators: log failed L2 deletes of a no-args invalidation once, with the count (LAB-6074) (#383) (ff952ef)
- decorators: re-raise KeyringConfigurationError from @cache L2 reads (LAB-4841) (#393) (eb6a6e4)
- decorators: re-raise KeyringConfigurationError from @cache writes (LAB-6519) (#396) (08179ba)
- decorators: re-track a key on any exception from its L2 delete (LAB-6198) (#382) (5bef998)
- decorators: run a decorated function at most once when it raises BackendError (LAB-5360) (#429) (7be20d7)
- decorators: serve L1 hits whatever the circuit-breaker state (LAB-5351) (#385) (6407b58)
- deps: raise the redis-py floor to 4.6.0 and test it in CI (LAB-7560) (#474) (fe317c9)
- deps: raise urllib3 to 2.8.0 for three 2026-09-30 advisories (LAB-6743) (#412) (90d705c)
- encryption: resolve the caller's tenant on encrypted reads (LAB-4260) (#410) (0f436f1)
- file: miss on nonzero header flags; correct File backend docs (LAB-7152) (#431) (6f649c9)
- interop: hash str/int/float/bytes subclass arguments as their exact base-type value (LAB-6201) (#384) (4044866)
- interop: reject double-dot interop segments (LAB-5906) (#391) (5ade81d)
- l1: at-fork hook repairs every cache even when its drop warning raises (LAB-6370) (#392) (b75bcbb)
- l1: never clear a cache under a live lock holder on fork reset (LAB-6375) (#416) (468c7c8)
- l1: remove the old entry before eviction on update (LAB-6897) (#419) (eb7af5f)
- l1: start no cleanup thread in a child forked without at-fork hooks (LAB-7271) (#436) (c3fabda)
- memcached: pool connections per server so threads never share a socket (LAB-6744) (#414) (7d5da3a)
- memcached: raise when HashClient skips a command in its retry window (LAB-6740) (#432) (20fce0e)
- metrics: export circuit_breaker_state to Prometheus as a per-state count (LAB-6403) (#428) (1f05bd4)
- metrics: give a forked child its own batching state and record synchronously after shutdown (LAB-6779) (#434) (7b985b0)
- metrics: make the circuit breaker the only writer of circuit_breaker_state (LAB-7269) (#463) (0b27d40)
- metrics: record label-less generic counters and histograms (LAB-6389) (#403) (a57569c)
- metrics: restart the batching worker when auto-detect returns to batched mode (LAB-6376) (#407) (dab6b08)
- redis: size every pool from config (default 50) and wait on exhaustion (LAB-7457) (#444) (e2a4c55)
- redis: wire socket_keepalive into both connection pools (LAB-7150) (#427) (87ed6b8)
- reliability: count CLOSED breaker failures in a 60 s rolling window (LAB-5352) (#405) (22a4178)
- security: keep secrets wrapped in cachekit frames on raised errors (LAB-6515) (#430) (02fb730)
- serializers: classify a non-encodable compressed header as corruption (LAB-6354) (#380) (1971dd6)
- serializers: integrity-off AutoSerializer refuses a header format no writer emits (LAB-6359) (#387) (b475b6d)
- swr: log failed and skipped background refreshes at WARNING (LAB-7456) (#446) (1ba3312)
- backends: batch L2 deletes in whole-function invalidation (LAB-6075) (#408) (db0345e)
- cachekitio: return the 404 miss before raise_for_status (LAB-7066) (#423) (d2934eb)
- keys: fast-path exact primitives in key normalisation (LAB-7068) (#422) (84db725)
- keys: skip func-name regex passes that cannot change the name (LAB-7405) (#469) (21c87b9)
- redis: cache the redis.Redis client per PooledClientProvider (LAB-7075) (#421) (b3312de)
- serializers: hash checksummed Arrow/NumPy bodies once per read (LAB-6371) (#406) (df121d8)
0.20.0 (2026-09-30)
- reliability: removed cachekit.reliability.create_decorator_config and, from cachekit.reliability.profiles, get_decorator_kwargs, minimal_reliability_decorator, balanced_reliability_decorator and full_reliability_decorator. Their output raised TypeError when passed to @cache, so no working call is lost. cachekit.decorators.wrapper.create_cache_wrapper no longer accepts undeclared keyword arguments; it raises TypeError instead of ignoring them.
- decorators:
FeatureOrchestrator(exported fromcachekit.decorators.__all__) no longer definesgenerate_correlation_id,create_correlation_id,set_correlation_id,clear_correlation_id,start_request,end_request, or thecorrelation_trackerproperty.FeatureOrchestrator.handle_cache_errorno longer accepts acorrelation_idkeyword argument. An explicit caller passingcorrelation_id=now flows it through**extra_context, and its log record is unchanged. The decorator's own*_failederror records no longer carry acorrelation_idkey; that value was a per-call UUID, orNone, and nothing read it.src/cachekit/monitoring/correlation_tracking.py(CorrelationTracker,LoggerIntegratedTracker, module-levelgenerate_correlation_id/set_correlation_id/get_correlation_id/clear_correlation_id/correlation_context) is removed entirely. - metrics:
AsyncMetricsCollector.record_cache_operation()andFeatureOrchestrator.record_cache_operation()no longer accepthit. Callers that pass it now raiseTypeError.FeatureOrchestrator.record_success()no longer emits a metrics record. - config: presets apply the canonical default TTLs; drop CACHEKIT_DEFAULT_TTL (LAB-4641) (#318)
- intent: .secure rejects integrity_checking=False on both paths (LAB-3970) (#359)
- interop: Existing deployments that use
nsornsapias an interop namespace on Redis, Memcached or File backends worked before this change and must now rename the namespace. Renaming means a full cache miss for that namespace. - backends: if your deployment used cachekit under more than one tenant (a call with no tenant set counts as
default), purge the Redis entries that earlier releases wrote. In earlier releases a decorated function that resolved Redis from the environment (CACHEKIT_REDIS_URL,REDIS_URLor the localhost default), or a backend taken fromRedisBackendProvider.get_backend(), stayed bound to the tenant current when that backend was first obtained, so every tenant's L2 writes through it landed under that one tenant'st:<tenant>:prefix. That tenant keeps reading those entries as its own after the upgrade, and some hold another tenant's value. Entries written withttl=None, or kept alive byrefresh_ttl_on_get=True, never expire, and a no-argumentinvalidate_cache()reaches only those the calling tenant has read in that process since it started, because no key registry recorded them. After the last process running an earlier release has stopped, delete everyt:*key in each database cachekit uses. Run this with the Python that cachekit is installed in, since it uses cachekit's redis-py dependency; it prints0 keys leftwhen it is done:python -c 'import itertools, sys, redis; r = redis.Redis.from_url(sys.argv[1]); keys = r.scan_iter(match=sys.argv[2], count=1000); [r.unlink(*b) for b in iter(lambda: list(itertools.islice(keys, 1000)), [])]; print(sum(1 for _ in r.scan_iter(match=sys.argv[2], count=1000)), "keys left")' '<redis-url>' 't:*'. Do not use aredis-cli --scanpipeline: a key set throughkey=can contain a newline or a NUL byte, which the pipeline turns into names that match nothing, so the key survives and the pipeline still exits 0. RunFLUSHDBinstead only if that database is dedicated to cachekit. Then restart every process, because L1 keeps any entry a process read before the purge for up to the function'sttl(300 s withttl=None), and expect a cold cache. On a shared databaset:*also matches any other application's keys that start witht:, so run the same command with't:<tenant>:*'in place of't:*'fordefaultand for each tenant you have set, the tenant percent-encoded withurllib.parse.quote(tenant, safe=''), anintorUUIDtenant as itsstr()first (tenantorg:123is't:org%3A123:*'). A deployment that only ever used one tenant is unaffected.RedisBackendProvider.get_backend()no longer binds its backend to one tenant: the backend followstenant_contexton every operation and falls back to the tenant current at theget_backend()call only when the calling context has none; for a backend bound to one tenant, constructcachekit.backends.redis.provider.PerRequestRedisBackend(client, tenant)directly, withclientaredis.Redis. A no-argumentinvalidate_cache()on the Redis backend now deletes only the calling tenant's L2 entries. Settenant_contextonly to astr,bytes,intoruuid.UUID: the Redis backend checks each operation's tenant and raisesTypeErrorfor any other type, including abool, anIntEnummember and abytearray, so convert anIntEnummember withint()and abytearraywithbytes()first.intandUUIDtenants now work, keyed by theirstr()form (42and"42"sharet:42:), which no earlier release did. - backends: the decorator namespace "ck" and any "ck:*" namespace are now reserved for cachekit's internal keys and raise ConfigurationError at decoration time. Rename any function cached under namespace="ck" or a "ck:" prefix before upgrading.
- decorators: configure the live circuit breaker from @cache(circuit_breaker=...) (LAB-5340) (#340)
- decorators: refuse encryption with backend=None — L1-only held plaintext (LAB-4665) (#322)
- logging:
cachekit.logging.JsonFormatteris removed; importing it now raises ImportError.StructuredLogger.circuit_breaker_state_changeandStructuredLogger.set_correlation_idare removed and now raise AttributeError. No deprecation aliases. Useset_trace_id/clear_trace_id(orcachekit.monitoring.correlation_tracking) for request correlation, and your ownlogging.Formattersubclass for JSON output. - reliability: cachekit.reliability.create_optimized_decorator_config is renamed to cachekit.reliability.create_decorator_config. Update imports; no alias is provided. cachekit.hash_utils.fast_hash is renamed to blake3_hash (module-internal, never exported from the package).
- keys: cache keys for any serializer other than the default change identity on upgrade — no configuration change is required to be affected. A deployment using
serializer="auto","orjson","arrow", or any serializer passed as an instance was writing:1skeys and will now write:1a,:1o,:1wor anx-prefixed code. That function's entire working set recomputes once at deploy, so plan a cold cache or roll out behind existing warm-up / stampede controls. Deployments on the default serializer are unaffected: their keys were already:1sand stay:1s. Orphaned entries are also a retention question, not only a hit-rate one: once the key changes,invalidate_cache()computes the new key and can no longer reach the old copy, so a deletion for erasure, consent withdrawal or permission revocation reports success while the pre-upgrade entry survives to its TTL — or indefinitely wherettl=None. Flush the affected namespaces on upgrade if you cache personal data rather than relying on expiry. - config: CachekitConfig drops retry_on_timeout, max_retries, retry_delay_ms, early_refresh_ratio, enable_corruption_detection and max_key_size, and no longer reads CACHEKIT_RETRY_ON_TIMEOUT, CACHEKIT_MAX_RETRIES, CACHEKIT_RETRY_DELAY_MS, CACHEKIT_EARLY_REFRESH_RATIO, CACHEKIT_ENABLE_CORRUPTION_DETECTION or CACHEKIT_MAX_KEY_SIZE. None of them changed behaviour. Passing one to CachekitConfig(...) now raises ValidationError; CachekitConfig ignores a still-exported env var, so startup is unaffected. CachekitIOBackendConfig declares its own max_retries under the same prefix and still parses CACHEKIT_MAX_RETRIES; this release does not change it. Integrity checking is set per decorator (integrity_checking=), refresh-ahead timing by L1CacheConfig.swr_threshold_ratio, and Memcached retries by MemcachedBackendConfig.retry_attempts.
- decorators:
CachekitIOBackend(...)configuration errors (missing, empty or whitespace-containing API key; invalid API URL) now raisecachekit.config.ConfigurationErrorat construction instead ofValueErroror pydanticValidationError, so code catchingValueErrorthere must catchConfigurationError.@cache.io(backend=...)and@cache.io(config=...)now raiseConfigurationErrorinstead of silently ignoring the argument. Underset_default_backend(),@cache(config=...)now uses the backend insideconfig=instead of the module default.cachekit.backends.cachekitio.client.get_sync_http_client()is replaced bylease_sync_http_client(), which returns aSyncClientLease: hold the lease for as long as its.clientis used, because the client is closed when the lease is dropped. - encryption: single-tenant tenant_id defaults to "default", not a deployment UUID (LAB-4666) (#321)
- backends: server-side key registry for cross-process whole-function invalidation (LAB-651) (#343) (d918a19)
- build: publish CPython 3.14 wheels (LAB-6297) (#361) (63fbe14)
- encryption: warn once when CACHEKIT_MASTER_KEY auto-activates encryption (LAB-4642) (#319) (6c8b1da)
- backends: scope each Redis operation to the calling tenant, not the first caller (LAB-4773) (#329) (0386d44)
- cache_handler: evict on corrupt CK frame header, not just payload (LAB-4075) (#307) (ed799c1)
- cachekitio: drain lock POST/DELETE so a cancel cannot orphan a granted lock (LAB-3648) (#345) (288cb0e)
- cachekitio: pin the hpack logger at INFO so DEBUG logs cannot expose the API key (LAB-5914) (#351) (3738339)
- cachekitio: reject API keys outside the RFC 6750 bearer-token charset (LAB-5943) (#352) (c9c9a0e)
- cachekitio: reject reserved key segments client-side (LAB-2880) (#364) (44b36f1)
- config: presets apply the canonical default TTLs; drop CACHEKIT_DEFAULT_TTL (LAB-4641) (#318) (f838c60)
- config: redact inputs from backend config ValidationErrors (LAB-5038) (#334) (c949454)
- decorators: @cache.io accepts api_key= and rejects backend= (LAB-4643) (#320) (4d36167)
- decorators: accept l1_enabled on intent presets and keep config= L1 tuning (LAB-4828) (#358) (3f5c1e0)
- decorators: async miss-store records carry serializer/hit like sync (LAB-3755) (#295) (b733196)
- decorators: configure the live circuit breaker from @cache(circuit_breaker=...) (LAB-5340) (#340) (a20cc69)
- decorators: invalidate_cache resolves the same key the write path wrote (LAB-4387) (#312) (bebfc1d)
- decorators: keep a key re-recorded during whole-function invalidation (LAB-5774) (#348) (c1b125b)
- decorators: keep L2 decrypt and integrity failures out of circuit-breaker accounting (LAB-5865) (1b30f43)
- decorators: raise TypeError for an unsupported tenant id on sync as on async (LAB-5713) (6612f39)
- decorators: refuse encryption with backend=None — L1-only held plaintext (LAB-4665) (#322) (f5340f6)
- encryption: classify a non-string original_type header as corruption, not tamper (LAB-4350) (#310) (2403e7e)
- encryption: single-tenant tenant_id defaults to "default", not a deployment UUID (LAB-4666) (#321) (c5da48a)
- encryption: surface keyring config faults as KeyringConfigurationError (LAB-4818) (93cafd3)
- intent: .secure rejects integrity_checking=False on both paths (LAB-3970) (#359) (4f25185)
- interop: key str-subclass segments by their exact str value (LAB-6196) (#360) (d067787)
- interop: reject reserved namespaces ns and nsapi (LAB-5876) (#350) (6ba98a6)
- keys: put the real serializer identity in the cache key (LAB-4351) (#311) (ee65250)
- l1: restart the background cleanup thread in forked children (LAB-4772) (#328) (593c194)
- metrics: drain queued records before batched worker exits (LAB-6351) (#365) (385229b)
- metrics: record each cache operation once in cache_operations_total (LAB-3761) (#346) (d317494)
- metrics: share Prometheus metric objects across collectors (LAB-6355) (#366) (e41c868)
- redis: classify ClusterDownError as TRANSIENT (LAB-5327) (#339) (e93b693)
- redis: classify TryAgainError transient, InvalidResponse/LockError permanent (LAB-5353) (#344) (fb9baa6)
- redis: release a lock won after acquire_lock cancellation (LAB-3606) (#293) (4cfdc63)
- reliability: circuit breaker recovers from OPEN on the decorator path (LAB-5326) (#341) (9182092)
- serializers: integrity-off AutoSerializer refuses envelope-shaped values at write (LAB-6401) (#377) (ea1a039)
- serializers: refuse verified envelopes on integrity-off StandardSerializer reads (LAB-4329) (#379) (9502662)
- serializers: settle the envelope format by agreement, not by a winner (LAB-2736) (#309) (165c9b5)
- serializers: treat an entry with no serializer name as a mismatch (LAB-4432) (#373) (b3869a7)
- config: remove six CachekitConfig knobs nothing reads (LAB-4740) (#324) (27e1f95)
- decorators: remove dead correlation-ID surface (LAB-4733) (#323) (841dba6)
- logging: remove circuit_breaker_state_change, set_correlation_id and JsonFormatter (LAB-4638) (#317) (0a71361)
- reliability: remove create_decorator_config family (LAB-4637) (#338) (4214448)
- reliability: rename create_optimized_decorator_config and fast_hash (LAB-4619) (#315) (8d49e38)
0.19.0 (2026-09-22)
- logging: remove dead compat surface and Redis branding from StructuredLogger (LAB-4621) (#316)
- logging:
cachekit.logging.UltraOptimizedStructuredLoggeris nowStructuredLogger; thecachekit.logging.StructuredRedisLoggeralias is removed;cachekit.monitoring.pool_monitor.OptimizedPoolMonitoris nowPoolMonitor. No deprecation aliases are provided.get_structured_logger()is unchanged and remains the supported entry point. - logging: UltraOptimizedStructuredLogger.init no longer accepts mask_sensitive; get_structured_logger() no longer accepts mask_sensitive and now keys _logger_instances on name alone; mask_sensitive_patterns is removed; ProfileConfig.mask_sensitive_data and ProfileConfig.lazy_pii_masking are removed. All were read by nothing and toggled no behavior. Constructors/callers passing them now raise TypeError instead of silently no-op'ing. Same removal shape as L1CacheConfig.namespace_index in v0.18.0 and L1CacheConfig.invalidation_enabled in v0.16.0 (LAB-520).
- backend: bound L1 backfill by the server's remaining freshness (LAB-557) (#268) (7bd5abf)
- concurrency: free-threaded CPython support — memory-ordering fixes, gil_used=false, CI lane (LAB-511) (#265) (bda770b)
- ci: fail loudly on attestation lookup failure; decide the codecov pair (LAB-2528) (#270) (2a8b941)
- ci: make the Atheris fuzz job capable of failing + repair its dead targets (LAB-1140) (#269) (6ab0c28)
- decorators: async get hits record serializer/size/hit like the sync path (LAB-3765) (#297) (9b96fd2)
- decorators: async lock double-check L2 hits record get telemetry (LAB-3769) (#303) (683b1c7)
- decorators: honour set_default_backend() when called after decoration (LAB-4457) (#313) (d20204c)
- file: guard eviction unlink against a concurrent rename (LAB-2685) (#285) (e917a57)
- file: write every byte or fail; evict a payload that shrank under read (LAB-2682) (#272) (068adb2)
- logging: redact raw cache keys on all log paths (LAB-304) (#264) (81f97fb)
- logging: sanitise error kwarg at the structured cache-operation sinks (LAB-3666) (#301) (d27ec29)
- redis: stop lock waiters pinning executor threads (LAB-3596) (#290) (ddbeb91)
- serializers: bound forged-entry error echoes; retire columnar dead code (LAB-3131) (#289) (10a1049)
- serializers: bound untrusted msgpack decode depth and header allocation (LAB-2503) (#276) (f7c087d)
- serializers: fail closed on unverified DataFrame/Series envelopes (LAB-2736) (#304) (60da54f)
- serializers: take the format from the envelope, not the header; drop the header-gated fall-through (LAB-2736) (#308) (69db1c5)
- serializers: type ByteStorage.retrieve failures and collapse duplicated columnar decode (LAB-2736) (#287) (0aa78ca)
- logging: remove dead compat surface and Redis branding from StructuredLogger (LAB-4621) (#316) (98d616e)
- logging: remove dead PII-masking knobs (LAB-3797) (#300) (705640b)
- logging: rename UltraOptimizedStructuredLogger to StructuredLogger, OptimizedPoolMonitor to PoolMonitor (LAB-4617) (#314) (2f7c979)
0.18.0 (2026-09-03)
- L1CacheConfig.namespace_index is removed. The flag was read by nothing and toggled no behavior, but it shipped in v0.17.1 and docs/configuration.md documented a copy-pasteable L1CacheConfig(..., namespace_index=True) example — L1CacheConfig is a frozen dataclass, so constructors still passing it now raise TypeError instead of silently lying. L1Cache.invalidate_by_key(), .invalidate_by_namespace() and .invalidate_all() are removed with it; per-key L1Cache.invalidate() is unaffected. Same removal shape as L1CacheConfig.invalidation_enabled in v0.16.0 (LAB-520).
- encryption: keyring rotation — previous_master_keys + fingerprint selection (LAB-684) (#261) (e1b05ce)
- cachekitio: percent-encode cache key in request path (LAB-2846) (#279) (f000ba3)
- fuzz: commit per-target corpus seeds cargo-fuzz actually loads (LAB-1149) (#263) (1b85f56)
- l1: delete dead backed-mode SWR machinery; docs stop claiming backed SWR (LAB-388) (#256) (878ad08)
0.17.1 (2026-07-29)
- fuzz: fuzz the codec that ships — core 0.4.0, all 14 targets, fail loudly (LAB-1136) (#251) (511b1e5)
0.17.0 (2026-07-29)
- serializers: byte-aware Arrow batch sizing holds the memory bound on skewed frames (LAB-110) (#244) (e67db58)
0.16.0 (2026-07-24)
- L1CacheConfig.invalidation_enabled is removed. The flag was read by nothing and toggled no behavior; constructors passing it now raise TypeError instead of silently lying.
- security: EncryptionWrapper.deserialize fails closed on plaintext-claiming input (LAB-271) (#242) (6fcb115)
0.15.0 (2026-07-23)
- the no-op
adaptive_timeoutdecorator kwarg, theTimeoutConfignested config,DecoratorConfig.timeout, andProfileConfig.adaptive_timeoutare removed;CircuitBreaker.call()and.call_async()are removed (use should_allow_request() + record_success() / record_failure()). All were non-functional or dead in production.
- remove non-functional adaptive timeout, dead CircuitBreaker.call, unused tenacity (LAB-522) (#239) (5ade101)
0.14.0 (2026-07-22)
- cache: stale-while-revalidate for cache.io — client transport + decorator surface (LAB-381) (#228) (7841ff0)
- decorators: bind session counters to function identity, not wrapper instance (#233) (83b7c92)
- swr: LAB-381 panel fast-follow — contextvar propagation, CWE-532 redaction, orphan cut (#235) (9e276b6)
0.13.0 (2026-07-21)
- interop: opt-in interop/v1 mode — cross-SDK keys + plain-MessagePack values (LAB-245) (#220) (e77fa6d)
- security: tamper telemetry + fail-closed decrypt policy (LAB-108) (#218) (52d6a46)
- address coderabbit review — run async interop guard before L1 lookup (#227) (1ae680f)
- security: route async decorator L2 reads through get_cached_value_async (LAB-111) (#216) (7df5860), closes #159
0.12.0 (2026-07-20)
- config: CachekitConfig fields enable_compression, compression_level and max_chunk_size_mb were removed; constructing CachekitConfig with them now raises a ValidationError (their CACHEKIT_* env vars were already no-ops and remain ignored). max_value_size is now enforced: serialized envelopes larger than it (default 100MB, CACHEKIT_MAX_VALUE_SIZE) are no longer cached.
- config: enforce l1_max_size_mb + max_value_size, remove dead zlib/chunk knobs (LAB-109) (#217) (a5a347c)
- decorators: L1-only mode (backend=None) honors L1CacheConfig SWR + max_size_mb (LAB-106) (#219) (bef4c05)
- redis: honour redis_url, fix zero-config DI crash, add finite socket timeouts (LAB-352) (#224) (e45106e)
- security: fail closed on plaintext frames when encryption is enabled (LAB-241) (#215) (7e5be4a)
- serializers: AutoSerializer sets compressed metadata from the actual ByteStorage codec (#211) (47f8520)
0.11.1 (2026-06-26)
- re-read settings when CACHEKIT_MASTER_KEY appears + correct missing-key env var name (#200) (12c9fff)
0.11.0 (2026-06-20)
- orjson is no longer installed by
pip install cachekit. To use the orjson serializer (serializer="orjson" or OrjsonSerializer), installcachekit[json]. Without it, get_serializer("orjson") raises an ImportError with an actionable install hint.
0.10.1 (2026-06-19)
- canonicalize the pythonic serializer alias to auto (#192) (666d09c)
- checksum-protect numpy serialization without compression overhead (#189) (3b0565f)
- return writable numpy/Series reads and surface DataFrame/Series corruption clearly (#191) (46a3c3c)
0.10.0 (2026-06-18)
0.9.1 (2026-06-15)
- cachekitio: surface HTTP 413 as a clear permanent "value too large" error (#182) (d76d526)
- deps: bump pyo3 to 0.29 to clear RUSTSEC-2026-0176 and -0177 (#183) (beffbfc)
0.9.0 (2026-06-11)
- drop Python 3.9 support, require >=3.10 (#148) (1bb9953)
- honor user serializer under encryption via cross_sdk_compatible marker (#153) (2ad219d), closes #134
- support explicit per-function encryption opt-out (tri-state) (#151) (bf86c43)
- bound memory for large DataFrame/Arrow caching (was OOMing at real sizes) (#152) (ccd32c5)
- evict poisoned L2 entry on corruption at the read API (#177) (3a538fa), closes #159
- handle pandas nullable/extension dtypes in no-pyarrow DataFrame fallback (#176) (4de3608)
- harden cache-envelope framing and compression config resolution (#172) (d079f79)
- reject non-finite (NaN/inf) TTL to prevent immortal cache entries (#174) (d90958c)
- stop legacy RedisBackend corrupting binary payloads (#173) (82d0417)
0.8.0 (2026-05-31)
0.7.0 (2026-05-28)
- 5 issues from encrypted-payload E2E testing (#127) (b1aab22)
- conform CachekitIOBackend.acquire_lock to LockableBackend protocol (#130) (835d20b)
- preserve Python types in L1-only mode, allow cache_clear() on async (#117) (1fc506b)
0.6.1 (2026-05-16)
0.6.0 (2026-04-28)
0.5.1 (2026-03-28)
0.5.0 (2026-03-27)
0.4.1 (2026-03-23)
0.4.0 (2026-03-17)
0.3.1 (2026-02-22)
- async cache_clear() TypeError + nested numpy serialization (#54) (5a9c8ca), closes #50
- lazy-load ArrowSerializer to avoid ImportError without pyarrow (#44) (065eb23)
0.3.0 (2025-12-18)
- extend key generator with Path, UUID, Decimal, Enum, datetime, and constrained numpy support (#38) (78e025a)
0.2.3 (2025-12-18)
- downgrade to edition 2021 and MSRV 1.80 for stable Rust compatibility (#32) (9b13722)
- use AliasChoices for REDIS_URL env var fallback (#35) (274a925)
0.2.2 (2025-12-18)
0.2.1 (2025-12-17)
0.2.0 (2025-12-16)
- initial cachekit v0.1.0-alpha oss release (a0800c3)
- backend=None uses L1 (in-memory) cache which works everywhere. (7ce81e2)
- L1-only mode (backend=None) should not attempt Redis connection (0898986)
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.