storage

package
v0.20.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

Documentation

Overview

Package storage watches the disk under the data directory and keeps the server from running it full (ADR-0481).

A full disk is where an event-sourced engine is most fragile. The WAL can be repaired — a write it could not finish is cut off again — but the state store cannot: Pebble ends the process when it fails to write its manifest, and a checkpoint, which is how WAL space is given back, needs room of its own to be taken. So the guard is a reserve, not a reaction. While less than the reserve is available, the server takes no new writes (the HTTP layer answers 507) and starts no work of its own; what is already running may finish, and reads go on. Once the reserve and a margin above it are free again, it resumes by itself, with nothing to restart.

The Monitor reads the file system on a fixed cadence, off the run loop: a reading is a single statfs or GetDiskFreeSpaceExW call whose cost does not depend on the data (ADR-0382). It also forecasts when the reserve will be reached, without keeping a history: each file system carries two numbers, a smoothed consumption rate and how long it has been observed. Prometheus users get the same readings as gauges and can ask predict_linear for a forecast over as long a window as their retention holds.

Index

Constants

View Source
const DefaultInterval = 5 * time.Second

DefaultInterval is how often the disk is read.

View Source
const DefaultReserve uint64 = 1 << 30

DefaultReserve is the room the server keeps free for itself: 1 GiB.

It is sized from what still has to fit after the server stops taking writes. A state-store flush writes out its two 16 MiB memtables; a compaction writes its output before deleting its input; the manifest Pebble rewrites can reach 128 MiB; a checkpoint copies that manifest and Pebble's log beside the files it links; the WAL may roll a fresh segment; and the requests already admitted when the reading was taken still land. That adds up to roughly half a gibibyte. The default is twice that, because the cost of an overestimate is a server that pauses early and the cost of an underestimate is Pebble ending the process. It is an estimate from those sizes, not a measurement.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Dirs are the directories the server writes to. Several on one file system are
	// read as one.
	Dirs []string
	// Reserve is the space kept free; 0 turns the freeze off and leaves the warnings.
	Reserve uint64
	// Read reads a file system; diskspace.Of unless a test supplies one.
	Read func(dir string) (diskspace.Usage, error)
	// Now is the clock readings are timed by; time.Now unless a test supplies one.
	Now func() time.Time
	// Halted reports why the engine stopped, or nil while it runs.
	Halted func() error
	// Footprint reports what Atlas holds on disk. Called on every reading, off the
	// run loop, so it must be safe from any goroutine.
	Footprint func() Footprint
}

Config is what a Monitor is built from. Only Dirs is required.

type FileSystem

type FileSystem struct {
	// Paths are the directories of the server's that live on it.
	Paths          []string `json:"paths"`
	TotalBytes     uint64   `json:"totalBytes"`
	UsedBytes      uint64   `json:"usedBytes"`
	AvailableBytes uint64   `json:"availableBytes"`
	// UsedPercent is df's Use%: used / (used + available), to one decimal.
	UsedPercent float64 `json:"usedPercent"`
	// WarnBelowBytes is the available space under which this file system warns: the
	// reserve plus the larger of the reserve and a tenth of used + available.
	WarnBelowBytes uint64 `json:"warnBelowBytes"`
	// Decides marks the file system with the least room — the one that freezes the
	// server first.
	Decides  bool      `json:"decides"`
	Forecast *Forecast `json:"forecast,omitempty"`
	// contains filtered or unexported fields
}

FileSystem is one file system the server writes to.

type Footprint

type Footprint struct {
	WALBytes    int64  `json:"walBytes"`
	WALSegments int    `json:"walSegments"`
	StateBytes  uint64 `json:"stateBytes"`
}

Footprint is what Atlas itself holds on disk, as far as it can tell without walking a directory: the WAL's segment sizes and the state store's own account.

type Forecast

type Forecast struct {
	// BytesPerSecond is the smoothed net consumption: positive while the disk fills,
	// negative while something — retention, compaction — gives back more than is
	// written.
	BytesPerSecond float64 `json:"bytesPerSecond"`
	BytesPerDay    float64 `json:"bytesPerDay"`
	// SecondsUntilFreeze is how long until less than the reserve is available at
	// that rate; absent while nothing grows.
	SecondsUntilFreeze *float64 `json:"secondsUntilFreeze,omitempty"`
	// BasisSeconds is how much observation the rate rests on. It starts over when the
	// server does: nothing of it is stored.
	BasisSeconds float64 `json:"basisSeconds"`
}

Forecast is the consumption the monitor has measured and where it leads.

type Monitor

type Monitor struct {
	// contains filtered or unexported fields
}

Monitor reads the disk and decides whether the server may take writes.

func New

func New(cfg Config) *Monitor

New builds a monitor. It reads nothing until Sample or Run is called.

func (*Monitor) Frozen

func (m *Monitor) Frozen() bool

Frozen reports whether new writes are refused. Cheap enough for every request.

func (*Monitor) HandleGet

func (m *Monitor) HandleGet(w http.ResponseWriter, _ *http.Request)

HandleGet serves GET /api/v1/storage: the last reading, what it means, and the forecast. It reads nothing itself — the monitor's loop does — so it costs the same however full the disk is.

func (*Monitor) Run

func (m *Monitor) Run(quit <-chan struct{}, every time.Duration)

Run reads the disk every interval until quit is closed, starting at once so the first request already has a reading.

func (*Monitor) Sample

func (m *Monitor) Sample()

Sample reads every file system once and updates the conclusion.

func (*Monitor) Snapshot

func (m *Monitor) Snapshot() Snapshot

Snapshot returns the last conclusion. The caller owns the copy.

type Snapshot

type Snapshot struct {
	State  State  `json:"state"`
	Reason string `json:"reason"`
	// SampledAt is when the file systems were last read successfully; null before the
	// first reading.
	SampledAt    *time.Time   `json:"sampledAt"`
	ReserveBytes uint64       `json:"reserveBytes"`
	ResumeBytes  uint64       `json:"resumeBytes"`
	FileSystems  []FileSystem `json:"fileSystems"`
	// Forecast is the soonest any file system reaches the reserve.
	Forecast     *Forecast `json:"forecast,omitempty"`
	Atlas        Footprint `json:"atlas"`
	EngineHalted string    `json:"engineHalted,omitempty"`
	Error        string    `json:"error,omitempty"`
}

Snapshot is the monitor's last conclusion, as GET /api/v1/storage serves it.

type State

type State string

State is what the monitor concludes about the disk.

const (
	// StateOK is plenty of room and no forecast of trouble within a week.
	StateOK State = "ok"
	// StateWarn is room running low, or a forecast that reaches the reserve within a
	// week. Nothing is refused yet.
	StateWarn State = "warn"
	// StateFrozen is less than the reserve available: new writes are refused.
	StateFrozen State = "frozen"
	// StateHalted is the engine stopped after a batch could not be made durable. It
	// needs a restart, which is why it outranks every other state.
	StateHalted State = "halted"
	// StateUnknown is no usable reading: none taken yet, or the last one failed.
	StateUnknown State = "unknown"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL