recordengine

package
v0.40.1 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: Apache-2.0 Imports: 41 Imported by: 0

Documentation

Overview

Package recordengine is the shared storage engine for **record-shaped** signals — logs and traces (later, profiles' sample table). A record-shaped signal is a stream (a Resource+Scope identity, indexed by the postings layer) of rows that each carry a primary timestamp plus a fixed set of typed columns. The engine is the structural twin of package engine (metrics) but generic over a Schema: it owns the in-memory head, columnar flush to immutable parts, the durable bucket-index + identity-index stateless read path, append-only merge with retention, per-part column blooms, lazy column decode, and the fetch.Fetcher contract — none of which is signal-specific. A signal package supplies the column Schema and projects its model into the engine's column vectors; the engine treats the columns opaquely.

The timestamp (the sort key) and the int128 stream id (the Resource+Scope hash) are implicit and not part of the Schema; the schema lists only the per-record columns.

Index

Constants

View Source
const DefaultStreamCostSketchGroups = 4096

DefaultStreamCostSketchGroups is StreamCostOptions.MaxSketchGroups's default: 4096 groups, i.e. 32 MiB of transient sketch state. It is set to cover a real store's group count (a corpus grouped by a pod-suffixed service.name produced 2256) rather than to ration the sketches — 8 KiB per group is negligible against the column decode the pass is already doing. The cap exists for the pathological case, grouping a million-stream store by raw stream id.

Variables

View Source
var ErrCorruptKeys = errors.New("recordengine: corrupt record-keys footer")

ErrCorruptKeys is returned when a serialized record-keys footer fails to parse.

Functions

func EncodeWAL

func EncodeWAL(b *Batch) []byte

EncodeWAL frames a batch as a replication/WAL payload — a stream-identity record followed by the batch's records — exactly the form Engine.ApplyPrimary and Engine.ApplyReplicated replay. The cluster write path builds a tenant's payload by concatenating EncodeWAL over its streams and routing it to the ring primary.

Types

type AppendLimits added in v0.4.0

type AppendLimits struct {
	// MaxSeries caps the number of distinct streams buffered in the head. A record that would
	// register a new stream once the head already holds MaxSeries is rejected (the whole batch,
	// since a batch is one stream); known streams are unaffected. 0 ⇒ unlimited.
	MaxSeries int64
	// MaxInFlightBytes caps the head's buffered record bytes. A record arriving while the head is
	// at or over the cap is rejected (memory backpressure) until a flush drains it. 0 ⇒ unlimited.
	MaxInFlightBytes int64
}

AppendLimits are the per-call admission limits the record head enforces while buffering a batch. The zero value imposes no limit; they are passed per Engine.AppendBatch call so a consumer's hot-reloaded tenant policy takes effect on the next write. The engine stays policy-agnostic — it sees only these numbers. They mirror the metric engine's limits so the facade maps one tenant.Limits to both.

type AppendResult added in v0.4.0

type AppendResult struct {
	Accepted            int
	RejectedOOO         int // older than the out-of-order window
	RejectedCardinality int // would exceed AppendLimits.MaxSeries (a new stream)
	RejectedBytes       int // head at or over AppendLimits.MaxInFlightBytes
}

AppendResult reports the disposition of an Engine.AppendBatch run by reason, so the caller can attribute an OTLP partial-success precisely.

func (AppendResult) Rejected added in v0.4.0

func (r AppendResult) Rejected() int

Rejected returns the total number of rejected records across all reasons.

type Batch

type Batch struct {
	Stream   signal.SeriesID
	Identity func() signal.Series // materialized only when the stream is newly seen
	Ts       []int64
	Ints     [][]int64  // len == schema int count; Ints[k][row]
	Bytes    [][][]byte // len == schema byte count; Bytes[k][row]
	// Side is an optional encoded side-store delta (the content-addressed symbols this batch's
	// records reference) absorbed by [Config.SideStore]. nil when the engine has no side store.
	Side []byte
}

Batch is one stream's projected records in the engine's column layout: the primary timestamps plus the int and byte column vectors in the schema's per-kind order. The signal package builds it; the engine treats the columns opaquely. Byte slices may alias the source batch (the head clones them on append).

func (*Batch) ByteSize added in v0.4.0

func (b *Batch) ByteSize() int64

ByteSize returns the in-flight memory the batch's records occupy (timestamps, int columns, and the lengths of the byte columns) — the size a caller charges against an ingest-rate budget.

func (*Batch) Len

func (b *Batch) Len() int

Len returns the number of records in the batch.

type BloomMode

type BloomMode uint8

BloomMode says whether and how a column feeds its per-part bloom for predicate pruning.

const (
	// BloomNone builds no bloom for the column.
	BloomNone BloomMode = iota
	// BloomFullText tokenizes the column's value (lowercased words) so a `contains token`
	// condition can prune a part whose bloom lacks the token (e.g. a log body, a span name).
	BloomFullText
	// BloomAttrs treats the column as a serialized [signal.Attributes] blob and adds key-scoped
	// equality (`key‖value`) and full-text (`key‖word`) tokens, so per-record attribute equality
	// and contains conditions prune.
	BloomAttrs
	// BloomEquality adds the column's exact value as a token so a `column == value` condition can
	// prune (e.g. trace-by-id over the trace_id column).
	BloomEquality
)

type CardinalityStat added in v0.12.0

type CardinalityStat struct {
	TotalSeries        int64 // distinct streams
	DistinctLabelNames int
	SymbolCount        int
	Top                []LabelCard // sorted by Series desc, then Name; truncated to the requested top-N
}

CardinalityStat summarizes the engine's label cardinality (the head's index spans head ∪ flushed streams). TotalSeries and SymbolCount are exact; Top is the highest-cardinality label names.

