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