github.com/Jeffail/benthos/v3@v3.65.0/lib/cache/multilevel.go (about) 1 package cache 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/Jeffail/benthos/v3/internal/docs" 9 "github.com/Jeffail/benthos/v3/internal/interop" 10 "github.com/Jeffail/benthos/v3/lib/log" 11 "github.com/Jeffail/benthos/v3/lib/metrics" 12 "github.com/Jeffail/benthos/v3/lib/types" 13 ) 14 15 //------------------------------------------------------------------------------ 16 17 func init() { 18 Constructors[TypeMultilevel] = TypeSpec{ 19 constructor: NewMultilevel, 20 Summary: ` 21 Combines multiple caches as levels, performing read-through and write-through 22 operations across them.`, 23 Footnotes: ` 24 For the Add command this cache first checks all levels except the last for the 25 key. If the key is not found it is added to the final cache level, if that 26 succeeds all higher cache levels have the key set. 27 28 ## Examples 29 30 It's possible to use multilevel to create a warm cache in memory above a cold 31 remote cache: 32 33 ` + "```yaml" + ` 34 pipeline: 35 processors: 36 - branch: 37 processors: 38 - cache: 39 resource: leveled 40 operator: get 41 key: ${! json("key") } 42 - catch: 43 - bloblang: 'root = {"err":error()}' 44 result_map: 'root.result = this' 45 46 cache_resources: 47 - label: leveled 48 multilevel: [ hot, cold ] 49 50 - label: hot 51 memory: 52 ttl: 300 53 54 - label: cold 55 memcached: 56 addresses: [ TODO:11211 ] 57 ttl: 3600 58 ` + "```" + ` 59 60 Using this config when a target key already exists in our local memory cache we 61 won't bother hitting the remote memcached instance.`, 62 config: docs.FieldComponent().Array().HasType(docs.FieldTypeString).HasDefault([]string{}), 63 } 64 } 65 66 //------------------------------------------------------------------------------ 67 68 // MultilevelConfig contains config fields for the Multilevel cache type. 69 type MultilevelConfig []string 70 71 // NewMultilevelConfig creates a MultilevelConfig populated with default values. 72 func NewMultilevelConfig() MultilevelConfig { 73 return []string{} 74 } 75 76 //------------------------------------------------------------------------------ 77 78 // Multilevel is a file system based cache implementation. 79 type Multilevel struct { 80 mgr types.Manager 81 log log.Modular 82 caches []string 83 } 84 85 // NewMultilevel creates a new Multilevel cache type. 86 func NewMultilevel(conf Config, mgr types.Manager, log log.Modular, stats metrics.Type) (types.Cache, error) { 87 if len(conf.Multilevel) < 2 { 88 return nil, fmt.Errorf("expected at least two cache levels, found %v", len(conf.Multilevel)) 89 } 90 for _, name := range conf.Multilevel { 91 if err := interop.ProbeCache(context.Background(), mgr, name); err != nil { 92 return nil, err 93 } 94 } 95 return &Multilevel{ 96 mgr: mgr, 97 log: log, 98 caches: conf.Multilevel, 99 }, nil 100 } 101 102 //------------------------------------------------------------------------------ 103 104 func (l *Multilevel) setUpToLevelPassive(i int, key string, value []byte) { 105 for j, name := range l.caches { 106 if j == i { 107 break 108 } 109 var setErr error 110 err := interop.AccessCache(context.Background(), l.mgr, name, func(c types.Cache) { 111 setErr = c.Set(key, value) 112 }) 113 if err != nil { 114 l.log.Errorf("Unable to passively set key '%v' for cache '%v': %v\n", key, name, err) 115 } 116 if setErr != nil { 117 l.log.Errorf("Unable to passively set key '%v' for cache '%v': %v\n", key, name, setErr) 118 } 119 } 120 } 121 122 // Get attempts to locate and return a cached value by its key, returns an error 123 // if the key does not exist. 124 func (l *Multilevel) Get(key string) ([]byte, error) { 125 for i, name := range l.caches { 126 var data []byte 127 var err error 128 if cerr := interop.AccessCache(context.Background(), l.mgr, name, func(c types.Cache) { 129 data, err = c.Get(key) 130 }); cerr != nil { 131 return nil, fmt.Errorf("unable to access cache '%v': %v", name, cerr) 132 } 133 if err != nil { 134 if err != types.ErrKeyNotFound { 135 return nil, err 136 } 137 } else { 138 l.setUpToLevelPassive(i, key, data) 139 return data, nil 140 } 141 } 142 return nil, types.ErrKeyNotFound 143 } 144 145 // SetWithTTL attempts to set the value of a key. 146 func (l *Multilevel) SetWithTTL(key string, value []byte, ttl *time.Duration) error { 147 for _, name := range l.caches { 148 var err error 149 if cerr := interop.AccessCache(context.Background(), l.mgr, name, func(c types.Cache) { 150 if cttl, ok := c.(types.CacheWithTTL); ok { 151 err = cttl.SetWithTTL(key, value, ttl) 152 } else { 153 err = c.Set(key, value) 154 } 155 }); cerr != nil { 156 return fmt.Errorf("unable to access cache '%v': %v", name, cerr) 157 } 158 if err != nil { 159 return err 160 } 161 } 162 return nil 163 } 164 165 // Set attempts to set the value of a key. 166 func (l *Multilevel) Set(key string, value []byte) error { 167 return l.SetWithTTL(key, value, nil) 168 } 169 170 // SetMultiWithTTL attempts to set the value of multiple keys, returns an error if any 171 // keys fail. 172 func (l *Multilevel) SetMultiWithTTL(items map[string]types.CacheTTLItem) error { 173 for _, name := range l.caches { 174 var err error 175 if cerr := interop.AccessCache(context.Background(), l.mgr, name, func(c types.Cache) { 176 if cttl, ok := c.(types.CacheWithTTL); ok { 177 err = cttl.SetMultiWithTTL(items) 178 } else { 179 sitems := make(map[string][]byte, len(items)) 180 for k, v := range items { 181 sitems[k] = v.Value 182 } 183 err = c.SetMulti(sitems) 184 } 185 }); cerr != nil { 186 return fmt.Errorf("unable to access cache '%v': %v", name, cerr) 187 } 188 if err != nil { 189 return err 190 } 191 } 192 return nil 193 } 194 195 // SetMulti attempts to set the value of multiple keys, returns an error if any 196 // keys fail. 197 func (l *Multilevel) SetMulti(items map[string][]byte) error { 198 sitems := make(map[string]types.CacheTTLItem, len(items)) 199 for k, v := range items { 200 sitems[k] = types.CacheTTLItem{ 201 Value: v, 202 } 203 } 204 return l.SetMultiWithTTL(sitems) 205 } 206 207 // AddWithTTL attempts to set the value of a key only if the key does not already exist 208 // and returns an error if the key already exists. 209 func (l *Multilevel) AddWithTTL(key string, value []byte, ttl *time.Duration) error { 210 for i := 0; i < len(l.caches)-1; i++ { 211 var err error 212 if cerr := interop.AccessCache(context.Background(), l.mgr, l.caches[i], func(c types.Cache) { 213 _, err = c.Get(key) 214 }); cerr != nil { 215 return fmt.Errorf("unable to access cache '%v': %v", l.caches[i], cerr) 216 } 217 if err != nil { 218 if err != types.ErrKeyNotFound { 219 return err 220 } 221 } else { 222 return types.ErrKeyAlreadyExists 223 } 224 } 225 226 var err error 227 if cerr := interop.AccessCache(context.Background(), l.mgr, l.caches[len(l.caches)-1], func(c types.Cache) { 228 if cttl, ok := c.(types.CacheWithTTL); ok { 229 err = cttl.AddWithTTL(key, value, ttl) 230 } else { 231 err = c.Add(key, value) 232 } 233 }); cerr != nil { 234 return fmt.Errorf("unable to access cache '%v': %v", l.caches[len(l.caches)-1], cerr) 235 } 236 if err != nil { 237 return err 238 } 239 240 for i := len(l.caches) - 2; i >= 0; i-- { 241 if cerr := interop.AccessCache(context.Background(), l.mgr, l.caches[i], func(c types.Cache) { 242 if cttl, ok := c.(types.CacheWithTTL); ok { 243 err = cttl.AddWithTTL(key, value, ttl) 244 } else { 245 err = c.Add(key, value) 246 } 247 }); cerr != nil { 248 return fmt.Errorf("unable to access cache '%v': %v", l.caches[i], cerr) 249 } 250 if err != nil { 251 return err 252 } 253 } 254 return nil 255 } 256 257 // Add attempts to set the value of a key only if the key does not already exist 258 // and returns an error if the key already exists. 259 func (l *Multilevel) Add(key string, value []byte) error { 260 return l.AddWithTTL(key, value, nil) 261 } 262 263 // Delete attempts to remove a key. 264 func (l *Multilevel) Delete(key string) error { 265 for _, name := range l.caches { 266 var err error 267 if cerr := interop.AccessCache(context.Background(), l.mgr, name, func(c types.Cache) { 268 err = c.Delete(key) 269 }); cerr != nil { 270 return fmt.Errorf("unable to access cache '%v': %v", name, cerr) 271 } 272 if err != nil && err != types.ErrKeyNotFound { 273 return err 274 } 275 } 276 return nil 277 } 278 279 // CloseAsync shuts down the cache. 280 func (l *Multilevel) CloseAsync() { 281 } 282 283 // WaitForClose blocks until the cache has closed down. 284 func (l *Multilevel) WaitForClose(timeout time.Duration) error { 285 return nil 286 } 287 288 //------------------------------------------------------------------------------