type Column

type Column struct {
	Name  string
	Kind  Kind
	Codec chunk.Codec
	Bloom BloomMode
}

Column is one per-record column of a Schema: its name, physical kind, on-disk codec (zero ⇒ the kind's default), and bloom contribution.

type ColumnCostStat added in v0.37.0

type ColumnCostStat struct {
	Name      string
	RawBytes  int64
	DiskBytes int64 // approximate, as [StreamCostStat.DiskBytes]

	// Distinct estimates the distinct values the group's rows hold in this column (HyperLogLog,
	// ~1.6% standard error). 0 for an int column and for a group outside the sketch budget.
	Distinct int64

	// DistinctNormalized is Distinct over values with every run of ASCII digits collapsed to '#'.
	// It is what separates a genuinely high-entropy column from a templated one carrying an embedded
	// timestamp or id: a group whose Distinct is large and whose DistinctNormalized is tiny is not a
	// storage problem but a parsing one — the same line, repeated, never turned into fields.
	DistinctNormalized int64
}

ColumnCostStat is one column's share of a group's cost.

type ColumnStat added in v0.12.0

type ColumnStat struct {
	Name     string
	Kind     string // physical type: int64 / float64 / bytes / int128
	Codec    string // value codec
	Compress string // block-compression algorithm
	Level    int    // block-compression level (0 ⇒ algorithm default or uncompressed)
}

ColumnStat is one part column's physical description (from the manifest).

type Config

type Config struct {
	// Schema is the per-record column set this engine stores (required; the signal supplies it).
	Schema *Schema
	// OOOWindow is a per-stream lateness bound: a record older than OOOWindow (nanoseconds) behind
	// the stream's own newest admitted record is rejected. 0 disables.
	OOOWindow int64
	// WAL, when non-nil, durably logs streams and records for crash recovery. nil ⇒ ephemeral.
	WAL *wal.SegmentWriter
	// Backend stores flushed parts. Required for [Engine.Flush]; nil ⇒ head-only.
	Backend backend.Backend
	// Prefix is the backend key prefix under which this engine's parts are written.
	Prefix string
	// Term reports this writer's current ownership term for Prefix — which tenure of the shard
	// this engine is writing as. It is stamped into every bucket index written, so a reader can
	// order two indexes of the same prefix even when neither the part names nor FlushedEpoch
	// moved; see [github.com/oteldb/storage/backend/bucketindex.Generation]. nil is a writer with
	// no cluster, whose generation is then a plain local counter.
	Term func() uint64
	// WriterID is this writer's stable identity — the cluster node id — under which its WAL flush
	// watermark is kept in the bucket index. It matters because that index is *shared*: over a
	// shared object store every replica of a shard commits one index object under one prefix, and
	// the watermark is a per-node count of that node's own flushes, so one scalar in a shared
	// object is meaningless to whichever node did not write it (see
	// [github.com/oteldb/storage/backend/bucketindex.WriterEpoch]).
	//
	// Empty is the anonymous writer: a single-writer engine, which keeps the sole pre-v4 slot.
	// Leaving it empty where two engines do share a prefix makes them share one slot, which is
	// the defect the slots exist to prevent.
	WriterID string
	// SideStore, when non-nil, is a signal-supplied content-addressed auxiliary store (e.g. the
	// profiles symbol store) that the engine persists as part sidecars on flush and unions on merge.
	// nil ⇒ no side data (logs, traces).
	SideStore SideStore
	// Obs is the observability handle (spans + metrics). nil ⇒ a no-op handle.
	Obs *obs.Obs
	// Signal is the signal label for this engine's metrics ("log"/"trace"/"profile"); the facade
	// sets it per signal. Empty ⇒ "record".
	Signal string
	// MaxPartBytes bounds a flushed/merged part's approximate uncompressed size. It is what lets
	// size-tiered compaction seal large parts and bound both part count and a single merge's decoded
	// working set (see compact.go) — without it a continuously-ingesting engine's merge grows to
	// re-materialize the whole dataset every cycle. 0 ⇒ unlimited (merge everything into one part;
	// the legacy behavior, unbounded working set). The facade resolves it from the tenant policy.
	MaxPartBytes int64
	// MergeMemoryBytes is how much memory all concurrent merges together may hold. A merge holds its
	// selected sources decoded plus the output buffer it is filling, so this bounds the merge cap
	// (see mergecap.go) independently of MaxPartBytes — a tiering target sized for the disk must not
	// size a working set the process cannot hold. 0 ⇒ a share of the process memory budget
	// (GOMEMLIMIT, else the cgroup limit, else host memory); negative ⇒ unbounded.
	MergeMemoryBytes int64
	// MergeConcurrency reports how many merges may run concurrently in this process, dividing the
	// merge memory allowance so they cannot collectively exceed it. nil or ≤ 1 ⇒ no division.
	MergeConcurrency func() int
	// MergeCompression block-compresses the columns of merged (compacted) parts on top of their chunk
	// codecs — the cold, long-lived data. Flushed parts stay codec-only so ingest is cheap; the
	// background merge is where recompression is amortized. Record byte columns are dict-coded but not
	// entropy-coded, so ZSTD here is a large on-disk win (≈10× on log-shaped data). AlgorithmNone (the
	// default) keeps the legacy uncompressed behavior.
	MergeCompression compress.Algorithm
	// MergeCompressionLevel is the level for MergeCompression (ZSTD only). 0 ⇒ the algorithm default.
	MergeCompressionLevel compress.Level
	// MinFreeBytes is the headroom the engine leaves unused on a backend that reports its free
	// space: a flush is refused, and the ingest path starts rejecting, once the medium holds less
	// than the pending part plus this. It leaves a merge room for its output — a merge must write
	// before it can retire the inputs it frees. 0 ⇒ [diskguard.DefaultReserveBytes]; negative ⇒ the
	// byte axis is not checked.
	MinFreeBytes int64
	// MinFreeInodes is the same headroom on the object-count axis, for a backend that reports free
	// inodes. It is checked separately because a part is many small objects: an inode table can
	// exhaust with the disk half empty, and byte accounting cannot see it. 0 ⇒
	// [diskguard.DefaultReserveInodes]; negative ⇒ the inode axis is not checked.
	MinFreeInodes int64
}

Config configures an Engine.

type Engine

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

Engine is one tenant's record store for a signal. Safe for concurrent use.

func New

func New(cfg Config) *Engine

New returns an engine with an empty head over cfg.Schema.

func (*Engine) AppendBatch

func (e *Engine) AppendBatch(b *Batch, limits AppendLimits) (AppendResult, error)

AppendBatch ingests one stream's records: it registers the stream on first sight, appends each record through the admission limits (OOO window, cardinality, in-flight bytes), and logs accepted records to the WAL. It returns an AppendResult breaking accepted/rejected down by reason, so the caller can report an exact OTLP partial-success. Safe for concurrent use.

func (*Engine) ApplyPrimary

func (e *Engine) ApplyPrimary(data []byte, limits AppendLimits) (accepted []byte, res AppendResult, err error)

ApplyPrimary applies a write as the stream's **primary**: it runs each record through the admission-checked append path (the single OOO decision for the shard, plus the cardinality and in-flight-memory valves from limits) and re-frames the *accepted* records into a WAL payload to replicate to the secondary owners. It returns that accepted payload and an AppendResult breaking the disposition down by reason, so the clustered ingest path attributes OTLP partial-success exactly like the single-node path. Every replica converges on the same data. Safe for concurrent use.

func (*Engine) ApplyReplicated

func (e *Engine) ApplyReplicated(data []byte) error

ApplyReplicated applies a replicated write from the primary verbatim (no OOO re-check — the primary already decided the accepted set), so all replicas hold identical data. Safe for concurrent use.

func (*Engine) Cardinality added in v0.12.0

func (e *Engine) Cardinality(topN int) CardinalityStat

Cardinality summarizes the engine's label cardinality from the head's inverted index (which spans every stream ever seen, flushed or not). topN bounds the returned Top slice (≤0 returns all label names). It takes a read lock and does no backend I/O.

func (*Engine) Close

func (e *Engine) Close(ctx context.Context) error

Close flushes any buffered records to a part and closes the WAL. It does not stop a background loop — the owner ([storage.Storage]) does that before calling Close.

func (*Engine) CloseWAL added in v0.4.0

func (e *Engine) CloseWAL() error

CloseWAL closes the engine's open WAL segment file handle without flushing the head or checkpointing — modeling a process crash, where the OS reclaims open descriptors but the on-disk WAL segments survive for replay. The head is left as-is (and lost, as a crash would lose it). A crash-recovery test uses this to release the file handle so the WAL directory can be removed even on platforms that refuse to delete a file held open by a live process (Windows). No-op without a WAL.

func (*Engine) Fetch

func (e *Engine) Fetch(ctx context.Context, r fetch.Request) (fetch.Iterator, error)

Fetch implements fetch.Fetcher over head ∪ flushed parts: it resolves matchers to streams, gathers each stream's in-window records (decoding only the referenced columns), applies the column conditions and projection, and returns one batch per stream sorted by timestamp.

func (*Engine) Flush

func (e *Engine) Flush(ctx context.Context) error

Flush writes the head's buffered records to a new immutable part and clears the buffers. No-op if the head is empty. Requires a Config.Backend.

func (*Engine) HeadBytes added in v0.4.0

func (e *Engine) HeadBytes() int64

HeadBytes returns the engine's buffered record bytes — the in-flight memory measure for AppendLimits.MaxInFlightBytes. It counts the live head plus the buffers an in-flight flush has detached but not yet published: those stay resident, so the measure must not dip to zero (and let a second head in) for the duration of a slow flush.

func (*Engine) HeadRecordCount

func (e *Engine) HeadRecordCount() int

HeadRecordCount returns the number of records buffered in the head across all streams.

func (*Engine) IdentityBytes added in v0.37.0

func (e *Engine) IdentityBytes() int64

IdentityBytes returns the resident bytes of the engine's identity state — the symbol table, the stream index, the postings lists and the per-stream out-of-order watermarks. It is reported separately from Engine.HeadBytes because a flush does not drain it: identities outlive their records and are cleared only by Engine.Reset, so this number tracks the engine's all-time stream count, not its buffered data.

func (*Engine) Keys added in v0.5.0

func (e *Engine) Keys(start, end int64) []KeyInfo

Keys enumerates the distinct attribute keys present across the engine's streams (head ∪ flushed parts) with at least one record in [start, end], each tagged with the scope(s) it appears in. A zero start AND end disables the time filter. Stream-identity keys (resource/scope) come from the authoritative series index; record-attribute keys come from the head buffers and each in-window part's persisted key footer. Window precision is part-granular and best-effort: a key whose records all fall outside the window may still be returned (harmless). Safe for concurrent use.

func (*Engine) LoadParts

func (e *Engine) LoadParts(ctx context.Context) error

LoadParts reconstructs the engine's durable state from the object store: the part set from the bucket index and the stream identity index from the persisted object. A head-only engine is a no-op. Replaces current parts. It assumes this node owns the prefix — it sweeps the part objects the index does not name (see [Engine.sweepOrphansLocked]).

func (*Engine) Merge

func (e *Engine) Merge(ctx context.Context, retainFrom int64) error

Merge runs one size-tiered compaction cycle, dropping records older than retainFrom (retention; retainFrom ≤ 0 disables it). It compacts only a bounded group of similarly-sized parts plus any part retention must rewrite (see [selectMergeParts]) — not the whole part set — so a single merge's decoded working set is O(part size), not O(dataset). No-op when no tier has accumulated enough parts and no part needs retention. Records are append-only: a stream's records are concatenated across parts (no value dedup) and re-sorted by timestamp.

func (*Engine) MergeBacklog added in v0.12.0

func (e *Engine) MergeBacklog() int

MergeBacklog returns the parts a merge may still take — the flushed parts less the sealed ones, which no merge reconsiders. It is MergeShape.Backlog; use Engine.MergeShape for the rest of the selector's inputs.

func (*Engine) MergeRunning added in v0.12.0

func (e *Engine) MergeRunning() bool

MergeRunning reports whether a merge/compaction is currently executing on this engine (an in-memory liveness flag for introspection).

func (*Engine) MergeShape added in v0.37.0

func (e *Engine) MergeShape() MergeShape

MergeShape returns the selector's view of the engine's parts. It takes a brief read lock, does no backend I/O and decodes nothing, so it is safe to poll at dashboard cadence.

func (*Engine) MergeWith added in v0.37.0

func (e *Engine) MergeWith(ctx context.Context, opts MergeOptions) error

MergeWith is Engine.Merge with the merge parameterized: the one background-merge entry point, so compaction, retention, and a forced compaction are the same pass over the immutable parts.

func (*Engine) PartCount

func (e *Engine) PartCount() int

PartCount returns the number of flushed parts (introspection).

func (*Engine) Parts added in v0.12.0

func (e *Engine) Parts() []PartStat

Parts returns an in-memory snapshot of the parts the engine can serve — its own, plus the ones it adopted from a rival writer's index — under a read lock, with no backend I/O and no decode, so it is safe to poll. It is the servable set rather than this writer's own, because that is what a read answers from and what the completeness accounting has to measure. For byte sizes, codecs, and chunk counts, use Engine.PartsDetailed.

func (*Engine) PartsDetailed added in v0.12.0

func (e *Engine) PartsDetailed(ctx context.Context) ([]PartDetailStat, error)

PartsDetailed augments Engine.Parts with each part's on-backend byte size, column/codec layout, and chunk (granule) count. It reads object sizes from the backend, so unlike Parts it is not hot-path-free — call it for a drill-down view, not a high-frequency poll. Each part is ref-held for the duration so a concurrent merge cannot reclaim its objects mid-read.

func (*Engine) PruneIdentities added in v0.37.0

func (e *Engine) PruneIdentities(ctx context.Context) (int, error)

PruneIdentities drops the stream identities no live data names any more — those the retention side of a merge left behind — rebuilding the resident index around the survivors. It returns the number removed (0 when nothing could have died, or when too little has).

Every node may call it, owner or replica: identity is scoped to the part that holds it, so the live set is derived from *this node's* parts and means exactly "what this node can still serve". A part a replica has not yet synced brings its identities with it when it arrives. It takes the same flush/merge exclusion as those paths, so no publish can add an identity underneath it.

func (*Engine) PruneIdentitiesWith added in v0.37.0

func (e *Engine) PruneIdentitiesWith(ctx context.Context, opts PruneOptions) (int, error)

PruneIdentitiesWith is Engine.PruneIdentities with explicit options.

func (*Engine) RefreshReplica

func (e *Engine) RefreshReplica(ctx context.Context) error

RefreshReplica brings a replica node's view up to date with the shared object store: it reconstructs the flushed parts and trims its head to the still-unflushed window. With no shared store, a safe no-op.

func (*Engine) Replay

func (e *Engine) Replay(dir string) error

Replay rebuilds the head (and side store) from the WAL segments in dir (durable restart). It skips segments at or below the flush watermark recovered by Engine.LoadParts (call LoadParts first), so records already in a flushed part are not re-applied — exactly-once recovery.

func (*Engine) Reset

func (e *Engine) Reset(ctx context.Context) error

Reset discards all data (head + parts, including the records an in-flight flush detached) and deletes this engine's objects, returning it to the empty state without reallocating. It waits for an in-flight flush or merge to finish first, so that operation cannot publish its part into the reset engine. Objects of parts a concurrent fetch is still reading are left for the deferred reclaim ([reclaimRetired], run by the next flush/merge cycle) rather than deleted underneath the reader. Safe for concurrent use.

func (*Engine) Series

func (e *Engine) Series(matchers []fetch.Matcher, start, end int64) []signal.Series

Series returns the identities of the streams matching matchers that hold at least one record in [start, end] — the enumeration primitive behind profile-type / label listing. A zero start AND end disables the time filter (return every matching stream). The time filter is part-overlap granular (a returned stream is guaranteed to match the matchers; its in-window records are a superset check). Safe for concurrent use.

func (*Engine) SideSnapshot

func (e *Engine) SideSnapshot(ctx context.Context) (map[string][]byte, error)

SideSnapshot returns the engine's full side-store tables — the live head accumulator unioned with every flushed part's sidecars — as named payloads, for a signal to build a resolver over (e.g. the profiles symbol store). nil when the engine has no side store. Safe for concurrent use.

func (*Engine) Stats added in v0.10.0

func (e *Engine) Stats() Stats

Stats returns an in-memory snapshot of the engine's state under a single read lock (no backend I/O, no decode), safe to poll at dashboard cadence. Part byte sizes are not included.

func (*Engine) StreamCost added in v0.37.0

func (e *Engine) StreamCost(ctx context.Context, opts StreamCostOptions) ([]StreamCostStat, error)

StreamCost attributes the engine's flushed parts to streams (or, with StreamCostOptions.GroupBy, to a label's values): rows, decoded bytes, an approximate compressed share, and per-column distinct estimates.

