github.com/cockroachdb/pebble@v1.1.2/record/rotation.go (about) 1 // Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package record 6 7 // RotationHelper is a type used to inform the decision of rotating a record log 8 // file. 9 // 10 // The assumption is that multiple records can be coalesced into a single record 11 // (called a snapshot). Starting a new file, where the first record is a 12 // snapshot of the current state is referred to as "rotating" the log. 13 // 14 // Normally we rotate files when a certain file size is reached. But in certain 15 // cases (e.g. contents become very large), this can result in too frequent 16 // rotation. This helper contains logic to impose extra conditions on the 17 // rotation. 18 // 19 // The rotation helper uses "size" as a unit-less estimation that is correlated 20 // with the on-disk size of a record or snapshot. 21 type RotationHelper struct { 22 // lastSnapshotSize is the size of the last snapshot. 23 lastSnapshotSize int64 24 // sizeSinceLastSnapshot is the sum of sizes of records applied since the last 25 // snapshot. 26 sizeSinceLastSnapshot int64 27 lastRecordSize int64 28 } 29 30 // AddRecord makes the rotation helper aware of a new record. 31 func (rh *RotationHelper) AddRecord(recordSize int64) { 32 rh.sizeSinceLastSnapshot += recordSize 33 rh.lastRecordSize = recordSize 34 } 35 36 // ShouldRotate returns whether we should start a new log file (with a snapshot). 37 // Does not need to be called if other rotation factors (log file size) are not 38 // satisfied. 39 func (rh *RotationHelper) ShouldRotate(nextSnapshotSize int64) bool { 40 // The primary goal is to ensure that when reopening a log file, the number of 41 // edits that need to be replayed on top of the snapshot is "sane" while 42 // keeping the rotation frequency as low as possible. 43 // 44 // For the purposes of this description, we assume that the log is mainly 45 // storing a collection of "entries", with edits adding or removing entries. 46 // Consider the following cases: 47 // 48 // - The number of live entries is roughly stable: after writing the snapshot 49 // (with S entries), we require that there be enough edits such that the 50 // cumulative number of entries in those edits, E, be greater than S. This 51 // will ensure that at most 50% of data written out is due to rotation. 52 // 53 // - The number of live entries K in the DB is shrinking drastically, say from 54 // S to S/10: After this shrinking, E = 0.9S, and so if we used the previous 55 // snapshot entry count, S, as the threshold that needs to be exceeded, we 56 // will further delay the snapshot writing. Which means on reopen we will 57 // need to replay 0.9S edits to get to a version with 0.1S entries. It would 58 // be better to create a new snapshot when E exceeds the number of entries in 59 // the current version. 60 // 61 // - The number of live entries L in the DB is growing; say the last snapshot 62 // had S entries, and now we have 10S entries, so E = 9S. If we required 63 // that E is at least the current number of entries, we would further delay 64 // writing a new snapshot (which is not desirable). 65 // 66 // The logic below uses the min of the last snapshot size count and the size 67 // count in the current version. 68 return rh.sizeSinceLastSnapshot > rh.lastSnapshotSize || rh.sizeSinceLastSnapshot > nextSnapshotSize 69 } 70 71 // Rotate makes the rotation helper aware that we are rotating to a new snapshot 72 // (to which we will apply the latest edit). 73 func (rh *RotationHelper) Rotate(snapshotSize int64) { 74 rh.lastSnapshotSize = snapshotSize 75 rh.sizeSinceLastSnapshot = rh.lastRecordSize 76 } 77 78 // DebugInfo returns the last snapshot size and size of the edits since the last 79 // snapshot; used for testing and debugging. 80 func (rh *RotationHelper) DebugInfo() (lastSnapshotSize int64, sizeSinceLastSnapshot int64) { 81 return rh.lastSnapshotSize, rh.sizeSinceLastSnapshot 82 }