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