It answers "which service is costing me, and why" — the diagnosis the label-cardinality and per-part views cannot give, since neither attributes bytes to a stream nor says anything about the cardinality of the values inside the columns, which is what drives the cost.

It is the heaviest introspection call in the engine: every accounted byte column of every live part is read and decoded once (int columns and the timestamp are accounted arithmetically, with no decode). Run it as an operator drill-down, not on a schedule, and narrow it with StreamCostOptions.Columns when only one column is in question. Each part is ref-held for the duration, so a concurrent merge cannot reclaim it mid-read.

func (*Engine) StreamCount

func (e *Engine) StreamCount() int

StreamCount returns the number of distinct streams in the head.

func (*Engine) SyncWAL added in v0.3.0

func (e *Engine) SyncWAL() error

SyncWAL fsyncs the engine's WAL, if any (the background WALSyncInterval path). No-op without a WAL.

func (*Engine) WALState added in v0.12.0

func (e *Engine) WALState() (segments int, bytes int64, epoch uint64, ok bool)

WALState returns the current WAL segment count, the open segment's byte size, and the active flush epoch. ok is false when the engine has no WAL (ephemeral). It takes a read lock, excluding concurrent appends (which hold the write lock).

type KeyInfo added in v0.5.0

type KeyInfo struct {
	Key   []byte
	Scope KeyScope
}

