Skip to content
Merged
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# Pinned with the linter, not stable: golangci-lint v2.13.2 cannot read the
# export data of Go 1.27.2. Move both together.
- uses: actions/setup-go@v7
with:
go-version: stable
go-version: 1.27.1
cache: false
- uses: golangci/golangci-lint-action@v9
with:
Expand Down
31 changes: 29 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,38 @@ While the major version is 0, the public API may change in any release.

## [Unreleased]

## [0.8.0] - 2026-10-09

### Added

- `Options.RefreshAfter` and `ViewOptions.RefreshAfter`: a value read after it is this old
is returned at once and reloaded in the background, so a key in steady use does not go
cold. Any read with a loader refreshes, `Get` and `Lookup` included; `GetManyOrLoad`
refreshes its due keys in one `BatchLoader` call. One refresh per value; a failed one
leaves the value to its TTL, which is never extended. Off by default; it must be shorter
than the TTL.
- `Stats.Refreshes` and `Stats.RefreshErrors`, also in `ViewStats`, apart from `Loads`.
- `SetWithCost` and `GetOrLoadFuncWithCost` on `Cache` and `View`: the value's cost comes
with it instead of from the `Cost` function, for values whose size is known only where
they are produced, such as the length of the response they were decoded from.
- `Stats.Sub` and `ViewStats.Sub`: the counters gained since a previous snapshot, which is
what a metrics exporter sends each interval. A counter that went down, from a cache
created anew, comes back whole.

### Changed

- `Stats` and `ViewStats` gained fields. Code that builds them with positional fields needs
updating; code that reads fields by name does not.

### Documentation

- The comparison benchmarks include theine and sturdyc, `make bench-compare` adds a serial
`Get` and five runs of each, and the README tables are medians of those runs.
- The README compares features with otter, theine, sturdyc and ttlcache, including what
they do that this library does not, and points to sturdyc for background refresh.
they do that this library does not.
- The `GetOrLoadFunc` and `GetManyOrLoad` examples had each other's descriptions on
pkg.go.dev.
- The README and the doc comments are shorter, with nothing taken out of what they promise.

## [0.7.0] - 2026-09-30

Expand Down Expand Up @@ -178,7 +204,8 @@ First cut.
- Built-in counters via `Stats`, and an `OnEvict` callback carrying a reason.
- `ErrTooLarge` from `Set` for values that can never fit.

[Unreleased]: https://github.com/andared/sanecache/compare/v0.7.0...HEAD
[Unreleased]: https://github.com/andared/sanecache/compare/v0.8.0...HEAD
[0.8.0]: https://github.com/andared/sanecache/compare/v0.7.0...v0.8.0
[0.7.0]: https://github.com/andared/sanecache/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/andared/sanecache/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/andared/sanecache/compare/v0.4.0...v0.5.0
Expand Down
475 changes: 177 additions & 298 deletions README.md

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,31 @@ func BenchmarkGetOrLoad(b *testing.B) {
}
}

// BenchmarkGetOrLoadRefreshAfter is BenchmarkGetOrLoad with RefreshAfter set
// and nothing due yet, which is where a refreshing cache spends nearly all its
// reads: what it adds is the check of whether the value is due.
func BenchmarkGetOrLoadRefreshAfter(b *testing.B) {
c := New(Options[int, int]{
TTL: time.Hour,
RefreshAfter: 30 * time.Minute,
MaxBytes: benchKeys,
Cost: unitCost[int](),
Loader: func(_ context.Context, key int) (int, error) {
return key, nil
},
})
defer c.Close()
for i := range benchKeys {
_ = c.Set(i, i)
}

ctx := context.Background()
b.ResetTimer()
for i := range b.N {
_, _ = c.GetOrLoad(ctx, i%benchKeys)
}
}

// BenchmarkGetManyOrLoad is the warm path of a batch: every key is cached, so
// the loader never runs and what is measured is the lookup of the batch and the
// map it comes back in, against the same keys fetched one GetOrLoad at a time.
Expand Down
Loading
Loading