Commit bd597b5
authored
perf(lease-read): cache LeaseProvider type assertion on Coordinate and ShardGroup (#947)
Closes #555.
## What
`Coordinate.LeaseRead` and `groupLeaseRead` performed the interface type
assertion `engine.(raftengine.LeaseProvider)` on every call (a hot path:
Redis GET and all lease reads). This caches the assertion once at
construction:
- Added `lp raftengine.LeaseProvider` field to `Coordinate`
(`kv/coordinator.go`), set once in `NewCoordinatorWithEngine` in the
same block that already asserts for the leader-loss callback.
- Added `lp raftengine.LeaseProvider` field to `ShardGroup`
(`kv/sharded_coordinator.go`), set once per group in the
`NewShardedCoordinator` loop that already asserts for the per-shard
leader-loss callback.
- `LeaseRead`, `refreshLeaseAfterDispatch`, `groupLeaseRead`, and
`leaseRefreshingTxn.maybeRefresh` now test `lp == nil` instead of
re-asserting. `groupLeaseRead` keeps a nil-group guard so
`engineForGroup`'s nil-safety is preserved.
- Added `BenchmarkLeaseRead` / `BenchmarkGroupLeaseRead`
(`kv/lease_read_benchmark_test.go`) exercising the fast path; both
assert the slow path (`LinearizableRead`) never runs so the benchmark
can't silently measure the wrong path.
The `engine` field is never reassigned after construction, so the cached
value stays valid for the object's lifetime without a lock.
## Behavior change
None expected. The assertion result is identical; it is now computed
once instead of per call. All existing lease tests pass unchanged.
## Risk
Low. The only subtlety is construction ordering in
`NewShardedCoordinator`: the `leaseRefreshingTxn` wrapper is created
earlier in the same loop iteration than `g.lp` is assigned, but it holds
a `*ShardGroup` pointer (not a copy), and `maybeRefresh` only runs at
Commit time — long after the loop finishes — so `g.lp` is always
populated by then.
## Scope note / deviation
`leaseReadEngineCtx` (`kv/raft_engine.go`) also does a per-call
`LeaseProvider` assertion, but it takes a bare `raftengine.LeaderView`
and is shared across the internal storage read path (`shard_store.go`);
it has no `Coordinate`/`ShardGroup` to cache on. The issue's accepted
direction scopes the field to `Coordinate` and `ShardGroup`, so
`leaseReadEngineCtx` is intentionally left unchanged.
## Test evidence
`go test -race ./kv/...`:
```
ok github.com/bootjp/elastickv/kv 10.499s
```
`golangci-lint --config=.golangci.yaml run ./kv/...`:
```
0 issues.
```
Benchmarks (Apple M1 Max, fast path, 0 allocs, LinearizableRead never
invoked):
```
BenchmarkLeaseRead-10 9749521 209.1 ns/op 0 B/op 0 allocs/op
BenchmarkGroupLeaseRead-10 11888186 89.07 ns/op 0 B/op 0 allocs/op
```
## Self-review
1. **Data loss** — No persistence/Raft propose/apply/snapshot path
touched. Lease-read semantics (fast vs. slow path selection) are
byte-for-byte identical; only the cached assertion replaces the per-call
one. No new error-swallowing. No risk.
2. **Concurrency / distributed failures** — `lp` is written once during
construction and only read afterward; `engine` is never reassigned, so
no lock is needed and there is no data race (confirmed by `go test
-race`). Construction ordering of `leaseRefreshingTxn` vs. `g.lp`
assignment is safe (pointer aliasing; `maybeRefresh` runs only at Commit
time). Leader-loss callback registration is unchanged.
3. **Performance** — Removes an interface type assertion from the
lease-read hot path, replacing it with a single nil check on a struct
field. Benchmarks show 0 allocs and the fast path is preserved. No extra
Raft round-trips, no new lock contention.
4. **Data consistency** — No change to MVCC visibility, OCC commit-ts
ordering, HLC ceiling, or the lease freshness bound
(`engineLeaseAckValid` and the secondary caller-side lease check are
untouched). The fallback to `LinearizableRead` when the engine lacks
`LeaseProvider` or the lease is disabled is preserved exactly.
5. **Test coverage** — Existing lease tests
(`TestCoordinate_LeaseRead_*`, `TestShardedCoordinator_LeaseRead*`,
fallback-when-engine-lacks-LeaseProvider, leader-loss registration)
cover both the cached-present and nil paths and pass unchanged. Added
two benchmarks that fail if the slow path is taken, locking in the
"assertion off the hot path" claim.3 files changed
Lines changed: 113 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
120 | 120 | | |
121 | 121 | | |
122 | 122 | | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
123 | 128 | | |
124 | 129 | | |
125 | 130 | | |
| |||
128 | 133 | | |
129 | 134 | | |
130 | 135 | | |
| 136 | + | |
131 | 137 | | |
132 | 138 | | |
133 | 139 | | |
| |||
169 | 175 | | |
170 | 176 | | |
171 | 177 | | |
172 | | - | |
173 | | - | |
174 | | - | |
175 | | - | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
176 | 190 | | |
177 | 191 | | |
178 | 192 | | |
| |||
599 | 613 | | |
600 | 614 | | |
601 | 615 | | |
602 | | - | |
603 | | - | |
| 616 | + | |
604 | 617 | | |
605 | 618 | | |
606 | | - | |
| 619 | + | |
607 | 620 | | |
608 | 621 | | |
609 | 622 | | |
| |||
760 | 773 | | |
761 | 774 | | |
762 | 775 | | |
763 | | - | |
764 | | - | |
| 776 | + | |
| 777 | + | |
765 | 778 | | |
766 | 779 | | |
767 | 780 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
27 | 35 | | |
28 | 36 | | |
29 | 37 | | |
| |||
283 | 291 | | |
284 | 292 | | |
285 | 293 | | |
286 | | - | |
287 | | - | |
| 294 | + | |
288 | 295 | | |
289 | 296 | | |
290 | | - | |
| 297 | + | |
291 | 298 | | |
292 | 299 | | |
293 | 300 | | |
| |||
577 | 584 | | |
578 | 585 | | |
579 | 586 | | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
580 | 590 | | |
581 | 591 | | |
582 | 592 | | |
583 | 593 | | |
| 594 | + | |
584 | 595 | | |
585 | 596 | | |
586 | 597 | | |
| |||
1547 | 1558 | | |
1548 | 1559 | | |
1549 | 1560 | | |
1550 | | - | |
1551 | | - | |
| 1561 | + | |
| 1562 | + | |
| 1563 | + | |
| 1564 | + | |
| 1565 | + | |
| 1566 | + | |
1552 | 1567 | | |
1553 | 1568 | | |
| 1569 | + | |
1554 | 1570 | | |
1555 | 1571 | | |
1556 | 1572 | | |
| |||
0 commit comments