KeyInfo is a distinct attribute key and the union of the scopes it was observed in. Key aliases engine-owned bytes (a head identity or a decoded part-footer entry); copy it to retain.

type KeyScope added in v0.5.0

type KeyScope uint8

KeyScope is a bitset of the scopes an attribute key was observed in. A key can appear in more than one — e.g. as a resource attribute on one stream and a per-record attribute on another — and the bitset records every scope, so a caller can tell a stream label from a record attribute (or both).

const (
	// KeyScopeResource marks a resource attribute (part of the stream identity, postings-indexed).
	KeyScopeResource KeyScope = 1 << iota
	// KeyScopeScope marks an instrumentation-scope attribute (also stream identity).
	KeyScopeScope
	// KeyScopeRecord marks a per-record attribute (the serialized attrs column).
	KeyScopeRecord
)

type Kind

type Kind uint8

Kind is a column's physical type. Records use only int64 and byte-string columns (floats live in the metrics engine); a typed value is projected onto one of these at the language edge.

const (
	// KindInt64 is a signed 64-bit column (timestamps-relative, severities, durations, ids, …).
	KindInt64 Kind = iota
	// KindBytes is a byte-string column (bodies, names, trace ids, serialized attributes, …).
	KindBytes
)

type LabelCard added in v0.12.0

