github.com/creachadair/ffs@v0.17.3/storage/wbstore/wrapper.go (about) 1 // Copyright 2024 Michael J. Fromberger. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package wbstore 16 17 import ( 18 "context" 19 "fmt" 20 "iter" 21 "log" 22 "time" 23 24 "github.com/creachadair/ffs/blob" 25 "github.com/creachadair/mds/mapset" 26 "github.com/creachadair/msync/trigger" 27 "github.com/creachadair/taskgroup" 28 ) 29 30 // A kvWrapper implements the [blob.KV] interface, and manages the forwarding 31 // of cached Put requests to an underlying KV. 32 type kvWrapper struct { 33 base blob.KV 34 buf blob.KV 35 36 // The background writer waits on nempty when it finds no blobs to push. 37 nempty trigger.Cond 38 } 39 40 // run implements the backround writer. It runs until ctx terminates or until 41 // it receives an unrecoverable error. 42 func (w *kvWrapper) run(ctx context.Context) { 43 g, run := taskgroup.New(nil).Limit(32) 44 for { 45 // Check for cancellation. 46 select { 47 case <-ctx.Done(): 48 return // normal shutdown 49 case <-w.nempty.Ready(): 50 } 51 52 for key, err := range w.buf.List(ctx, "") { 53 if err != nil { 54 log.Printf("DEBUG :: error scanning buffer: %v", err) 55 break 56 } 57 if ctx.Err() != nil { 58 break 59 } 60 run(func() error { 61 // Read the blob and forward it to the base store, then delete it. 62 // Because the buffer contains only non-replacement blobs, it is 63 // safe to delete the blob even if another copy was written while 64 // we worked, since the content will be the same. If Get or Delete 65 // fails, it means someone deleted the key before us. That's fine. 66 67 data, err := w.buf.Get(ctx, key) 68 if blob.IsKeyNotFound(err) { 69 return nil 70 } else if err != nil { 71 return err 72 } 73 74 // An individual write should not be allowed to stall for too long. 75 rtctx, cancel := context.WithTimeout(ctx, 20*time.Second) 76 defer cancel() 77 perr := w.base.Put(rtctx, blob.PutOptions{ 78 Key: key, 79 Data: data, 80 Replace: false, 81 }) 82 if perr == nil || blob.IsKeyExists(perr) { 83 // OK writeback succeeded, drop this blob from the buffer. 84 if err := w.buf.Delete(ctx, key); err != nil && !blob.IsKeyNotFound(err) { 85 return err 86 } 87 return nil 88 } 89 return perr 90 }) 91 } 92 if err := g.Wait(); err != nil { 93 log.Printf("DEBUG :: error in writeback: %v", err) 94 } else if n, err := w.buf.Len(ctx); err == nil && n == 0 { 95 w.nempty.Reset() 96 } 97 } 98 } 99 100 // Get implements part of [blob.KV]. If key is in the write-behind store, its 101 // value there is returned; otherwise it is fetched from the base store. 102 func (w *kvWrapper) Get(ctx context.Context, key string) ([]byte, error) { 103 // Fetch from the buffer and the base store concurrently. A hit in the base 104 // store takes precedence, since that reflects ground truth; but if that 105 // fails we will fall back to the buffered value. 106 ctx, cancel := context.WithCancel(ctx) 107 defer cancel() 108 buf := taskgroup.Call(func() ([]byte, error) { 109 return w.buf.Get(ctx, key) 110 }) 111 base, err := w.base.Get(ctx, key) 112 if err != nil { 113 return buf.Wait().Get() 114 } 115 return base, nil 116 } 117 118 // Has implements part of [blob.KV]. 119 func (w *kvWrapper) Has(ctx context.Context, keys ...string) (blob.KeySet, error) { 120 // Look up keys in the buffer first. It is possible we may have some there 121 // that are not yet written back. Do this first so that if a writeback 122 // completes while we're checking the base store, we will still have a 123 // coherent value. 124 want := mapset.New(keys...) 125 have, err := w.buf.Has(ctx, want.Slice()...) 126 if err != nil { 127 return nil, fmt.Errorf("buffer stat: %w", err) 128 } 129 if have.Equals(want) { 130 return have, nil // we found everything 131 } 132 133 // Check for any keys we did not find in the buffer, in the base. 134 want.RemoveAll(have) 135 base, err := w.base.Has(ctx, want.Slice()...) 136 if err != nil { 137 return nil, fmt.Errorf("base stat: %w", err) 138 } 139 have.AddAll(base) 140 return have, nil 141 } 142 143 // Delete implements part of [blob.KV]. The key is deleted from both the buffer 144 // and the base store, and succeeds as long as either of those operations 145 // succeeds. 146 func (w *kvWrapper) Delete(ctx context.Context, key string) error { 147 cerr := w.buf.Delete(ctx, key) 148 berr := w.base.Delete(ctx, key) 149 if cerr != nil && berr != nil { 150 return berr 151 } 152 return nil 153 } 154 155 // Put implements part of [blob.KV]. It delegates to the base store directly 156 // for writes that request replacement; otherwise it stores the blob into the 157 // buffer for writeback. 158 func (w *kvWrapper) Put(ctx context.Context, opts blob.PutOptions) error { 159 if opts.Replace { 160 // Don't buffer writes that request replacement. 161 return w.base.Put(ctx, opts) 162 } 163 164 // To guarantee correct semantics we should do an occurs check here for the 165 // key in the base store. However, that is also expensive and reduces the 166 // value of the write-behind quite a bit. So we optimistically queue the 167 // write and report success (even though maybe we shouldn't). This will not 168 // write invalid data -- if the write-behind fails it means there is already 169 // a value in the base that we can linearize before this write. The only 170 // consequence is that this Put will not report that to the caller. 171 if err := w.buf.Put(ctx, opts); err != nil { 172 return err 173 } 174 w.nempty.Set() 175 return nil 176 } 177 178 // bufferKeysBetween iterates the keys in the buffer that are greater than or 179 // equal to notBefore and less than notAfter. 180 func (w *kvWrapper) bufferKeysBetween(ctx context.Context, notBefore, notAfter string) iter.Seq2[string, error] { 181 return func(yield func(string, error) bool) { 182 for key, err := range w.buf.List(ctx, notBefore) { 183 if err != nil { 184 yield("", err) 185 return 186 } 187 if key >= notAfter { 188 return 189 } 190 if !yield(key, nil) { 191 return 192 } 193 } 194 } 195 } 196 197 // Len implements part of [blob.KV]. It merges contents from the buffer that 198 // are not listed in the underlying store, so that the reported length reflects 199 // the total number of unique keys across both the buffer and the base store. 200 func (w *kvWrapper) Len(ctx context.Context) (int64, error) { 201 var bufKeys mapset.Set[string] 202 for key, err := range w.buf.List(ctx, "") { 203 if err != nil { 204 return 0, err 205 } 206 bufKeys.Add(key) 207 } 208 209 numKeys := int64(bufKeys.Len()) 210 for key, err := range w.base.List(ctx, "") { 211 if err != nil { 212 return 0, err 213 } 214 if !bufKeys.Has(key) { 215 numKeys++ 216 } 217 } 218 return numKeys, nil 219 } 220 221 // List implements part of [blob.KV]. It merges contents from the buffer that 222 // are not listed in the underlying store, so that the keys reported include 223 // those that have not yet been written back. 224 func (w *kvWrapper) List(ctx context.Context, start string) iter.Seq2[string, error] { 225 return func(yield func(string, error) bool) { 226 prev := start 227 for key, err := range w.base.List(ctx, start) { 228 if err != nil { 229 yield("", err) 230 return 231 } 232 for fill, err := range w.bufferKeysBetween(ctx, prev, key) { 233 if err != nil { 234 yield("", err) 235 return 236 } 237 if fill == prev { 238 continue 239 } 240 if !yield(fill, nil) { 241 return 242 } 243 } 244 if !yield(key, nil) { 245 return 246 } 247 prev = key 248 } 249 250 // Now ship any keys left in the buffer after the last key we sent. 251 for key, err := range w.buf.List(ctx, prev) { 252 if err != nil { 253 yield("", err) 254 return 255 } 256 if prev == key { 257 continue // we already shipped this key 258 } 259 if !yield(key, nil) { 260 return 261 } 262 } 263 } 264 }