perf: gRPC caching#1272
Conversation
|
/run-integration |
PerformanceSingle global mutex on the hot read path. func (c *QueryCache) lookup(height int64, key string) ([]byte, bool) {
c.mu.Lock()
defer c.mu.Unlock()
...
c.lru.MoveToFront(el) // write under lock on every read
return v, true
}The whole point of this cache is to absorb bursts (the PR tests 1000 concurrent requests/endpoint), but every hit serializes on one lock — including the MoveToFront pointer surgery and a map lookup on LRU recency is mostly wasted work With |
Good catch, missed it. Updated the table Also fixed stale reads in the cache |
|
@x0152 Did you consider using golang-lru or some other cache library here instead of implementing the cache logic by hand? The block-height logic is specific to Gonka, but the LRU/eviction/concurrency parts look like generic cache machinery. |
Yes, agree - rewrote the generic cache machinery to use otter/v2 (better fit here then golang-lru). ran the benchmark and got results a bit faster than the hand-written version |
Supersedes #542
Summary
Implemented a gRPC query cache in DAPI:
Endpoints
GET /admin/v1/cache/statsPOST /admin/v1/cache/stats/resetLoad test
Tested under load (localtestnet , ~5s blocks). The cache stores results per block, so many identical requests in the same block are served from one backend call (extra ones are deduplicated).
1000 concurrent requests per endpoint (bypassing the proxy, ~5s blocks, cache memory limit = 1 GiB):
/v1/governance/models/v1/models/v1/governance/pricing/v1/pricing/v1/participantsThe latency gain is small on the local testnet (tiny state, backend ~150 ms). On prod (bigger state, real load) it will be more noticeable.
Out of scope (follow-up PR)
/v1/statusand ABCI queries are not cached - they use CometBFT RPC, not the gRPC layer. Caching them needs a separate cache, so it's left for a follow-up pr