ceremonyclient/pebble/record/rotation.go

83 lines
3.8 KiB
Go
Raw Permalink Normal View History

2024-01-03 07:31:42 +00:00
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
package record
// RotationHelper is a type used to inform the decision of rotating a record log
// file.
//
// The assumption is that multiple records can be coalesced into a single record
// (called a snapshot). Starting a new file, where the first record is a
// snapshot of the current state is referred to as "rotating" the log.
//
// Normally we rotate files when a certain file size is reached. But in certain
// cases (e.g. contents become very large), this can result in too frequent
// rotation. This helper contains logic to impose extra conditions on the
// rotation.
//
// The rotation helper uses "size" as a unit-less estimation that is correlated
// with the on-disk size of a record or snapshot.
type RotationHelper struct {
// lastSnapshotSize is the size of the last snapshot.
lastSnapshotSize int64
// sizeSinceLastSnapshot is the sum of sizes of records applied since the last
// snapshot.
sizeSinceLastSnapshot int64
lastRecordSize int64
}
// AddRecord makes the rotation helper aware of a new record.
func (rh *RotationHelper) AddRecord(recordSize int64) {
rh.sizeSinceLastSnapshot += recordSize
rh.lastRecordSize = recordSize
}
// ShouldRotate returns whether we should start a new log file (with a snapshot).
// Does not need to be called if other rotation factors (log file size) are not
// satisfied.
func (rh *RotationHelper) ShouldRotate(nextSnapshotSize int64) bool {
// The primary goal is to ensure that when reopening a log file, the number of
// edits that need to be replayed on top of the snapshot is "sane" while
// keeping the rotation frequency as low as possible.
//
// For the purposes of this description, we assume that the log is mainly
// storing a collection of "entries", with edits adding or removing entries.
// Consider the following cases:
//
// - The number of live entries is roughly stable: after writing the snapshot
// (with S entries), we require that there be enough edits such that the
// cumulative number of entries in those edits, E, be greater than S. This
// will ensure that at most 50% of data written out is due to rotation.
//
// - The number of live entries K in the DB is shrinking drastically, say from
// S to S/10: After this shrinking, E = 0.9S, and so if we used the previous
// snapshot entry count, S, as the threshold that needs to be exceeded, we
// will further delay the snapshot writing. Which means on reopen we will
// need to replay 0.9S edits to get to a version with 0.1S entries. It would
// be better to create a new snapshot when E exceeds the number of entries in
// the current version.
//
// - The number of live entries L in the DB is growing; say the last snapshot
// had S entries, and now we have 10S entries, so E = 9S. If we required
// that E is at least the current number of entries, we would further delay
// writing a new snapshot (which is not desirable).
//
// The logic below uses the min of the last snapshot size count and the size
// count in the current version.
return rh.sizeSinceLastSnapshot > rh.lastSnapshotSize || rh.sizeSinceLastSnapshot > nextSnapshotSize
}
// Rotate makes the rotation helper aware that we are rotating to a new snapshot
// (to which we will apply the latest edit).
func (rh *RotationHelper) Rotate(snapshotSize int64) {
rh.lastSnapshotSize = snapshotSize
rh.sizeSinceLastSnapshot = rh.lastRecordSize
}
// DebugInfo returns the last snapshot size and size of the edits since the last
// snapshot; used for testing and debugging.
func (rh *RotationHelper) DebugInfo() (lastSnapshotSize int64, sizeSinceLastSnapshot int64) {
return rh.lastSnapshotSize, rh.sizeSinceLastSnapshot
}