github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/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  }