github.com/xgzlucario/GigaCache@v0.0.0-20240508025442-54204e9c8a6b/bucket.go (about) 1 package cache 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "sync" 7 ) 8 9 // bucket is the data container for GigaCache. 10 type bucket struct { 11 sync.RWMutex 12 options *Options 13 14 // index is the index map for cache, mapped hash(kstr) to the position that data real stored. 15 index map[Key]Idx 16 17 // conflict map store keys that hash conflict with index. 18 cmap map[string]Idx 19 20 // data store all key-value bytes data. 21 data []byte 22 23 // runtime stats. 24 interval int 25 unused uint64 26 migrates uint64 27 evict uint64 28 probe uint64 29 } 30 31 func newBucket(options Options) *bucket { 32 return &bucket{ 33 options: &options, 34 index: make(map[Key]Idx, options.IndexSize), 35 cmap: map[string]Idx{}, 36 data: bpool.Get(options.BufferSize)[:0], 37 } 38 } 39 40 func (b *bucket) get(kstr string, key Key) ([]byte, int64, bool) { 41 // find conflict map. 42 idx, ok := b.cmap[kstr] 43 if ok && !idx.expired() { 44 _, _, val := b.find(idx) 45 return val, idx.TTL(), ok 46 } 47 48 // find index map. 49 idx, ok = b.index[key] 50 if ok && !idx.expired() { 51 _, _, val := b.find(idx) 52 return val, idx.TTL(), ok 53 } 54 55 return nil, 0, false 56 } 57 58 // map[Key]Idx ----+ 59 // | 60 // v 61 // start 62 // +-----+------------+------------+------------+------------+-----+ 63 // | ... | klen | vlen | key | value | ... | 64 // +-----+------------+------------+------------+------------+-----+ 65 // |<- varint ->|<- varint ->|<-- klen -->|<-- vlen -->| 66 // |<--------------------- entry --------------------->| 67 // 68 // set stores key-value pair into bucket. 69 func (b *bucket) set(key Key, kstr, val []byte, ts int64) { 70 // check conflict map. 71 idx, ok := b.cmap[b2s(kstr)] 72 if ok { 73 entry, kstrOld, valOld := b.find(idx) 74 75 // update inplaced. 76 if len(kstr) == len(kstrOld) && len(val) == len(valOld) { 77 copy(kstrOld, kstr) 78 copy(valOld, val) 79 b.cmap[string(kstr)] = idx.setTTL(ts) 80 return 81 } 82 83 // alloc new space. 84 b.unused += uint64(len(entry)) 85 b.cmap[string(kstr)] = b.appendEntry(kstr, val, ts) 86 return 87 } 88 89 // check index map. 90 idx, ok = b.index[key] 91 if ok { 92 entry, kstrOld, valOld := b.find(idx) 93 94 // if hash conflict, insert to cmap. 95 if !idx.expired() && !bytes.Equal(kstr, kstrOld) { 96 b.cmap[string(kstr)] = b.appendEntry(kstr, val, ts) 97 return 98 } 99 100 // update inplaced. 101 if len(kstr) == len(kstrOld) && len(val) == len(valOld) { 102 copy(kstrOld, kstr) 103 copy(valOld, val) 104 b.index[key] = idx.setTTL(ts) 105 return 106 } 107 108 // alloc new space. 109 b.unused += uint64(len(entry)) 110 } 111 112 // insert. 113 b.index[key] = b.appendEntry(kstr, val, ts) 114 } 115 116 func (b *bucket) appendEntry(kstr, val []byte, ts int64) Idx { 117 idx := newIdx(len(b.data), ts) 118 // append klen, vlen, key, val. 119 b.data = binary.AppendUvarint(b.data, uint64(len(kstr))) 120 b.data = binary.AppendUvarint(b.data, uint64(len(val))) 121 b.data = append(b.data, kstr...) 122 b.data = append(b.data, val...) 123 return idx 124 } 125 126 func (b *bucket) remove(key Key, kstr string) bool { 127 idx, ok := b.cmap[kstr] 128 if ok { 129 b.removeConflict(kstr, idx) 130 return !idx.expired() 131 } 132 133 idx, ok = b.index[key] 134 if ok { 135 b.removeIndex(key, idx) 136 return !idx.expired() 137 } 138 139 return false 140 } 141 142 func (b *bucket) setTTL(key Key, kstr string, ts int64) bool { 143 idx, ok := b.cmap[kstr] 144 if ok && !idx.expired() { 145 b.cmap[kstr] = newIdx(idx.start(), ts) 146 return true 147 } 148 149 idx, ok = b.index[key] 150 if ok && !idx.expired() { 151 b.index[key] = newIdx(idx.start(), ts) 152 return true 153 } 154 155 return false 156 } 157 158 func (b *bucket) scan(f Walker) (next bool) { 159 next = true 160 161 for _, idx := range b.cmap { 162 if idx.expired() { 163 continue 164 } 165 _, kstr, val := b.find(idx) 166 next = f(kstr, val, idx.TTL()) 167 if !next { 168 return 169 } 170 } 171 172 for _, idx := range b.index { 173 if idx.expired() { 174 continue 175 } 176 _, kstr, val := b.find(idx) 177 next = f(kstr, val, idx.TTL()) 178 if !next { 179 return 180 } 181 } 182 return 183 } 184 185 // eliminate the expired key-value pairs. 186 func (b *bucket) eliminate() { 187 var failed int 188 if b.options.DisableEvict { 189 goto MIG 190 } 191 192 b.interval++ 193 if b.interval < b.options.EvictInterval { 194 return 195 } 196 b.interval = 0 197 198 // probing 199 for key, idx := range b.cmap { 200 b.probe++ 201 if idx.expired() { 202 b.removeConflict(key, idx) 203 b.evict++ 204 } 205 } 206 207 for key, idx := range b.index { 208 b.probe++ 209 if idx.expired() { 210 b.removeIndex(key, idx) 211 b.evict++ 212 failed = 0 213 214 } else { 215 failed++ 216 if failed > maxFailCount { 217 break 218 } 219 } 220 } 221 222 MIG: 223 // check need to migrate. 224 rate := float64(b.unused) / float64(len(b.data)) 225 if rate >= b.options.MigrateRatio { 226 b.migrate() 227 } 228 } 229 230 // migrate move valid key-value pairs to the new container to save memory. 231 func (b *bucket) migrate() { 232 newData := bpool.Get(len(b.data))[:0] 233 234 // migrate data to new bucket. 235 for key, idx := range b.index { 236 if idx.expired() { 237 delete(b.index, key) 238 continue 239 } 240 // update with new position. 241 b.index[key] = newIdxx(len(newData), idx) 242 entry, _, _ := b.find(idx) 243 newData = append(newData, entry...) 244 } 245 246 for kstr, idx := range b.cmap { 247 if idx.expired() { 248 delete(b.cmap, kstr) 249 continue 250 } 251 key := Key(b.options.HashFn(kstr)) 252 // check if conflict. 253 _, ok := b.index[key] 254 if ok { 255 b.cmap[kstr] = newIdxx(len(newData), idx) 256 } else { 257 b.index[key] = newIdxx(len(newData), idx) 258 delete(b.cmap, kstr) 259 } 260 entry, _, _ := b.find(idx) 261 newData = append(newData, entry...) 262 } 263 264 bpool.Put(b.data) 265 b.data = newData 266 b.unused = 0 267 b.migrates++ 268 } 269 270 func (b *bucket) find(idx Idx) (entry, kstr, val []byte) { 271 var index = idx.start() 272 // klen 273 klen, n := binary.Uvarint(b.data[index:]) 274 index += n 275 // vlen 276 vlen, n := binary.Uvarint(b.data[index:]) 277 index += n 278 // kstr 279 kstr = b.data[index : index+int(klen)] 280 index += int(klen) 281 // val 282 val = b.data[index : index+int(vlen)] 283 index += int(vlen) 284 285 return b.data[idx.start():index], kstr, val 286 } 287 288 func (b *bucket) removeConflict(key string, idx Idx) { 289 entry, _, _ := b.find(idx) 290 b.unused += uint64(len(entry)) 291 delete(b.cmap, key) 292 } 293 294 func (b *bucket) removeIndex(key Key, idx Idx) { 295 entry, _, _ := b.find(idx) 296 b.unused += uint64(len(entry)) 297 delete(b.index, key) 298 }