github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/replica_sst_snapshot_storage.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package kvserver
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"os"
    17  	"path/filepath"
    18  	"strconv"
    19  
    20  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    21  	"github.com/cockroachdb/cockroach/pkg/storage"
    22  	"github.com/cockroachdb/cockroach/pkg/storage/fs"
    23  	"github.com/cockroachdb/cockroach/pkg/util/uuid"
    24  	"github.com/cockroachdb/errors"
    25  	"golang.org/x/time/rate"
    26  )
    27  
    28  // SSTSnapshotStorage provides an interface to create scratches and owns the
    29  // directory of scratches created. A scratch manages the SSTs created during a
    30  // specific snapshot.
    31  type SSTSnapshotStorage struct {
    32  	engine  storage.Engine
    33  	limiter *rate.Limiter
    34  	dir     string
    35  }
    36  
    37  // NewSSTSnapshotStorage creates a new SST snapshot storage.
    38  func NewSSTSnapshotStorage(engine storage.Engine, limiter *rate.Limiter) SSTSnapshotStorage {
    39  	return SSTSnapshotStorage{
    40  		engine:  engine,
    41  		limiter: limiter,
    42  		dir:     filepath.Join(engine.GetAuxiliaryDir(), "sstsnapshot"),
    43  	}
    44  }
    45  
    46  // NewScratchSpace creates a new storage scratch space for SSTs for a specific
    47  // snapshot.
    48  func (s *SSTSnapshotStorage) NewScratchSpace(
    49  	rangeID roachpb.RangeID, snapUUID uuid.UUID,
    50  ) *SSTSnapshotStorageScratch {
    51  	snapDir := filepath.Join(s.dir, strconv.Itoa(int(rangeID)), snapUUID.String())
    52  	return &SSTSnapshotStorageScratch{
    53  		storage: s,
    54  		snapDir: snapDir,
    55  	}
    56  }
    57  
    58  // Clear removes all created directories and SSTs.
    59  func (s *SSTSnapshotStorage) Clear() error {
    60  	return os.RemoveAll(s.dir)
    61  }
    62  
    63  // SSTSnapshotStorageScratch keeps track of the SST files incrementally created
    64  // when receiving a snapshot. Each scratch is associated with a specific
    65  // snapshot.
    66  type SSTSnapshotStorageScratch struct {
    67  	storage    *SSTSnapshotStorage
    68  	ssts       []string
    69  	snapDir    string
    70  	dirCreated bool
    71  }
    72  
    73  func (s *SSTSnapshotStorageScratch) filename(id int) string {
    74  	return filepath.Join(s.snapDir, fmt.Sprintf("%d.sst", id))
    75  }
    76  
    77  func (s *SSTSnapshotStorageScratch) createDir() error {
    78  	// TODO(peter): The directory creation needs to be plumbed through the Engine
    79  	// interface. Right now, this is creating a directory on disk even when the
    80  	// Engine has an in-memory filesystem. The only reason everything still works
    81  	// is because RocksDB MemEnvs allow the creation of files when the parent
    82  	// directory doesn't exist.
    83  	err := os.MkdirAll(s.snapDir, 0755)
    84  	s.dirCreated = s.dirCreated || err == nil
    85  	return err
    86  }
    87  
    88  // NewFile adds another file to SSTSnapshotStorageScratch. This file is lazily
    89  // created when the file is written to the first time. A nonzero value for
    90  // chunkSize buffers up writes until the buffer is greater than chunkSize.
    91  func (s *SSTSnapshotStorageScratch) NewFile(
    92  	ctx context.Context, chunkSize int64,
    93  ) (*SSTSnapshotStorageFile, error) {
    94  	id := len(s.ssts)
    95  	filename := s.filename(id)
    96  	s.ssts = append(s.ssts, filename)
    97  	f := &SSTSnapshotStorageFile{
    98  		scratch:   s,
    99  		filename:  filename,
   100  		ctx:       ctx,
   101  		chunkSize: chunkSize,
   102  	}
   103  	return f, nil
   104  }
   105  
   106  // WriteSST writes SST data to a file. The method closes
   107  // the provided SST when it is finished using it. If the provided SST is empty,
   108  // then no file will be created and nothing will be written.
   109  func (s *SSTSnapshotStorageScratch) WriteSST(ctx context.Context, data []byte) error {
   110  	if len(data) == 0 {
   111  		return nil
   112  	}
   113  	f, err := s.NewFile(ctx, 0)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	defer func() {
   118  		// Closing an SSTSnapshotStorageFile multiple times is idempotent. Nothing
   119  		// actionable if closing fails.
   120  		_ = f.Close()
   121  	}()
   122  	if _, err := f.Write(data); err != nil {
   123  		return err
   124  	}
   125  	return f.Close()
   126  }
   127  
   128  // SSTs returns the names of the files created.
   129  func (s *SSTSnapshotStorageScratch) SSTs() []string {
   130  	return s.ssts
   131  }
   132  
   133  // Clear removes the directory and SSTs created for a particular snapshot.
   134  func (s *SSTSnapshotStorageScratch) Clear() error {
   135  	return os.RemoveAll(s.snapDir)
   136  }
   137  
   138  // SSTSnapshotStorageFile is an SST file managed by a
   139  // SSTSnapshotStorageScratch.
   140  type SSTSnapshotStorageFile struct {
   141  	scratch   *SSTSnapshotStorageScratch
   142  	created   bool
   143  	file      fs.File
   144  	filename  string
   145  	ctx       context.Context
   146  	chunkSize int64
   147  	buffer    []byte
   148  }
   149  
   150  func (f *SSTSnapshotStorageFile) openFile() error {
   151  	if f.created {
   152  		if f.file == nil {
   153  			return errors.Errorf("file has already been closed")
   154  		}
   155  		return nil
   156  	}
   157  	if !f.scratch.dirCreated {
   158  		if err := f.scratch.createDir(); err != nil {
   159  			return err
   160  		}
   161  	}
   162  	file, err := f.scratch.storage.engine.Create(f.filename)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	f.file = file
   167  	f.created = true
   168  	return nil
   169  }
   170  
   171  // Write writes contents to the file while respecting the limiter passed into
   172  // SSTSnapshotStorageScratch. Writing empty contents is okay and is treated as
   173  // a noop. The file must have not been closed.
   174  func (f *SSTSnapshotStorageFile) Write(contents []byte) (int, error) {
   175  	if len(contents) == 0 {
   176  		return 0, nil
   177  	}
   178  	if err := f.openFile(); err != nil {
   179  		return 0, err
   180  	}
   181  	limitBulkIOWrite(f.ctx, f.scratch.storage.limiter, len(contents))
   182  	if f.chunkSize > 0 {
   183  		if int64(len(contents)+len(f.buffer)) < f.chunkSize {
   184  			// Don't write to file yet - buffer write until next time.
   185  			f.buffer = append(f.buffer, contents...)
   186  			return len(contents), nil
   187  		} else if len(f.buffer) > 0 {
   188  			// Write buffered writes and then empty the buffer.
   189  			if _, err := f.file.Write(f.buffer); err != nil {
   190  				return 0, err
   191  			}
   192  			f.buffer = f.buffer[:0]
   193  		}
   194  	}
   195  	if _, err := f.file.Write(contents); err != nil {
   196  		return 0, err
   197  	}
   198  	return len(contents), f.file.Sync()
   199  }
   200  
   201  // Close closes the file. Calling this function multiple times is idempotent.
   202  // The file must have been written to before being closed.
   203  func (f *SSTSnapshotStorageFile) Close() error {
   204  	// We throw an error for empty files because it would be an error to ingest
   205  	// an empty SST so catch this error earlier.
   206  	if !f.created {
   207  		return errors.New("file is empty")
   208  	}
   209  	if f.file == nil {
   210  		return nil
   211  	}
   212  	if len(f.buffer) > 0 {
   213  		// Write out any buffered data.
   214  		if _, err := f.file.Write(f.buffer); err != nil {
   215  			return err
   216  		}
   217  		f.buffer = f.buffer[:0]
   218  	}
   219  	if err := f.file.Close(); err != nil {
   220  		return err
   221  	}
   222  	f.file = nil
   223  	return nil
   224  }
   225  
   226  // Sync syncs the file to disk. Implements writeCloseSyncer in engine.
   227  func (f *SSTSnapshotStorageFile) Sync() error {
   228  	return f.file.Sync()
   229  }