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  }