github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/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 "time" 28 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/ethdb" 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/ethereum/go-ethereum/metrics" 33 "github.com/gofrs/flock" 34 ) 35 36 var ( 37 // errReadOnly is returned if the freezer is opened in read only mode. All the 38 // mutations are disallowed. 39 errReadOnly = errors.New("read only") 40 41 // errUnknownTable is returned if the user attempts to read from a table that is 42 // not tracked by the freezer. 43 errUnknownTable = errors.New("unknown table") 44 45 // errOutOrderInsertion is returned if the user attempts to inject out-of-order 46 // binary blobs into the freezer. 47 errOutOrderInsertion = errors.New("the append operation is out-order") 48 49 // errSymlinkDatadir is returned if the ancient directory specified by user 50 // is a symbolic link. 51 errSymlinkDatadir = errors.New("symbolic link datadir is not supported") 52 ) 53 54 // freezerTableSize defines the maximum size of freezer data files. 55 const freezerTableSize = 2 * 1000 * 1000 * 1000 56 57 // Freezer is a memory mapped append-only database to store immutable ordered 58 // data into flat files: 59 // 60 // - The append-only nature ensures that disk writes are minimized. 61 // - The memory mapping ensures we can max out system memory for caching without 62 // reserving it for go-ethereum. This would also reduce the memory requirements 63 // of Geth, and thus also GC overhead. 64 type Freezer struct { 65 frozen atomic.Uint64 // Number of items already frozen 66 tail atomic.Uint64 // Number of the first stored item in the freezer 67 68 // This lock synchronizes writers and the truncate operation, as well as 69 // the "atomic" (batched) read operations. 70 writeLock sync.RWMutex 71 writeBatch *freezerBatch 72 73 readonly bool 74 tables map[string]*freezerTable // Data tables for storing everything 75 instanceLock *flock.Flock // File-system lock to prevent double opens 76 closeOnce sync.Once 77 } 78 79 // NewFreezer creates a freezer instance for maintaining immutable ordered 80 // data according to the given parameters. 81 // 82 // The 'tables' argument defines the data tables. If the value of a map 83 // entry is true, snappy compression is disabled for the table. 84 func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*Freezer, error) { 85 // Create the initial freezer object 86 var ( 87 readMeter = metrics.NewRegisteredMeter(namespace+"ancient/read", nil) 88 writeMeter = metrics.NewRegisteredMeter(namespace+"ancient/write", nil) 89 sizeGauge = metrics.NewRegisteredGauge(namespace+"ancient/size", nil) 90 ) 91 // Ensure the datadir is not a symbolic link if it exists. 92 if info, err := os.Lstat(datadir); !os.IsNotExist(err) { 93 if info.Mode()&os.ModeSymlink != 0 { 94 log.Warn("Symbolic link ancient database is not supported", "path", datadir) 95 return nil, errSymlinkDatadir 96 } 97 } 98 flockFile := filepath.Join(datadir, "FLOCK") 99 if err := os.MkdirAll(filepath.Dir(flockFile), 0755); err != nil { 100 return nil, err 101 } 102 // Leveldb uses LOCK as the filelock filename. To prevent the 103 // name collision, we use FLOCK as the lock name. 104 lock := flock.New(flockFile) 105 tryLock := lock.TryLock 106 if readonly { 107 tryLock = lock.TryRLock 108 } 109 if locked, err := tryLock(); err != nil { 110 return nil, err 111 } else if !locked { 112 return nil, errors.New("locking failed") 113 } 114 // Open all the supported data tables 115 freezer := &Freezer{ 116 readonly: readonly, 117 tables: make(map[string]*freezerTable), 118 instanceLock: lock, 119 } 120 121 // Create the tables. 122 for name, disableSnappy := range tables { 123 table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, maxTableSize, disableSnappy, readonly) 124 if err != nil { 125 for _, table := range freezer.tables { 126 table.Close() 127 } 128 lock.Unlock() 129 return nil, err 130 } 131 freezer.tables[name] = table 132 } 133 var err error 134 if freezer.readonly { 135 // In readonly mode only validate, don't truncate. 136 // validate also sets `freezer.frozen`. 137 err = freezer.validate() 138 } else { 139 // Truncate all tables to common length. 140 err = freezer.repair() 141 } 142 if err != nil { 143 for _, table := range freezer.tables { 144 table.Close() 145 } 146 lock.Unlock() 147 return nil, err 148 } 149 150 // Create the write batch. 151 freezer.writeBatch = newFreezerBatch(freezer) 152 153 log.Info("Opened ancient database", "database", datadir, "readonly", readonly) 154 return freezer, nil 155 } 156 157 // Close terminates the chain freezer, unmapping all the data files. 158 func (f *Freezer) Close() error { 159 f.writeLock.Lock() 160 defer f.writeLock.Unlock() 161 162 var errs []error 163 f.closeOnce.Do(func() { 164 for _, table := range f.tables { 165 if err := table.Close(); err != nil { 166 errs = append(errs, err) 167 } 168 } 169 if err := f.instanceLock.Unlock(); err != nil { 170 errs = append(errs, err) 171 } 172 }) 173 if errs != nil { 174 return fmt.Errorf("%v", errs) 175 } 176 return nil 177 } 178 179 // HasAncient returns an indicator whether the specified ancient data exists 180 // in the freezer. 181 func (f *Freezer) HasAncient(kind string, number uint64) (bool, error) { 182 if table := f.tables[kind]; table != nil { 183 return table.has(number), nil 184 } 185 return false, nil 186 } 187 188 // Ancient retrieves an ancient binary blob from the append-only immutable files. 189 func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) { 190 if table := f.tables[kind]; table != nil { 191 return table.Retrieve(number) 192 } 193 return nil, errUnknownTable 194 } 195 196 // AncientRange retrieves multiple items in sequence, starting from the index 'start'. 197 // It will return 198 // - at most 'count' items, 199 // - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize), 200 // but will otherwise return as many items as fit into maxByteSize. 201 // - if maxBytes is not specified, 'count' items will be returned if they are present. 202 func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { 203 if table := f.tables[kind]; table != nil { 204 return table.RetrieveItems(start, count, maxBytes) 205 } 206 return nil, errUnknownTable 207 } 208 209 // Ancients returns the length of the frozen items. 210 func (f *Freezer) Ancients() (uint64, error) { 211 return f.frozen.Load(), nil 212 } 213 214 // Tail returns the number of first stored item in the freezer. 215 func (f *Freezer) Tail() (uint64, error) { 216 return f.tail.Load(), nil 217 } 218 219 // AncientSize returns the ancient size of the specified category. 220 func (f *Freezer) AncientSize(kind string) (uint64, error) { 221 // This needs the write lock to avoid data races on table fields. 222 // Speed doesn't matter here, AncientSize is for debugging. 223 f.writeLock.RLock() 224 defer f.writeLock.RUnlock() 225 226 if table := f.tables[kind]; table != nil { 227 return table.size() 228 } 229 return 0, errUnknownTable 230 } 231 232 // ReadAncients runs the given read operation while ensuring that no writes take place 233 // on the underlying freezer. 234 func (f *Freezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { 235 f.writeLock.RLock() 236 defer f.writeLock.RUnlock() 237 238 return fn(f) 239 } 240 241 // ModifyAncients runs the given write operation. 242 func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) { 243 if f.readonly { 244 return 0, errReadOnly 245 } 246 f.writeLock.Lock() 247 defer f.writeLock.Unlock() 248 249 // Roll back all tables to the starting position in case of error. 250 prevItem := f.frozen.Load() 251 defer func() { 252 if err != nil { 253 // The write operation has failed. Go back to the previous item position. 254 for name, table := range f.tables { 255 err := table.truncateHead(prevItem) 256 if err != nil { 257 log.Error("Freezer table roll-back failed", "table", name, "index", prevItem, "err", err) 258 } 259 } 260 } 261 }() 262 263 f.writeBatch.reset() 264 if err := fn(f.writeBatch); err != nil { 265 return 0, err 266 } 267 item, writeSize, err := f.writeBatch.commit() 268 if err != nil { 269 return 0, err 270 } 271 f.frozen.Store(item) 272 return writeSize, nil 273 } 274 275 // TruncateHead discards any recent data above the provided threshold number. 276 // It returns the previous head number. 277 func (f *Freezer) TruncateHead(items uint64) (uint64, error) { 278 if f.readonly { 279 return 0, errReadOnly 280 } 281 f.writeLock.Lock() 282 defer f.writeLock.Unlock() 283 284 oitems := f.frozen.Load() 285 if oitems <= items { 286 return oitems, nil 287 } 288 for _, table := range f.tables { 289 if err := table.truncateHead(items); err != nil { 290 return 0, err 291 } 292 } 293 f.frozen.Store(items) 294 return oitems, nil 295 } 296 297 // TruncateTail discards any recent data below the provided threshold number. 298 func (f *Freezer) TruncateTail(tail uint64) (uint64, error) { 299 if f.readonly { 300 return 0, errReadOnly 301 } 302 f.writeLock.Lock() 303 defer f.writeLock.Unlock() 304 305 old := f.tail.Load() 306 if old >= tail { 307 return old, nil 308 } 309 for _, table := range f.tables { 310 if err := table.truncateTail(tail); err != nil { 311 return 0, err 312 } 313 } 314 f.tail.Store(tail) 315 return old, nil 316 } 317 318 // Sync flushes all data tables to disk. 319 func (f *Freezer) Sync() error { 320 var errs []error 321 for _, table := range f.tables { 322 if err := table.Sync(); err != nil { 323 errs = append(errs, err) 324 } 325 } 326 if errs != nil { 327 return fmt.Errorf("%v", errs) 328 } 329 return nil 330 } 331 332 // validate checks that every table has the same boundary. 333 // Used instead of `repair` in readonly mode. 334 func (f *Freezer) validate() error { 335 if len(f.tables) == 0 { 336 return nil 337 } 338 var ( 339 head uint64 340 tail uint64 341 name string 342 ) 343 // Hack to get boundary of any table 344 for kind, table := range f.tables { 345 head = table.items.Load() 346 tail = table.itemHidden.Load() 347 name = kind 348 break 349 } 350 // Now check every table against those boundaries. 351 for kind, table := range f.tables { 352 if head != table.items.Load() { 353 return fmt.Errorf("freezer tables %s and %s have differing head: %d != %d", kind, name, table.items.Load(), head) 354 } 355 if tail != table.itemHidden.Load() { 356 return fmt.Errorf("freezer tables %s and %s have differing tail: %d != %d", kind, name, table.itemHidden.Load(), tail) 357 } 358 } 359 f.frozen.Store(head) 360 f.tail.Store(tail) 361 return nil 362 } 363 364 // repair truncates all data tables to the same length. 365 func (f *Freezer) repair() error { 366 var ( 367 head = uint64(math.MaxUint64) 368 tail = uint64(0) 369 ) 370 for _, table := range f.tables { 371 items := table.items.Load() 372 if head > items { 373 head = items 374 } 375 hidden := table.itemHidden.Load() 376 if hidden > tail { 377 tail = hidden 378 } 379 } 380 for _, table := range f.tables { 381 if err := table.truncateHead(head); err != nil { 382 return err 383 } 384 if err := table.truncateTail(tail); err != nil { 385 return err 386 } 387 } 388 f.frozen.Store(head) 389 f.tail.Store(tail) 390 return nil 391 } 392 393 // convertLegacyFn takes a raw freezer entry in an older format and 394 // returns it in the new format. 395 type convertLegacyFn = func([]byte) ([]byte, error) 396 397 // MigrateTable processes the entries in a given table in sequence 398 // converting them to a new format if they're of an old format. 399 func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { 400 if f.readonly { 401 return errReadOnly 402 } 403 f.writeLock.Lock() 404 defer f.writeLock.Unlock() 405 406 table, ok := f.tables[kind] 407 if !ok { 408 return errUnknownTable 409 } 410 // forEach iterates every entry in the table serially and in order, calling `fn` 411 // with the item as argument. If `fn` returns an error the iteration stops 412 // and that error will be returned. 413 forEach := func(t *freezerTable, offset uint64, fn func(uint64, []byte) error) error { 414 var ( 415 items = t.items.Load() 416 batchSize = uint64(1024) 417 maxBytes = uint64(1024 * 1024) 418 ) 419 for i := offset; i < items; { 420 if i+batchSize > items { 421 batchSize = items - i 422 } 423 data, err := t.RetrieveItems(i, batchSize, maxBytes) 424 if err != nil { 425 return err 426 } 427 for j, item := range data { 428 if err := fn(i+uint64(j), item); err != nil { 429 return err 430 } 431 } 432 i += uint64(len(data)) 433 } 434 return nil 435 } 436 // TODO(s1na): This is a sanity-check since as of now no process does tail-deletion. But the migration 437 // process assumes no deletion at tail and needs to be modified to account for that. 438 if table.itemOffset.Load() > 0 || table.itemHidden.Load() > 0 { 439 return errors.New("migration not supported for tail-deleted freezers") 440 } 441 ancientsPath := filepath.Dir(table.index.Name()) 442 // Set up new dir for the migrated table, the content of which 443 // we'll at the end move over to the ancients dir. 444 migrationPath := filepath.Join(ancientsPath, "migration") 445 newTable, err := newFreezerTable(migrationPath, kind, table.noCompression, false) 446 if err != nil { 447 return err 448 } 449 var ( 450 batch = newTable.newBatch() 451 out []byte 452 start = time.Now() 453 logged = time.Now() 454 offset = newTable.items.Load() 455 ) 456 if offset > 0 { 457 log.Info("found previous migration attempt", "migrated", offset) 458 } 459 // Iterate through entries and transform them 460 if err := forEach(table, offset, func(i uint64, blob []byte) error { 461 if i%10000 == 0 && time.Since(logged) > 16*time.Second { 462 log.Info("Processing legacy elements", "count", i, "elapsed", common.PrettyDuration(time.Since(start))) 463 logged = time.Now() 464 } 465 out, err = convert(blob) 466 if err != nil { 467 return err 468 } 469 if err := batch.AppendRaw(i, out); err != nil { 470 return err 471 } 472 return nil 473 }); err != nil { 474 return err 475 } 476 if err := batch.commit(); err != nil { 477 return err 478 } 479 log.Info("Replacing old table files with migrated ones", "elapsed", common.PrettyDuration(time.Since(start))) 480 // Release and delete old table files. Note this won't 481 // delete the index file. 482 table.releaseFilesAfter(0, true) 483 484 if err := newTable.Close(); err != nil { 485 return err 486 } 487 files, err := os.ReadDir(migrationPath) 488 if err != nil { 489 return err 490 } 491 // Move migrated files to ancients dir. 492 for _, f := range files { 493 // This will replace the old index file as a side-effect. 494 if err := os.Rename(filepath.Join(migrationPath, f.Name()), filepath.Join(ancientsPath, f.Name())); err != nil { 495 return err 496 } 497 } 498 // Delete by now empty dir. 499 if err := os.Remove(migrationPath); err != nil { 500 return err 501 } 502 return nil 503 }