type LabelCard struct {
	Name           string
	Series         int64
	DistinctValues int
}

LabelCard is one label name's cardinality: how many streams carry it and how many distinct values it takes across them.

type MergeOptions added in v0.37.0

type MergeOptions struct {
	// RetainFrom drops records older than this absolute unix-nanosecond cutoff (retention);
	// ≤ 0 disables it.
	RetainFrom int64
	// Force compacts a bucket's unsealed parts whatever their tiers, instead of waiting for one tier
	// to accumulate minTierParts of them — the operator escape from a part set the tier rule will
	// never select. It bypasses the selection heuristic only: sealing, the time-bucket ladder, and
	// the cumulative-bytes cap still bound what one merge decodes and holds.
	Force bool
}

MergeOptions parameterizes a merge. The zero value is a plain compaction.

type MergeShape added in v0.37.0

type MergeShape struct {
	// Parts is the flushed parts; Sealed those already at the cap, which no merge reconsiders;
	// Backlog the rest — the parts a merge may still take.
	Parts   int
	Sealed  int
	Backlog int
	// Candidates is how many parts the next merge would select right now. 0 with a non-zero Backlog
	// is the stuck state: parts remain mergeable but no tier of any time bucket holds minTierParts
	// of them, which is what [MergeOptions.Force] exists to break.
	Candidates int
	// CapBytes is the seal threshold in effect, in decoded bytes (0 ⇒ sealing disabled).
	CapBytes int64
	// Tiers is how many distinct size tiers the unsealed parts fall into and LargestTierParts the
	// count in the fullest of them — a LargestTierParts below minTierParts is why nothing merges.
	// Tiers span the whole engine here, not one time bucket, so LargestTierParts is an upper bound
	// on what the ladder can select.
	Tiers            int
	LargestTierParts int
	// MinTierParts is the same-tier count a merge waits for.
	MinTierParts int
}

