github.com/Jeffail/benthos/v3@v3.65.0/lib/cache/memory.go (about) 1 package cache 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/Jeffail/benthos/v3/internal/component/cache" 10 "github.com/Jeffail/benthos/v3/internal/docs" 11 "github.com/Jeffail/benthos/v3/lib/log" 12 "github.com/Jeffail/benthos/v3/lib/metrics" 13 "github.com/Jeffail/benthos/v3/lib/types" 14 "github.com/OneOfOne/xxhash" 15 ) 16 17 //------------------------------------------------------------------------------ 18 19 func init() { 20 Constructors[TypeMemory] = TypeSpec{ 21 constructor: NewMemory, 22 Summary: ` 23 Stores key/value pairs in a map held in memory. This cache is therefore reset 24 every time the service restarts. Each item in the cache has a TTL set from the 25 moment it was last edited, after which it will be removed during the next 26 compaction.`, 27 Description: ` 28 The compaction interval determines how often the cache is cleared of expired 29 items, and this process is only triggered on writes to the cache. Access to the 30 cache is blocked during this process. 31 32 Item expiry can be disabled entirely by either setting the 33 ` + "`compaction_interval`" + ` to an empty string. 34 35 The field ` + "`init_values`" + ` can be used to prepopulate the memory cache 36 with any number of key/value pairs which are exempt from TTLs: 37 38 ` + "```yaml" + ` 39 memory: 40 ttl: 60 41 init_values: 42 foo: bar 43 ` + "```" + ` 44 45 These values can be overridden during execution, at which point the configured 46 TTL is respected as usual.`, 47 FieldSpecs: docs.FieldSpecs{ 48 docs.FieldCommon("ttl", "The TTL of each item in seconds. After this period an item will be eligible for removal during the next compaction."), 49 docs.FieldCommon("compaction_interval", "The period of time to wait before each compaction, at which point expired items are removed."), 50 docs.FieldAdvanced("shards", "A number of logical shards to spread keys across, increasing the shards can have a performance benefit when processing a large number of keys."), 51 docs.FieldString( 52 "init_values", "A table of key/value pairs that should be present in the cache on initialization. This can be used to create static lookup tables.", 53 map[string]string{ 54 "Nickelback": "1995", 55 "Spice Girls": "1994", 56 "The Human League": "1977", 57 }, 58 ).Map(), 59 }, 60 } 61 } 62 63 //------------------------------------------------------------------------------ 64 65 // MemoryConfig contains config fields for the Memory cache type. 66 type MemoryConfig struct { 67 TTL int `json:"ttl" yaml:"ttl"` 68 CompactionInterval string `json:"compaction_interval" yaml:"compaction_interval"` 69 InitValues map[string]string `json:"init_values" yaml:"init_values"` 70 Shards int `json:"shards" yaml:"shards"` 71 } 72 73 // NewMemoryConfig creates a MemoryConfig populated with default values. 74 func NewMemoryConfig() MemoryConfig { 75 return MemoryConfig{ 76 TTL: 300, // 5 Mins 77 CompactionInterval: "60s", 78 InitValues: map[string]string{}, 79 Shards: 1, 80 } 81 } 82 83 //------------------------------------------------------------------------------ 84 85 type item struct { 86 value []byte 87 ts time.Time 88 } 89 90 type shard struct { 91 items map[string]item 92 ttl time.Duration 93 94 compInterval time.Duration 95 lastCompaction time.Time 96 97 mKeys metrics.StatGauge 98 mCompactions metrics.StatCounter 99 100 sync.RWMutex 101 } 102 103 func (s *shard) isExpired(i item) bool { 104 if s.compInterval == 0 { 105 return false 106 } 107 if i.ts.IsZero() { 108 return false 109 } 110 return time.Since(i.ts) >= s.ttl 111 } 112 113 func (s *shard) compaction() { 114 if s.compInterval == 0 { 115 return 116 } 117 if time.Since(s.lastCompaction) < s.compInterval { 118 return 119 } 120 s.mCompactions.Incr(1) 121 for k, v := range s.items { 122 if s.isExpired(v) { 123 delete(s.items, k) 124 } 125 } 126 s.lastCompaction = time.Now() 127 } 128 129 //------------------------------------------------------------------------------ 130 131 // NewMemory creates a new Memory cache type. 132 func NewMemory(conf Config, mgr types.Manager, log log.Modular, stats metrics.Type) (types.Cache, error) { 133 var interval time.Duration 134 if tout := conf.Memory.CompactionInterval; len(tout) > 0 { 135 var err error 136 if interval, err = time.ParseDuration(tout); err != nil { 137 return nil, fmt.Errorf("failed to parse compaction interval string: %v", err) 138 } 139 } 140 141 m := &memoryV2{} 142 if conf.Memory.Shards <= 0 { 143 return nil, fmt.Errorf("expected >=1 shards, found: %v", conf.Memory.Shards) 144 } 145 if conf.Memory.Shards == 1 { 146 m.shards = []*shard{ 147 { 148 items: map[string]item{}, 149 ttl: time.Second * time.Duration(conf.Memory.TTL), 150 151 compInterval: interval, 152 lastCompaction: time.Now(), 153 154 mKeys: stats.GetGauge("keys"), 155 mCompactions: stats.GetCounter("compaction"), 156 }, 157 } 158 } else { 159 for i := 0; i < conf.Memory.Shards; i++ { 160 m.shards = append(m.shards, &shard{ 161 items: map[string]item{}, 162 ttl: time.Second * time.Duration(conf.Memory.TTL), 163 164 compInterval: interval, 165 lastCompaction: time.Now(), 166 167 mKeys: stats.GetGauge(fmt.Sprintf("shard.%v.keys", i)), 168 mCompactions: stats.GetCounter(fmt.Sprintf("shard.%v.compaction", i)), 169 }) 170 } 171 } 172 173 for k, v := range conf.Memory.InitValues { 174 m.getShard(k).items[k] = item{ 175 value: []byte(v), 176 ts: time.Time{}, 177 } 178 } 179 180 return cache.NewV2ToV1Cache(m, stats), nil 181 } 182 183 type memoryV2 struct { 184 shards []*shard 185 } 186 187 func (m *memoryV2) getShard(key string) *shard { 188 if len(m.shards) == 1 { 189 return m.shards[0] 190 } 191 h := xxhash.New64() 192 h.WriteString(key) 193 return m.shards[h.Sum64()%uint64(len(m.shards))] 194 } 195 196 func (m *memoryV2) Get(_ context.Context, key string) ([]byte, error) { 197 shard := m.getShard(key) 198 shard.RLock() 199 k, exists := shard.items[key] 200 shard.RUnlock() 201 if !exists { 202 return nil, types.ErrKeyNotFound 203 } 204 // Simulate compaction by returning ErrKeyNotFound if ttl expired. 205 if shard.isExpired(k) { 206 return nil, types.ErrKeyNotFound 207 } 208 return k.value, nil 209 } 210 211 func (m *memoryV2) Set(_ context.Context, key string, value []byte, _ *time.Duration) error { 212 shard := m.getShard(key) 213 shard.Lock() 214 shard.compaction() 215 shard.items[key] = item{value: value, ts: time.Now()} 216 shard.mKeys.Set(int64(len(shard.items))) 217 shard.Unlock() 218 return nil 219 } 220 221 func (m *memoryV2) SetMulti(ctx context.Context, keyValues map[string]types.CacheTTLItem) error { 222 for k, v := range keyValues { 223 if err := m.Set(ctx, k, v.Value, v.TTL); err != nil { 224 return err 225 } 226 } 227 return nil 228 } 229 230 func (m *memoryV2) Add(_ context.Context, key string, value []byte, _ *time.Duration) error { 231 shard := m.getShard(key) 232 shard.Lock() 233 if _, exists := shard.items[key]; exists { 234 shard.Unlock() 235 return types.ErrKeyAlreadyExists 236 } 237 shard.compaction() 238 shard.items[key] = item{value: value, ts: time.Now()} 239 shard.mKeys.Set(int64(len(shard.items))) 240 shard.Unlock() 241 return nil 242 } 243 244 func (m *memoryV2) Delete(_ context.Context, key string) error { 245 shard := m.getShard(key) 246 shard.Lock() 247 shard.compaction() 248 delete(shard.items, key) 249 shard.mKeys.Set(int64(len(shard.items))) 250 shard.Unlock() 251 return nil 252 } 253 254 func (m *memoryV2) Close(context.Context) error { 255 return nil 256 } 257 258 //------------------------------------------------------------------------------ 259 260 // Memory is a memory based cache implementation. 261 // 262 // TODO: V4 remove this 263 // 264 // Deprecated: This implementation is no longer used. 265 type Memory struct { 266 shards []*shard 267 } 268 269 func (m *Memory) getShard(key string) *shard { 270 if len(m.shards) == 1 { 271 return m.shards[0] 272 } 273 h := xxhash.New64() 274 h.WriteString(key) 275 return m.shards[h.Sum64()%uint64(len(m.shards))] 276 } 277 278 // Get attempts to locate and return a cached value by its key, returns an error 279 // if the key does not exist. 280 // 281 // Deprecated: This implementation is no longer used. 282 func (m *Memory) Get(key string) ([]byte, error) { 283 shard := m.getShard(key) 284 shard.RLock() 285 k, exists := shard.items[key] 286 shard.RUnlock() 287 if !exists { 288 return nil, types.ErrKeyNotFound 289 } 290 // Simulate compaction by returning ErrKeyNotFound if ttl expired. 291 if shard.isExpired(k) { 292 return nil, types.ErrKeyNotFound 293 } 294 return k.value, nil 295 } 296 297 // Set attempts to set the value of a key. 298 // 299 // Deprecated: This implementation is no longer used. 300 func (m *Memory) Set(key string, value []byte) error { 301 shard := m.getShard(key) 302 shard.Lock() 303 shard.compaction() 304 shard.items[key] = item{value: value, ts: time.Now()} 305 shard.mKeys.Set(int64(len(shard.items))) 306 shard.Unlock() 307 return nil 308 } 309 310 // SetMulti attempts to set the value of multiple keys, returns an error if any 311 // keys fail. 312 // 313 // Deprecated: This implementation is no longer used. 314 func (m *Memory) SetMulti(items map[string][]byte) error { 315 for k, v := range items { 316 if err := m.Set(k, v); err != nil { 317 return err 318 } 319 } 320 return nil 321 } 322 323 // Add attempts to set the value of a key only if the key does not already exist 324 // and returns an error if the key already exists. 325 // 326 // Deprecated: This implementation is no longer used. 327 func (m *Memory) Add(key string, value []byte) error { 328 shard := m.getShard(key) 329 shard.Lock() 330 if _, exists := shard.items[key]; exists { 331 shard.Unlock() 332 return types.ErrKeyAlreadyExists 333 } 334 shard.compaction() 335 shard.items[key] = item{value: value, ts: time.Now()} 336 shard.mKeys.Set(int64(len(shard.items))) 337 shard.Unlock() 338 return nil 339 } 340 341 // Delete attempts to remove a key. 342 // 343 // Deprecated: This implementation is no longer used. 344 func (m *Memory) Delete(key string) error { 345 shard := m.getShard(key) 346 shard.Lock() 347 shard.compaction() 348 delete(shard.items, key) 349 shard.mKeys.Set(int64(len(shard.items))) 350 shard.Unlock() 351 return nil 352 } 353 354 // CloseAsync shuts down the cache. 355 // 356 // Deprecated: This implementation is no longer used. 357 func (m *Memory) CloseAsync() { 358 } 359 360 // WaitForClose blocks until the cache has closed down. 361 // 362 // Deprecated: This implementation is no longer used. 363 func (m *Memory) WaitForClose(timeout time.Duration) error { 364 return nil 365 }