|
| 1 | +# Changelog |
| 2 | + |
| 3 | +All notable changes to HyperCache are recorded here. The format follows |
| 4 | +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project |
| 5 | +adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 6 | + |
| 7 | +## [Unreleased] |
| 8 | + |
| 9 | +## [2.0.0] — 2026-05-04 |
| 10 | + |
| 11 | +A modernization release. The headline themes: |
| 12 | + |
| 13 | +- Eviction is now sharded by default for concurrency-friendly throughput. |
| 14 | +- The distributed-memory backend (`DistMemory`) gained body limits, TLS, |
| 15 | + bearer-token auth, lifecycle-context cancellation, and surfaced |
| 16 | + listener errors. |
| 17 | +- A typed wrapper (`Typed[T, V]`) is available for compile-time |
| 18 | + type-safe access without the caller-side type assertions of the |
| 19 | + untyped API. |
| 20 | +- The legacy `pkg/cache` v1 store and the `longbridgeapp/assert` test |
| 21 | + dependency are gone. |
| 22 | + |
| 23 | +The full course-correction plan (Phase 0 baseline → Phase 6 file split, |
| 24 | +plus Phase 5a–5e DistMemory hardening) is in commit history. The two |
| 25 | +RFCs that informed the design decisions live under [docs/rfcs/](docs/rfcs/). |
| 26 | + |
| 27 | +### Breaking changes |
| 28 | + |
| 29 | +- **`pkg/cache` v1 removed.** All callers must use `pkg/cache/v2`. |
| 30 | +- **`longbridgeapp/assert` test dependency removed.** Tests now use |
| 31 | + `stretchr/testify/require`. Internal test code only — no impact on |
| 32 | + library consumers, but downstream contributors authoring tests |
| 33 | + against this codebase must use `require`. |
| 34 | +- **`sentinel.ErrMgmtHTTPShutdownTimeout` removed.** |
| 35 | + `ManagementHTTPServer.Shutdown` now calls `app.ShutdownWithContext` |
| 36 | + and returns the underlying ctx error directly. Callers comparing |
| 37 | + against the removed sentinel must switch to `errors.Is(err, |
| 38 | + context.DeadlineExceeded)` or equivalent. |
| 39 | +- **Sharded eviction is default-on (32 shards).** Items no longer |
| 40 | + evict in strict global LRU/LFU order — the algorithm operates |
| 41 | + independently within each shard. Total capacity is honored within |
| 42 | + ±32 (one slot of slack per shard). Use `WithEvictionShardCount(1)` |
| 43 | + to restore strict-global ordering at the cost of single-mutex |
| 44 | + contention. |
| 45 | +- **`hypercache.go` decomposed into 6 files** (`hypercache.go`, |
| 46 | + `hypercache_io.go`, `hypercache_eviction.go`, |
| 47 | + `hypercache_expiration.go`, `hypercache_dist.go`, |
| 48 | + `hypercache_construct.go`). No public API change; third-party |
| 49 | + patches against line numbers in the prior single-file layout will |
| 50 | + not apply. |
| 51 | +- **`ManagementHTTPServer` constructor order fix.** |
| 52 | + `WithMgmtReadTimeout` and `WithMgmtWriteTimeout` previously mutated |
| 53 | + struct fields *after* `fiber.New` had locked in the defaults — the |
| 54 | + options were silent no-ops. Construction order is now correct, so |
| 55 | + any code relying on the silent no-op (e.g., setting absurd values |
| 56 | + knowing they would be ignored) will see those values take effect. |
| 57 | + |
| 58 | +### Performance |
| 59 | + |
| 60 | +Measurements on Apple M4 Pro, `go test -bench`, `count=5`, benchstat. |
| 61 | +Full release snapshot captured in [bench-v2.0.0.txt](bench-v2.0.0.txt). |
| 62 | + |
| 63 | +- **Per-shard atomic `Count`.** `BenchmarkConcurrentMap_Count`: |
| 64 | + 53 → ~10 ns/op. `_CountParallel`: 1181 → ~13 ns/op. Eliminates the |
| 65 | + lock-storm that previously serialized on a single mutex during |
| 66 | + eviction-loop count checks. |
| 67 | +- **Sharded eviction algorithm** (`pkg/eviction/sharded.go`). |
| 68 | + Replaces the global eviction-algorithm mutex with 32 per-shard |
| 69 | + mutexes routed by the same hash `ConcurrentMap` uses, so a key's |
| 70 | + data shard and eviction shard align (cache-locality on Set). |
| 71 | +- **`iter.Seq2` migration** replacing channel-based `IterBuffered`. |
| 72 | + `BenchmarkConcurrentMap_All` (renamed from `_IterBuffered`): |
| 73 | + 757µs → 26.5µs/op (-96.51%). Bytes/op: 1.73 MiB → 0 B/op. |
| 74 | + Allocs/op: 230 → 0. Eliminated 32 goroutines + 32 channels per |
| 75 | + iteration. |
| 76 | +- **xxhash consolidation** (`pkg/cache/v2/hash.go`). Replaced inlined |
| 77 | + FNV-1a with `xxhash.Sum64String` folded to 32 bits. |
| 78 | + `BenchmarkConcurrentMap_GetShard`: 10.07 → 3.46 ns/op (-65.63%). |
| 79 | +- **Sharded item-aware eviction was tried and rejected** per |
| 80 | + [RFC 0001](docs/rfcs/0001-backend-owned-eviction.md). The |
| 81 | + hypothesis (duplicate-map overhead is the bottleneck) was |
| 82 | + falsified — sharded contention dominates. Code removed; lessons |
| 83 | + preserved in the RFC for future contributors. |
| 84 | + |
| 85 | +### Features |
| 86 | + |
| 87 | +- **`hypercache.Typed[T, V]` wrapper** for compile-time type-safe |
| 88 | + cache access. Wraps an existing `HyperCache[T]`; multiple `Typed` |
| 89 | + views can share one underlying cache over disjoint keyspaces. |
| 90 | + Includes `Set`, `Get`, `GetTyped` (explicit `ErrTypeMismatch`), |
| 91 | + `GetWithInfo`, `GetOrSet`, `GetMultiple`, `Remove`, `Clear`. See |
| 92 | + [hypercache_typed.go](hypercache_typed.go) and |
| 93 | + [RFC 0002 Phase 1](docs/rfcs/0002-generic-item-typing.md). Phase 2 |
| 94 | + (deep `Item[V]` generics) is v3 territory, conditional on adoption |
| 95 | + signal. |
| 96 | +- **`WithDistHTTPLimits(DistHTTPLimits)` option** for the dist |
| 97 | + transport: server `BodyLimit` / `ReadTimeout` / `WriteTimeout` / |
| 98 | + `IdleTimeout` / `Concurrency`, plus client `ResponseLimit` / |
| 99 | + `ClientTimeout`. Defaults: 16 MiB request/response body cap, 5 s |
| 100 | + read/write/client timeout, 60 s idle, fiber's 256 KiB concurrency |
| 101 | + cap. Partial overrides honored — zero fields inherit defaults. |
| 102 | +- **`WithDistHTTPAuth(DistHTTPAuth)` option** for bearer-token auth on |
| 103 | + `/internal/*` and `/health` (`Token` for the common case; |
| 104 | + `ServerVerify`/`ClientSign` hooks for JWT, mTLS-derived identity, |
| 105 | + HMAC, etc.). Constant-time token compare on the server side. The |
| 106 | + auto-created HTTP client signs every outgoing request with the |
| 107 | + same token. Mismatched-token peers are rejected with HTTP 401 |
| 108 | + (`sentinel.ErrUnauthorized`). |
| 109 | +- **TLS support** via `DistHTTPLimits.TLSConfig`. The server wraps |
| 110 | + its listener with `tls.NewListener`; the auto-created HTTP client |
| 111 | + attaches the same `*tls.Config` to its `Transport.TLSClientConfig` |
| 112 | + with ALPN forced to `http/1.1` (fiber/fasthttp doesn't speak h2). |
| 113 | + Same `*tls.Config` configures both sides — operators applying it |
| 114 | + consistently across the cluster get encrypted intra-cluster |
| 115 | + traffic out of the box. Plaintext peers handshake-fail. |
| 116 | +- **Dist server lifecycle context** — `DistMemory.LifecycleContext()` |
| 117 | + exposes a context derived from the constructor's that is canceled |
| 118 | + on `Stop()`. Replaces the prior pattern where handlers captured |
| 119 | + the constructor's `context.Background()` and never observed |
| 120 | + cancellation. In-flight handlers and replica forwards see `Done()` |
| 121 | + the moment `Stop` is called. |
| 122 | +- **`LastServeError()` accessor** on both `distHTTPServer` and |
| 123 | + `ManagementHTTPServer`. Replaces the prior `_ = serveErr` pattern |
| 124 | + that silently swallowed listener-loop crashes — operators can now |
| 125 | + surface the failure to logs/alerts. |
| 126 | +- **`Stop()` goroutine-leak fix.** Both `distHTTPServer.stop` and |
| 127 | + `ManagementHTTPServer.Shutdown` now call |
| 128 | + `app.ShutdownWithContext(ctx)` directly instead of wrapping |
| 129 | + `app.Shutdown()` in a goroutine and racing it against ctx done |
| 130 | + (which leaked the goroutine when ctx fired first). |
| 131 | +- **New sentinels:** `sentinel.ErrTypeMismatch`, |
| 132 | + `sentinel.ErrUnauthorized`. |
| 133 | + |
| 134 | +### Internal |
| 135 | + |
| 136 | +Worth surfacing for contributors: |
| 137 | + |
| 138 | +- **v2 module layout** is the file split listed under "Breaking |
| 139 | + changes" above — readability win, no API change. |
| 140 | +- **Test helpers** introduced under `tests/`: |
| 141 | + `tests/dist_cluster_helper.go::SetupInProcessCluster[RF]`, |
| 142 | + `tests/merkle_node_helper.go`, |
| 143 | + `pkg/backend/dist_memory_test_helpers.go::EnableHTTPForTest` |
| 144 | + (build tag `test`). |
| 145 | +- **Lint discipline:** 35 `nolint` directives total across the repo, |
| 146 | + each with a one-line justification. golangci-lint v2.12.1 runs |
| 147 | + clean with `--build-tags test`. |
| 148 | + |
| 149 | +### Removed |
| 150 | + |
| 151 | +- `pkg/cache` v1 (see "Breaking changes"). |
| 152 | +- `longbridgeapp/assert` test dependency (see "Breaking changes"). |
| 153 | +- `sentinel.ErrMgmtHTTPShutdownTimeout` (see "Breaking changes"). |
| 154 | +- Experimental `WithItemAwareEviction` option / `IAlgorithmItemAware` |
| 155 | + interface / `LRUItemAware` / `ShardedItemAware` types — landed |
| 156 | + briefly during the RFC 0001 spike, then torn out per the RFC's |
| 157 | + own discipline when the perf gate failed. The |
| 158 | + [RFC document](docs/rfcs/0001-backend-owned-eviction.md) preserves |
| 159 | + the measurement and the lessons. |
| 160 | + |
| 161 | +[Unreleased]: https://github.com/hyp3rd/hypercache/compare/v2.0.0...HEAD |
| 162 | +[2.0.0]: https://github.com/hyp3rd/hypercache/releases/tag/v2.0.0 |
0 commit comments