github.com/ethereum/go-ethereum@v1.16.1/core/rawdb/freezer.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package rawdb 18 19 import ( 20 "errors" 21 "fmt" 22 "math" 23 "os" 24 "path/filepath" 25 "sync" 26 "sync/atomic" 27 28 "github.com/ethereum/go-ethereum/ethdb" 29 "github.com/ethereum/go-ethereum/log" 30 "github.com/ethereum/go-ethereum/metrics" 31 "github.com/gofrs/flock" 32 ) 33 34 var ( 35 // errReadOnly is returned if the freezer is opened in read only mode. All the 36 // mutations are disallowed. 37 errReadOnly = errors.New("read only") 38 39 // errUnknownTable is returned if the user attempts to read from a table that is 40 // not tracked by the freezer. 41 errUnknownTable = errors.New("unknown table") 42 43 // errOutOrderInsertion is returned if the user attempts to inject out-of-order 44 // binary blobs into the freezer. 45 errOutOrderInsertion = errors.New("the append operation is out-order") 46 47 // errSymlinkDatadir is returned if the ancient directory specified by user 48 // is a symbolic link. 49 errSymlinkDatadir = errors.New("symbolic link datadir is not supported") 50 ) 51 52 // freezerTableSize defines the maximum size of freezer data files. 53 const freezerTableSize = 2 * 1000 * 1000 * 1000 54 55 // Freezer is an append-only database to store immutable ordered data into 56 // flat files: 57 // 58 // - The append-only nature ensures that disk writes are minimized. 59 // - The in-order data ensures that disk reads are always optimized. 60 type Freezer struct { 61 datadir string 62 frozen atomic.Uint64 // Number of items already frozen 63 tail atomic.Uint64 // Number of the first stored item in the freezer 64 65 // This lock synchronizes writers and the truncate operation, as well as 66 // the "atomic" (batched) read operations. 67 writeLock sync.RWMutex 68 writeBatch *freezerBatch 69 70 readonly bool 71 tables map[string]*freezerTable // Data tables for storing everything 72 instanceLock *flock.Flock // File-system lock to prevent double opens 73 closeOnce sync.Once 74 } 75 76 // NewFreezer creates a freezer instance for maintaining immutable ordered 77 // data according to the given parameters. 78 // 79 // The 'tables' argument defines the data tables. If the value of a map 80 // entry is true, snappy compression is disabled for the table. 81 func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]freezerTableConfig) (*Freezer, error) { 82 // Create the initial freezer object 83 var ( 84 readMeter = metrics.NewRegisteredMeter(namespace+"ancient/read", nil) 85 writeMeter = metrics.NewRegisteredMeter(namespace+"ancient/write", nil) 86 sizeGauge = metrics.NewRegisteredGauge(namespace+"ancient/size", nil) 87 ) 88 // Ensure the datadir is not a symbolic link if it exists. 89 if info, err := os.Lstat(datadir); !os.IsNotExist(err) { 90 if info == nil { 91 log.Warn("Could not Lstat the database", "path", datadir) 92 return nil, errors.New("lstat failed") 93 } 94 if info.Mode()&os.ModeSymlink != 0 { 95 log.Warn("Symbolic link ancient database is not supported", "path", datadir) 96 return nil, errSymlinkDatadir 97 } 98 } 99 flockFile := filepath.Join(datadir, "FLOCK") 100 if err := os.MkdirAll(filepath.Dir(flockFile), 0755); err != nil { 101 return nil, err 102 } 103 // Leveldb uses LOCK as the filelock filename. To prevent the 104 // name collision, we use FLOCK as the lock name. 105 lock := flock.New(flockFile) 106 tryLock := lock.TryLock 107 if readonly { 108 tryLock = lock.TryRLock 109 } 110 if locked, err := tryLock(); err != nil { 111 return nil, err 112 } else if !locked { 113 return nil, errors.New("locking failed") 114 } 115 // Open all the supported data tables 116 freezer := &Freezer{ 117 datadir: datadir, 118 readonly: readonly, 119 tables: make(map[string]*freezerTable), 120 instanceLock: lock, 121 } 122 123 // Create the tables. 124 for name, config := range tables { 125 table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, maxTableSize, config, readonly) 126 if err != nil { 127 for _, table := range freezer.tables { 128 table.Close() 129 } 130 lock.Unlock() 131 return nil, err 132 } 133 freezer.tables[name] = table 134 } 135 var err error 136 if freezer.readonly { 137 // In readonly mode only validate, don't truncate. 138 // validate also sets `freezer.frozen`. 139 err = freezer.validate() 140 } else { 141 // Truncate all tables to common length. 142 err = freezer.repair() 143 } 144 if err != nil { 145 for _, table := range freezer.tables { 146 table.Close() 147 } 148 lock.Unlock() 149 return nil, err 150 } 151 152 // Create the write batch. 153 freezer.writeBatch = newFreezerBatch(freezer) 154 155 log.Info("Opened ancient database", "database", datadir, "readonly", readonly) 156 return freezer, nil 157 } 158 159 // Close terminates the chain freezer, closing all the data files. 160 func (f *Freezer) Close() error { 161 f.writeLock.Lock() 162 defer f.writeLock.Unlock() 163 164 var errs []error 165 f.closeOnce.Do(func() { 166 for _, table := range f.tables { 167 if err := table.Close(); err != nil { 168 errs = append(errs, err) 169 } 170 } 171 if err := f.instanceLock.Unlock(); err != nil { 172 errs = append(errs, err) 173 } 174 }) 175 return errors.Join(errs...) 176 } 177 178 // AncientDatadir returns the path of the ancient store. 179 func (f *Freezer) AncientDatadir() (string, error) { 180 return f.datadir, nil 181 } 182 183 // Ancient retrieves an ancient binary blob from the append-only immutable files. 184 func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) { 185 if table := f.tables[kind]; table != nil { 186 return table.Retrieve(number) 187 } 188 return nil, errUnknownTable 189 } 190 191 // AncientRange retrieves multiple items in sequence, starting from the index 'start'. 192 // It will return 193 // - at most 'count' items, 194 // - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize), 195 // but will otherwise return as many items as fit into maxByteSize. 196 // - if maxBytes is not specified, 'count' items will be returned if they are present. 197 func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { 198 if table := f.tables[kind]; table != nil { 199 return table.RetrieveItems(start, count, maxBytes) 200 } 201 return nil, errUnknownTable 202 } 203 204 // Ancients returns the length of the frozen items. 205 func (f *Freezer) Ancients() (uint64, error) { 206 return f.frozen.Load(), nil 207 } 208 209 // Tail returns the number of first stored item in the freezer. 210 func (f *Freezer) Tail() (uint64, error) { 211 return f.tail.Load(), nil 212 } 213 214 // AncientSize returns the ancient size of the specified category. 215 func (f *Freezer) AncientSize(kind string) (uint64, error) { 216 // This needs the write lock to avoid data races on table fields. 217 // Speed doesn't matter here, AncientSize is for debugging. 218 f.writeLock.RLock() 219 defer f.writeLock.RUnlock() 220 221 if table := f.tables[kind]; table != nil { 222 return table.size() 223 } 224 return 0, errUnknownTable 225 } 226 227 // ReadAncients runs the given read operation while ensuring that no writes take place 228 // on the underlying freezer. 229 func (f *Freezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { 230 f.writeLock.RLock() 231 defer f.writeLock.RUnlock() 232 233 return fn(f) 234 } 235 236 // ModifyAncients runs the given write operation. 237 func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) { 238 if f.readonly { 239 return 0, errReadOnly 240 } 241 f.writeLock.Lock() 242 defer f.writeLock.Unlock() 243 244 // Roll back all tables to the starting position in case of error. 245 prevItem := f.frozen.Load() 246 defer func() { 247 if err != nil { 248 // The write operation has failed. Go back to the previous item position. 249 for name, table := range f.tables { 250 err := table.truncateHead(prevItem) 251 if err != nil { 252 log.Error("Freezer table roll-back failed", "table", name, "index", prevItem, "err", err) 253 } 254 } 255 } 256 }() 257 258 f.writeBatch.reset() 259 if err := fn(f.writeBatch); err != nil { 260 return 0, err 261 } 262 item, writeSize, err := f.writeBatch.commit() 263 if err != nil { 264 return 0, err 265 } 266 f.frozen.Store(item) 267 return writeSize, nil 268 } 269 270 // TruncateHead discards any recent data above the provided threshold number. 271 // It returns the previous head number. 272 func (f *Freezer) TruncateHead(items uint64) (uint64, error) { 273 if f.readonly { 274 return 0, errReadOnly 275 } 276 f.writeLock.Lock() 277 defer f.writeLock.Unlock() 278 279 oitems := f.frozen.Load() 280 if oitems <= items { 281 return oitems, nil 282 } 283 for _, table := range f.tables { 284 if err := table.truncateHead(items); err != nil { 285 return 0, err 286 } 287 } 288 f.frozen.Store(items) 289 return oitems, nil 290 } 291 292 // TruncateTail discards all data below the specified threshold. Note that only 293 // 'prunable' tables will be truncated. 294 func (f *Freezer) TruncateTail(tail uint64) (uint64, error) { 295 if f.readonly { 296 return 0, errReadOnly 297 } 298 f.writeLock.Lock() 299 defer f.writeLock.Unlock() 300 301 old := f.tail.Load() 302 if old >= tail { 303 return old, nil 304 } 305 for _, table := range f.tables { 306 if table.config.prunable { 307 if err := table.truncateTail(tail); err != nil { 308 return 0, err 309 } 310 } 311 } 312 f.tail.Store(tail) 313 return old, nil 314 } 315 316 // SyncAncient flushes all data tables to disk. 317 func (f *Freezer) SyncAncient() error { 318 var errs []error 319 for _, table := range f.tables { 320 if err := table.Sync(); err != nil { 321 errs = append(errs, err) 322 } 323 } 324 if errs != nil { 325 return fmt.Errorf("%v", errs) 326 } 327 return nil 328 } 329 330 // validate checks that every table has the same boundary. 331 // Used instead of `repair` in readonly mode. 332 func (f *Freezer) validate() error { 333 if len(f.tables) == 0 { 334 return nil 335 } 336 var ( 337 head uint64 338 prunedTail *uint64 339 ) 340 // get any head value 341 for _, table := range f.tables { 342 head = table.items.Load() 343 break 344 } 345 for kind, table := range f.tables { 346 // all tables have to have the same head 347 if head != table.items.Load() { 348 return fmt.Errorf("freezer table %s has a differing head: %d != %d", kind, table.items.Load(), head) 349 } 350 if !table.config.prunable { 351 // non-prunable tables have to start at 0 352 if table.itemHidden.Load() != 0 { 353 return fmt.Errorf("non-prunable freezer table '%s' has a non-zero tail: %d", kind, table.itemHidden.Load()) 354 } 355 } else { 356 // prunable tables have to have the same length 357 if prunedTail == nil { 358 tmp := table.itemHidden.Load() 359 prunedTail = &tmp 360 } 361 if *prunedTail != table.itemHidden.Load() { 362 return fmt.Errorf("freezer table %s has differing tail: %d != %d", kind, table.itemHidden.Load(), *prunedTail) 363 } 364 } 365 } 366 367 if prunedTail == nil { 368 tmp := uint64(0) 369 prunedTail = &tmp 370 } 371 372 f.frozen.Store(head) 373 f.tail.Store(*prunedTail) 374 return nil 375 } 376 377 // repair truncates all data tables to the same length. 378 func (f *Freezer) repair() error { 379 var ( 380 head = uint64(math.MaxUint64) 381 prunedTail = uint64(0) 382 ) 383 // get the minimal head and the maximum tail 384 for _, table := range f.tables { 385 head = min(head, table.items.Load()) 386 prunedTail = max(prunedTail, table.itemHidden.Load()) 387 } 388 // apply the pruning 389 for kind, table := range f.tables { 390 // all tables need to have the same head 391 if err := table.truncateHead(head); err != nil { 392 return err 393 } 394 if !table.config.prunable { 395 // non-prunable tables have to start at 0 396 if table.itemHidden.Load() != 0 { 397 panic(fmt.Sprintf("non-prunable freezer table %s has non-zero tail: %v", kind, table.itemHidden.Load())) 398 } 399 } else { 400 // prunable tables have to have the same length 401 if err := table.truncateTail(prunedTail); err != nil { 402 return err 403 } 404 } 405 } 406 407 f.frozen.Store(head) 408 f.tail.Store(prunedTail) 409 return nil 410 }