github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/plugins/pi/statusescache.go (about) 1 // Copyright (c) 2021-2022 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package pi 6 7 import ( 8 "container/list" 9 "sync" 10 11 backend "github.com/decred/politeia/politeiad/backendv2" 12 "github.com/decred/politeia/politeiad/plugins/pi" 13 "github.com/decred/politeia/politeiad/plugins/ticketvote" 14 ) 15 16 // statusesCacheLimit is the cache's default maximum capacity. Note that it's 17 // a var in order to allow setting different limit values in test files. 18 var statusesCacheLimit = 1000 19 20 // proposalStatuses is a lazy loaded, memory cache that caches proposal data 21 // required to determine the proposal status at runtime such as record 22 // metadata, vote metadata, the vote status and the proposal billing status 23 // changes. The cache is necessary to improve the performance of determining a 24 // status of a proposal at runtime by reducing the number of expensive backend 25 // calls that result in the tlog tree be retrieved, which gets very expensive 26 // when a tree contains tens of thousands of ticket vote leaves. This is 27 // helpful when the cached data is not expected to change, which means that 28 // once we store the data in cache we don't need to fetch it again. 29 // 30 // Number of entries stored in cache is limited by statusesCacheLimit. If the 31 // cache is full and a new entry is being added, the oldest entry is removed 32 // from the `data` map and the `entries` list. 33 // 34 // Currently the limit is set to 1000 as we don't really need more than that 35 // as the goal of the cache is to speed up fetching the statuses of the 36 // most recent proposals. Each cache entry size is ~150bytes so the cache total 37 // size when full is expected to be ~150KB. 38 type proposalStatuses struct { 39 sync.Mutex 40 data map[string]*statusEntry // [token]statusEntry 41 entries *list.List // list of cache records tokens 42 } 43 44 // statusEntry represents a cached proposal status and proposal data required 45 // to determine the proposal status. 46 type statusEntry struct { 47 propStatus pi.PropStatusT 48 49 // The following fields cache data in order to reduce the number of backend 50 // calls required to determine the proposal status. 51 recordState backend.StateT 52 recordStatus backend.StatusT 53 voteStatus ticketvote.VoteStatusT 54 voteMetadata *ticketvote.VoteMetadata 55 billingStatusesCount int // Number of billing status changes 56 } 57 58 // get retrieves the data associated with the given token from the 59 // memory cache. If data doesn't exist in cache it returns nil. 60 func (s *proposalStatuses) get(token string) *statusEntry { 61 s.Lock() 62 defer s.Unlock() 63 64 return s.data[token] 65 } 66 67 // set stores the given entry in cache, if a cache entry associated with the 68 // token already exists it overwrites the old entry. If the cache is full and 69 // a new entry is being added, the oldest entry is removed from the cache. 70 func (s *proposalStatuses) set(token string, entry statusEntry) { 71 s.Lock() 72 defer s.Unlock() 73 74 // If an entry associated with the proposal already exists in cache 75 // overwrite the proposal status. 76 if e, ok := s.data[token]; ok { 77 if e.propStatus == entry.propStatus { 78 // Entry exists, but has not changed. No 79 // need to overwrite the existing entry. 80 return 81 } 82 s.data[token] = &entry 83 log.Debugf("proposalStatuses: updated entry %v from %v to %v", 84 token, e.propStatus, entry.propStatus) 85 return 86 } 87 88 // If entry does not exist and cache is full, then remove oldest entry 89 if s.entries.Len() == statusesCacheLimit { 90 // Remove front - oldest entry from entries list. 91 t := s.entries.Remove(s.entries.Back()).(string) 92 // Remove oldest status from map. 93 delete(s.data, t) 94 log.Debugf("proposalStatuses: removed entry %v", t) 95 } 96 97 // Store new status. 98 s.entries.PushFront(token) 99 s.data[token] = &entry 100 log.Debugf("proposalStatuses: added entry %v with status %v", 101 token, entry.propStatus) 102 }