github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/nbs/manifest.go (about) 1 // Copyright 2019 Dolthub, Inc. 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 // This file incorporates work covered by the following copyright and 16 // permission notice: 17 // 18 // Copyright 2016 Attic Labs, Inc. All rights reserved. 19 // Licensed under the Apache License, version 2.0: 20 // http://www.apache.org/licenses/LICENSE-2.0 21 22 package nbs 23 24 import ( 25 "context" 26 "crypto/sha512" 27 "errors" 28 "fmt" 29 "strconv" 30 "sync" 31 "time" 32 33 "github.com/dolthub/dolt/go/store/d" 34 "github.com/dolthub/dolt/go/store/hash" 35 ) 36 37 var ErrCorruptManifest = errors.New("corrupt manifest") 38 var ErrUnsupportedManifestAppendixOption = errors.New("unsupported manifest appendix option") 39 40 type manifest interface { 41 // Name returns a stable, unique identifier for the store this manifest describes. 42 Name() string 43 44 // ParseIfExists extracts and returns values from a NomsBlockStore 45 // manifest, if one exists. Concrete implementations are responsible for 46 // defining how to find and parse the desired manifest, e.g. a 47 // particularly-named file in a given directory. Implementations are also 48 // responsible for managing whatever concurrency guarantees they require 49 // for correctness. If the manifest exists, |exists| is set to true and 50 // manifest data is returned, including the version of the Noms data in 51 // the store, the root root hash.Hash of the store, and a tableSpec 52 // describing every table that comprises the store. 53 // If the manifest doesn't exist, |exists| is set to false and the other 54 // return values are undefined. The |readHook| parameter allows race 55 // condition testing. If it is non-nil, it will be invoked while the 56 // implementation is guaranteeing exclusive access to the manifest. 57 ParseIfExists(ctx context.Context, stats *Stats, readHook func() error) (exists bool, contents manifestContents, err error) 58 59 manifestUpdater 60 } 61 62 type manifestUpdater interface { 63 // Update optimistically tries to write a new manifest containing 64 // |newContents|. If |lastLock| matches the lock hash in the currently 65 // persisted manifest (logically, the lock that would be returned by 66 // ParseIfExists), then Update succeeds and subsequent calls to both 67 // Update and ParseIfExists will reflect a manifest containing 68 // |newContents|. If not, Update fails. Regardless, the returned 69 // manifestContents will reflect the current state of the world. Callers 70 // should check that the returned root == the proposed root and, if not, 71 // merge any desired new table information with the contents of the 72 // returned []tableSpec before trying again. 73 // Concrete implementations are responsible for ensuring that concurrent 74 // Update calls (and ParseIfExists calls) are correct. 75 // If writeHook is non-nil, it will be invoked while the implementation is 76 // guaranteeing exclusive access to the manifest. This allows for testing 77 // of race conditions. 78 Update(ctx context.Context, lastLock hash.Hash, newContents manifestContents, stats *Stats, writeHook func() error) (manifestContents, error) 79 } 80 81 type manifestGCGenUpdater interface { 82 // UpdateGCGen tries to write a new manifest containing |newContents|. 83 // Like Update(), it requires that |lastLock| matches the currently persisted 84 // lock hash. However, unlike Update() |newContents.root| must remain the same, 85 // while |newContents.gcGen| must be updated to a new value. 86 // Concrete implementations are responsible for ensuring that concurrent 87 // Update calls (and ParseIfExists calls) are correct. 88 // If writeHook is non-nil, it will be invoked while the implementation is 89 // guaranteeing exclusive access to the manifest. This allows for testing 90 // of race conditions. 91 UpdateGCGen(ctx context.Context, lastLock hash.Hash, newContents manifestContents, stats *Stats, writeHook func() error) (manifestContents, error) 92 } 93 94 // ManifestInfo is an interface for retrieving data from a manifest outside of this package 95 type ManifestInfo interface { 96 GetVersion() string 97 GetLock() string 98 GetGCGen() string 99 GetRoot() hash.Hash 100 NumTableSpecs() int 101 NumAppendixSpecs() int 102 GetTableSpecInfo(i int) TableSpecInfo 103 GetAppendixTableSpecInfo(i int) TableSpecInfo 104 } 105 106 type ManifestAppendixOption int 107 108 const ( 109 ManifestAppendixOption_Unspecified ManifestAppendixOption = iota 110 ManifestAppendixOption_Set 111 ManifestAppendixOption_Append 112 ) 113 114 type manifestContents struct { 115 manifestVers string 116 nbfVers string 117 lock hash.Hash 118 root hash.Hash 119 gcGen hash.Hash 120 specs []tableSpec 121 122 // An appendix is a list of |tableSpecs| that track an auxillary collection of 123 // table files used _only_ for query performance optimizations. These appendix |tableSpecs| can be safely 124 // managed with nbs.UpdateManifestWithAppendix, however generation and removal of the actual table files 125 // the appendix |tableSpecs| reference is done manually. All appendix |tableSpecs| will be prepended to the 126 // manifest.specs across manifest updates. 127 appendix []tableSpec 128 } 129 130 // GetVersion returns the noms binary format of the manifest 131 func (mc manifestContents) GetVersion() string { 132 return mc.nbfVers 133 } 134 135 func (mc manifestContents) GetLock() string { 136 return mc.lock.String() 137 } 138 139 func (mc manifestContents) GetGCGen() string { 140 return mc.gcGen.String() 141 } 142 143 func (mc manifestContents) GetRoot() hash.Hash { 144 return mc.root 145 } 146 147 func (mc manifestContents) NumTableSpecs() int { 148 return len(mc.specs) 149 } 150 151 func (mc manifestContents) NumAppendixSpecs() int { 152 return len(mc.appendix) 153 } 154 155 func (mc manifestContents) GetTableSpecInfo(i int) TableSpecInfo { 156 return mc.specs[i] 157 } 158 159 func (mc manifestContents) GetAppendixTableSpecInfo(i int) TableSpecInfo { 160 return mc.appendix[i] 161 } 162 163 func (mc manifestContents) getSpec(i int) tableSpec { 164 return mc.specs[i] 165 } 166 167 func (mc manifestContents) getAppendixSpec(i int) tableSpec { 168 return mc.appendix[i] 169 } 170 171 func (mc manifestContents) removeAppendixSpecs() (manifestContents, []tableSpec) { 172 if mc.appendix == nil || len(mc.appendix) == 0 { 173 return mc, nil 174 } 175 176 appendixSet := mc.getAppendixSet() 177 filtered := make([]tableSpec, 0) 178 removed := make([]tableSpec, 0) 179 for _, s := range mc.specs { 180 if _, ok := appendixSet[s.name]; ok { 181 removed = append(removed, s) 182 } else { 183 filtered = append(filtered, s) 184 } 185 } 186 187 return manifestContents{ 188 nbfVers: mc.nbfVers, 189 lock: mc.lock, 190 root: mc.root, 191 gcGen: mc.gcGen, 192 specs: filtered, 193 }, removed 194 } 195 196 func (mc manifestContents) getSpecSet() (ss map[hash.Hash]struct{}) { 197 return toSpecSet(mc.specs) 198 } 199 200 func (mc manifestContents) getAppendixSet() (ss map[hash.Hash]struct{}) { 201 return toSpecSet(mc.appendix) 202 } 203 204 func toSpecSet(specs []tableSpec) (ss map[hash.Hash]struct{}) { 205 ss = make(map[hash.Hash]struct{}, len(specs)) 206 for _, ts := range specs { 207 ss[ts.name] = struct{}{} 208 } 209 return ss 210 } 211 212 func (mc manifestContents) size() (size uint64) { 213 size += uint64(len(mc.nbfVers)) + hash.ByteLen + hash.ByteLen 214 for _, sp := range mc.specs { 215 size += uint64(len(sp.name)) + uint32Size // for sp.chunkCount 216 } 217 return 218 } 219 220 func newManifestLocks() *manifestLocks { 221 return &manifestLocks{map[string]struct{}{}, map[string]struct{}{}, sync.NewCond(&sync.Mutex{})} 222 } 223 224 type manifestLocks struct { 225 updating map[string]struct{} 226 fetching map[string]struct{} 227 cond *sync.Cond 228 } 229 230 func (ml *manifestLocks) lockForFetch(db string) { 231 lockByName(db, ml.cond, ml.fetching) 232 } 233 234 func (ml *manifestLocks) unlockForFetch(db string) error { 235 return unlockByName(db, ml.cond, ml.fetching) 236 } 237 238 func (ml *manifestLocks) lockForUpdate(db string) { 239 lockByName(db, ml.cond, ml.updating) 240 } 241 242 func (ml *manifestLocks) unlockForUpdate(db string) error { 243 return unlockByName(db, ml.cond, ml.updating) 244 } 245 246 func lockByName(db string, c *sync.Cond, locks map[string]struct{}) { 247 c.L.Lock() 248 defer c.L.Unlock() 249 250 for { 251 if _, inProgress := locks[db]; !inProgress { 252 locks[db] = struct{}{} 253 break 254 } 255 c.Wait() 256 } 257 } 258 259 func unlockByName(db string, c *sync.Cond, locks map[string]struct{}) error { 260 c.L.Lock() 261 defer c.L.Unlock() 262 263 if _, ok := locks[db]; !ok { 264 return errors.New("unlock failed") 265 } 266 267 delete(locks, db) 268 269 c.Broadcast() 270 271 return nil 272 } 273 274 type manifestManager struct { 275 m manifest 276 cache *manifestCache 277 locks *manifestLocks 278 } 279 280 func (mm manifestManager) lockOutFetch() { 281 mm.locks.lockForFetch(mm.Name()) 282 } 283 284 func (mm manifestManager) allowFetch() error { 285 return mm.locks.unlockForFetch(mm.Name()) 286 } 287 288 func (mm manifestManager) LockForUpdate() { 289 mm.locks.lockForUpdate(mm.Name()) 290 } 291 292 func (mm manifestManager) UnlockForUpdate() error { 293 return mm.locks.unlockForUpdate(mm.Name()) 294 } 295 296 func (mm manifestManager) updateWillFail(lastLock hash.Hash) (cached manifestContents, doomed bool) { 297 if upstream, _, hit := mm.cache.Get(mm.Name()); hit { 298 if lastLock != upstream.lock { 299 doomed, cached = true, upstream 300 } 301 } 302 return 303 } 304 305 func (mm manifestManager) Fetch(ctx context.Context, stats *Stats) (exists bool, contents manifestContents, t time.Time, err error) { 306 entryTime := time.Now() 307 308 mm.lockOutFetch() 309 defer func() { 310 afErr := mm.allowFetch() 311 312 if err == nil { 313 err = afErr 314 } 315 }() 316 317 f := func() (bool, manifestContents, time.Time, error) { 318 cached, t, hit := mm.cache.Get(mm.Name()) 319 320 if hit && t.After(entryTime) { 321 // Cache contains a manifest which is newer than entry time. 322 return true, cached, t, nil 323 } 324 325 t = time.Now() 326 327 exists, contents, err := mm.m.ParseIfExists(ctx, stats, nil) 328 329 if err != nil { 330 return false, manifestContents{}, t, err 331 } 332 333 err = mm.cache.Put(mm.Name(), contents, t) 334 335 if err != nil { 336 return false, manifestContents{}, t, err 337 } 338 339 return exists, contents, t, nil 340 } 341 342 exists, contents, t, err = f() 343 return 344 } 345 346 // Update attempts to write a new manifest. 347 // Callers MUST protect uses of Update with Lock/UnlockForUpdate. 348 // Update does not call Lock/UnlockForUpdate() on its own because it is 349 // intended to be used in a larger critical section along with updateWillFail. 350 func (mm manifestManager) Update(ctx context.Context, lastLock hash.Hash, newContents manifestContents, stats *Stats, writeHook func() error) (contents manifestContents, err error) { 351 if upstream, _, hit := mm.cache.Get(mm.Name()); hit { 352 if lastLock != upstream.lock { 353 return upstream, nil 354 } 355 } 356 t := time.Now() 357 358 mm.lockOutFetch() 359 defer func() { 360 afErr := mm.allowFetch() 361 362 if err == nil { 363 err = afErr 364 } 365 }() 366 367 f := func() (manifestContents, error) { 368 contents, err := mm.m.Update(ctx, lastLock, newContents, stats, writeHook) 369 370 if err != nil { 371 return contents, err 372 } 373 374 err = mm.cache.Put(mm.Name(), contents, t) 375 376 if err != nil { 377 return manifestContents{}, err 378 } 379 380 return contents, nil 381 } 382 383 contents, err = f() 384 return 385 } 386 387 // UpdateGCGen will update the manifest with a new garbage collection generation. 388 // Callers MUST protect uses of UpdateGCGen with Lock/UnlockForUpdate. 389 func (mm manifestManager) UpdateGCGen(ctx context.Context, lastLock hash.Hash, newContents manifestContents, stats *Stats, writeHook func() error) (contents manifestContents, err error) { 390 updater, ok := mm.m.(manifestGCGenUpdater) 391 if !ok { 392 return manifestContents{}, errors.New("manifest does not support updating gc gen") 393 } 394 395 if upstream, _, hit := mm.cache.Get(mm.Name()); hit { 396 if lastLock != upstream.lock { 397 return manifestContents{}, errors.New("manifest was modified during garbage collection") 398 } 399 } 400 t := time.Now() 401 402 mm.lockOutFetch() 403 defer func() { 404 afErr := mm.allowFetch() 405 406 if err == nil { 407 err = afErr 408 } 409 }() 410 411 f := func() (manifestContents, error) { 412 contents, err := updater.UpdateGCGen(ctx, lastLock, newContents, stats, writeHook) 413 414 if err != nil { 415 return contents, err 416 } 417 418 err = mm.cache.Put(mm.Name(), contents, t) 419 420 if err != nil { 421 return manifestContents{}, err 422 } 423 424 return contents, nil 425 } 426 427 contents, err = f() 428 return 429 } 430 431 func (mm manifestManager) Close() error { 432 mm.cache.Delete(mm.Name()) 433 return nil 434 } 435 436 func (mm manifestManager) Name() string { 437 return mm.m.Name() 438 } 439 440 // TableSpecInfo is an interface for retrieving data from a tableSpec outside of this package 441 type TableSpecInfo interface { 442 GetName() string 443 GetChunkCount() uint32 444 } 445 446 type tableSpec struct { 447 name hash.Hash 448 chunkCount uint32 449 } 450 451 func (ts tableSpec) GetName() string { 452 return ts.name.String() 453 } 454 455 func (ts tableSpec) GetChunkCount() uint32 { 456 return ts.chunkCount 457 } 458 459 func tableSpecsToMap(specs []tableSpec) map[string]int { 460 m := make(map[string]int) 461 for _, spec := range specs { 462 m[spec.name.String()] = int(spec.chunkCount) 463 } 464 465 return m 466 } 467 468 func parseSpecs(tableInfo []string) ([]tableSpec, error) { 469 specs := make([]tableSpec, len(tableInfo)/2) 470 for i := range specs { 471 var err error 472 var ok bool 473 specs[i].name, ok = hash.MaybeParse(tableInfo[2*i]) 474 if !ok { 475 return nil, fmt.Errorf("invalid table file name: %s", tableInfo[2*i]) 476 } 477 478 c, err := strconv.ParseUint(tableInfo[2*i+1], 10, 32) 479 480 if err != nil { 481 return nil, err 482 } 483 484 specs[i].chunkCount = uint32(c) 485 } 486 487 return specs, nil 488 } 489 490 func formatSpecs(specs []tableSpec, tableInfo []string) { 491 d.Chk.True(len(tableInfo) == 2*len(specs)) 492 for i, t := range specs { 493 tableInfo[2*i] = t.name.String() 494 tableInfo[2*i+1] = strconv.FormatUint(uint64(t.chunkCount), 10) 495 } 496 } 497 498 // generateLockHash returns a hash of root and the names of all the tables in 499 // specs, which should be included in all persisted manifests. When a client 500 // attempts to update a manifest, it must check the lock hash in the currently 501 // persisted manifest against the lock hash it saw last time it loaded the 502 // contents of a manifest. If they do not match, the client must not update 503 // the persisted manifest. 504 func generateLockHash(root hash.Hash, specs []tableSpec, appendix []tableSpec) hash.Hash { 505 blockHash := sha512.New() 506 blockHash.Write(root[:]) 507 for _, spec := range appendix { 508 blockHash.Write(spec.name[:]) 509 } 510 blockHash.Write([]byte{0}) 511 for _, spec := range specs { 512 blockHash.Write(spec.name[:]) 513 } 514 var h []byte 515 h = blockHash.Sum(h) // Appends hash to h 516 return hash.New(h[:hash.ByteLen]) 517 }