Help us shape the future of Go! Take the 2026 Go Developer Survey

storage

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 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