MergeShape is the merge selector's view of the flushed parts: the inputs to the decision the background merge makes each cycle. Without them an engine sitting on a part count it will never reduce is indistinguishable from an idle healthy one — the two differ only in whether the parts are sealed and whether one tier has accumulated enough of the rest.

type PartDetailStat added in v0.12.0

type PartDetailStat struct {
	PartStat

	Bytes   int64        // sum of the part's backend object sizes
	Chunks  int          // sparse-index granules: ceil(RowCount / GranuleSize)
	Columns []ColumnStat // per-column physical layout
}

PartDetailStat augments PartStat with fields that need a backend read: the on-backend byte size (summed over the part's objects) and the column/codec layout and chunk count from the manifest (cached on the open part, so only Bytes incurs additional I/O).

type PartStat added in v0.12.0

type PartStat struct {
	ID      string // the part's backend key prefix
	MinTime int64  // inclusive unix-ns bounds of the part's records
	MaxTime int64
	Series  int   // distinct streams in the part (len of its row-range index)
	Rows    int64 // total records (sum of the per-stream row spans)
	// SizeBytes is the part's *decoded* footprint as recorded in its manifest — what the merge cap
	// compares against, and so what explains why a part is or is not sealed. It needs no I/O. Unlike
	// the metric engine's [engine.PartStat.SizeBytes], which is a size on disk, this is the size in
	// memory: the record merge is bounded by what it holds, not by what it writes. 0 for a part
	// written before the manifest recorded it (the row estimate then stands in internally).
	SizeBytes int64
}

PartStat is one flushed part's in-memory shape (no backend I/O, no decode): identity, time bounds, and the stream/row counts from the part's in-memory row-range index.

type PruneOptions added in v0.37.0

type PruneOptions struct {
	// Force runs the prune even when the background thresholds would skip it — an engine that has
	// merged nothing away since the last pass, or one whose dead set is too small to pay for the
	// rebuild. It is what an operator-triggered sweep wants.
	Force bool
}

PruneOptions tunes an identity prune.

type Schema

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

Schema is the ordered set of per-record columns a signal stores. It is immutable after construction and shared by every engine, part, and record batch of that signal.

func NewSchema

func NewSchema(cols ...Column) *Schema

NewSchema builds a Schema from its columns (declaration order is preserved within each kind).

type SideStore

type SideStore interface {
	// Absorb merges one batch's encoded side delta ([Batch.Side]) into the live accumulator.
	Absorb(delta []byte) error
	// Encode serializes the accumulated side data into named sidecar payloads (name → bytes),
	// written as {prefix}/sym-{name}.bin at flush.
	Encode() map[string][]byte
	// Reset clears the live accumulator (after a flush drains the head).
	Reset()
	// Restore merges an [SideStore.Encode] snapshot back into the live accumulator. The engine calls
	// it when a flush fails after the snapshot+Reset: the records return to the head, so their side
	// data must too. Content-addressing makes the merge a plain dedup with whatever the accumulator
	// gained meanwhile.
	Restore(snapshot map[string][]byte) error
	// Names returns the sidecar names to read back for a part on merge (the keys [SideStore.Encode]
	// may produce). A part missing a named sidecar is skipped.
	Names() []string
	// Union merges the loaded sidecars of the compacted parts (one map per part) and returns the
	// merged named payloads to write under the new part. Pure; ignores the live accumulator.
	Union(parts []map[string][]byte) (map[string][]byte, error)
}

SideStore is an optional per-engine auxiliary store that rides the part lifecycle. A signal whose records reference content-addressed side data (e.g. the profiles symbol store: strings, functions, locations, stacks) supplies one via Config.SideStore; the engine then absorbs each batch's delta (Batch.Side) into a live accumulator, persists the accumulator as part sidecars on flush, and unions the sidecars of compacted parts on merge. The engine treats the data opaquely — only the signal package knows the table formats.

Content-addressing is the load-bearing assumption: an entry's id is a hash of its content, so the same entry has the same id everywhere and SideStore.Union is a plain dedup with no id remap.

All methods are called under the engine's lock, so an implementation need not be safe for concurrent use. SideStore.Union must be a pure function of its arguments and must not read or mutate the live accumulator (it merges already-flushed part data, independent of the head).

type Stats added in v0.10.0

type Stats struct {
	Streams     int64 // distinct streams ever seen (index span: head ∪ flushed)
	HeadRecords int64 // records currently buffered in the head (unflushed)
	HeadBytes   int64 // buffered record bytes, head + in-flight flush (the in-flight memory measure)
	// IdentityBytes is the resident identity state (symbols + stream index + postings + OOO
	// watermarks) — memory a flush does not drain, and which no other counter here reports.
	IdentityBytes int64
	Parts         int   // flushed immutable parts
	MinTime       int64 // oldest flushed record time (unix ns); 0 when no parts
	MaxTime       int64 // newest record time across parts and the head (unix ns); 0 when empty
	// OutOfSpace is set while the engine refuses writes because its backend is out of bytes or
	// inodes. Reads still answer from what is on disk; it clears when a flush finds room again.
	OutOfSpace bool
}

Stats is an in-memory snapshot of a record engine's state for introspection (no backend I/O).

type StreamCostOptions added in v0.37.0

type StreamCostOptions struct {
	// GroupBy is the stream label (a resource or scope attribute name, e.g. "service.name") whose
	// value keys the report. Empty groups by raw stream id. Grouping by label is the useful form:
	// what an operator can act on is a service, and stream identity is a field policy that can
	// change under them.
	GroupBy string

	// Columns restricts the byte columns that are decoded and attributed (nil ⇒ every byte column).
	// The int columns and the timestamp are always accounted — they need no decode — so a narrowed
	// request still reports complete row counts, and RawBytes/DiskBytes cover exactly the columns
	// listed in each group's Columns.
	Columns []string

	// TopN keeps only the N costliest groups by RawBytes (≤0 ⇒ every group).
	TopN int

	// MaxSketchGroups bounds how many groups carry distinct estimates, which cost 8 KiB of sketch
	// each for the duration of one column's pass (≤0 ⇒ [DefaultStreamCostSketchGroups]). The budget
	// goes to the groups with the most rows; the rest report DistinctEstimated false.
	MaxSketchGroups int
}

StreamCostOptions selects what Engine.StreamCost attributes and how much of it to estimate.

type StreamCostStat added in v0.37.0

type StreamCostStat struct {
	Key     string // the GroupBy label's value, or the stream id; empty ⇒ the label is absent
	Streams int    // distinct streams folded into this group
	Rows    int64

	// RawBytes is the decoded footprint of the group's rows over the accounted columns.
	RawBytes int64

	// DiskBytes is APPROXIMATE. Compression is per column per frame and a frame spans whatever
	// streams its rows fall in, so a group's compressed footprint is not directly measurable: each
	// frame's compressed size is apportioned across the groups holding its rows by their raw-byte
	// share. Rows are (stream, ts)-ordered, so most frames hold one stream and the estimate is
	// close; a group narrower than a frame is the case where it is not.
	DiskBytes int64

	// DistinctEstimated reports whether the distinct counts below were computed for this group — see
	// [StreamCostOptions.MaxSketchGroups]. False ⇒ Distinct/DistinctNormalized are 0, not zero.
	DistinctEstimated bool

	Columns []ColumnCostStat
}

StreamCostStat is one group's (one label value's, or one stream's) share of the engine's flushed parts. The head is not included — it holds no compressed bytes to attribute.

Jump to

Keyboard shortcuts

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