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

execopt

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package execopt carries the execution-time knobs that are not planning decisions: how much memory one query may hold, and where it is allowed to spill.

Why a ledger rather than a counter

Seven operators in ursus buffer, and every one of them retains the SOURCE's batches rather than copies of them. Columns share payloads on top of that — Rename shares one, so does a sliced String column's character buffer. So a counter that adds up what each operator was handed double-counts, and a budget that double-counts fires early and unpredictably, which is worse for a user than having no budget at all.

The ledger below therefore counts ALLOCATIONS, keyed by data.BufferID, with one reference count per allocation across every account. An allocation two sinks hold is one entry, and it is released when the last account lets go.

What it deliberately does not do

It does not hook the allocator. arrowx pins memory.NewGoAllocator, whose Free is a no-op and which reports nothing, so there is no allocation-site seam to hook — and design/physical.md's proposed kernel.Ctx, which would have carried a reservation into every kernel, was never built. Accounting therefore happens where memory is RETAINED, not where it is allocated. That is exactly right for the buffering operators, which are the only things that hold memory across batches, and it is silent about a single kernel's transient output.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bytes

func Bytes(n int64) string

Bytes renders a byte count the way a person reads one.

Types

type Account

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

Account is one operator's share of a budget.

It holds each allocation at most once — retaining the same buffer twice does not hold more memory — so Retain is idempotent per allocation and Release is exact.

A nil *Account is a working no-op, which is what lets an operator written against it work unchanged when no budget was configured.

func (*Account) Check

func (a *Account) Check() error

Check returns a resource error naming this operator when the query is over budget. An operator that can spill calls Over instead and spills.

func (*Account) Over

func (a *Account) Over() bool

Over reports whether the QUERY is over its ceiling.

Deliberately query-wide rather than per-account: the budget is shared, so an operator that can spill should do so when the query is under pressure, not only when its own share happens to be the large one.

func (*Account) Release

func (a *Account) Release()

Release drops everything this account holds.

func (*Account) Retain

func (a *Account) Retain(b *data.Batch)

Retain records that the operator now holds the batch's allocations.

func (*Account) RetainBytes

func (a *Account) RetainBytes(n int64)

RetainBytes records plain-Go state that has no BufferID.

This is not a rounding detail. Three of the largest retentions in the engine are not batches at all — windowSink's one int32 per row PER DISTINCT PARTITIONING, joinBuildSink's one int32 per build row, and quantileAcc's []float64 per group — so a walker over batches alone would report a window sink at half its true size.

func (*Account) Used

func (a *Account) Used() int64

Used returns what this account is holding.

type Budget

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

Budget is one query's memory ceiling and spill location.

A nil *Budget means unlimited and untracked; every method is nil-safe so that operators need no branch of their own.

func NewBudget

func NewBudget(limit int64, spillDir string) *Budget

NewBudget returns a budget with the given limit in bytes; a limit <= 0 means unlimited, and the ledger still tracks the peak so a caller can measure what a query actually held.

func (*Budget) Account

func (b *Budget) Account(op string) *Account

Account opens an operator's view of the budget.

op is the user-facing operator name — "sort", "group_by", "join" — because the only useful thing a memory error can say is which operator could not fit.

func (*Budget) Limit

func (b *Budget) Limit() int64

Limit returns the ceiling in bytes, or 0 when unlimited.

func (*Budget) NoteSpill

func (b *Budget) NoteSpill()

NoteSpill records that one run was written.

func (*Budget) Over

func (b *Budget) Over() bool

Over reports whether the query is above its ceiling. Always false when unlimited.

func (*Budget) Peak

func (b *Budget) Peak() int64

Peak returns the highest retained total the query reached.

This is the number that demonstrates bounded memory. A test that asserts a query completed proves only that it did not run out; asserting the peak proves it stayed under the ceiling.

func (*Budget) SpillDir

func (b *Budget) SpillDir() string

SpillDir returns the directory spill files are created under, or "" for the system temporary directory.

func (*Budget) Spills

func (b *Budget) Spills() int64

Spills returns how many runs were written to disk.

It exists so a test can assert that a small budget ACTUALLY spilled. Without it, "external sort matches in-memory sort" passes trivially when the spill never triggered.

func (*Budget) Used

func (b *Budget) Used() int64

Used returns the current retained total in bytes.

Jump to

Keyboard shortcuts

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