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 }