chunk

package
v0.28.0 Latest Latest
Warning

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

Go to latest
Published: Jul 2, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package chunk implements the zero-alloc value-column codecs (DESIGN.md §14 M0): delta-of-delta timestamps, Gorilla XOR float gauges, T64, dictionary, and scaled-decimal+nearest-delta. Each codec is an append-style Encode(dst []byte, ...) []byte over bitstream and is fuzzed for round-trip identity. Not yet implemented (M0 in progress; bitstream is the first foundation).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeBytes

func DecodeBytes(dst [][]byte, src []byte) ([][]byte, int, error)

DecodeBytes decodes a dictionary-encoded column from src into dst. Returned byte slices alias src.

func DecodeFloats

func DecodeFloats(dst []float64, src []byte) ([]float64, int, error)

DecodeFloats decodes a Gorilla XOR encoded float64 column from src into dst.

func DecodeFloatsDecimal

func DecodeFloatsDecimal(dst []float64, src []byte) ([]float64, int, error)

DecodeFloatsDecimal decodes a scaled-decimal + nearest-delta encoded float64 column.

func DecodeIntsT64

func DecodeIntsT64(dst []int64, src []byte) ([]int64, int, error)

DecodeIntsT64 decodes a T64-encoded int64 column from src into dst.

func DecodeTimestamps

func DecodeTimestamps(dst []int64, src []byte) ([]int64, int, error)

DecodeTimestamps decodes a DoD-encoded timestamp column from src into dst (growing it as needed) and returns the result with the number of source bytes consumed.

func EncodeBytes

func EncodeBytes(dst []byte, vals [][]byte) []byte

EncodeBytes appends a dictionary-encoded []byte column to dst and returns the extended slice (DESIGN.md §6, §14 M0).

It uses a pool.ByteIntMap (xxh3-based open-addressing hash table) instead of Go's map[string]int: xxh3 is faster than the runtime's string hash, and []byte keys avoid the string-conversion allocation on every lookup. For low-cardinality columns (≤256 distinct), each row id is 1 byte.

Layout: [uvarint rows] [1 byte flag] [payload]

flagDict payload: [uvarint dictSize] [dict entries: per entry uvarint len + bytes] [row ids: 1 byte each if dictSize ≤ 256, else 2 bytes big-endian each]

flagFlat payload: per row [uvarint len]bytes (the flat fallback for >65536 distinct).

For repeated encodes of many column batches, prefer a DictEncoder: it keeps the hash table and scratch slices cache-warm across calls instead of paying the sync.Pool Get/Put on each call.

func EncodeBytesBlob added in v0.25.0

func EncodeBytesBlob(dst, blob []byte, offsets []int32) []byte

EncodeBytesBlob is EncodeBytes over the blob+offsets column form (cell i is blob[offsets[i]:offsets[i+1]]; len(offsets) == rows+1, offsets[0] == 0). The output is byte-identical to EncodeBytes over the equivalent [][]byte, without materializing a view per row.

func EncodeBytesRaw added in v0.2.0

func EncodeBytesRaw(dst []byte, vals [][]byte) []byte

EncodeBytesRaw encodes a byte-string column without a dictionary (CodecBytesRaw): when every value shares one length it writes a single shared width followed by the values back-to-back (the fixed-width form, ideal for id columns); otherwise it falls back to the same length-prefixed inline layout as the dictionary codec's flat fallback. The output is a self-describing bytes-column stream that DecodeBytes and DecodeBytesDict read directly.

Layout: [uvarint rows] [1 byte flag] [payload]

flagFixed payload: [uvarint width] [rows × width bytes]
flagFlat  payload: per row [uvarint len][bytes]

func EncodeBytesRawBlob added in v0.25.0

func EncodeBytesRawBlob(dst, blob []byte, offsets []int32) []byte

EncodeBytesRawBlob is EncodeBytesRaw over the blob+offsets column form (see EncodeBytesBlob); the output is byte-identical to EncodeBytesRaw over the equivalent [][]byte.

func EncodeFloats

func EncodeFloats(dst []byte, vals []float64) []byte

EncodeFloats appends a Gorilla XOR encoded float64 column to dst and returns the extended slice (DESIGN.md §6, §14 M0; Prometheus/Gorilla-style).

Layout: [uvarint rows] [bitstream payload]:

row 0:  raw64(Float64bits(v0))    // 64 raw bits, MSB-first
row 1+: xor-delta(v_n, v_{n-1})  // per the 3-case prefix below

The XOR control-bit cases (delta = Float64bits(new) XOR Float64bits(prev)):

0b0                    → delta == 0, value unchanged         (1 bit)
0b10 + M bits          → reuse prev leading/trailing, M = 64-leading-trailing
0b11 + 5b lead + 6b sig + S bits → new leading/trailing, S = sig (0→64 sentinel)

Leading-zero count is clamped to 31 (5-bit field). The significant-bit count is stored in 6 bits; 0 is a sentinel meaning 64 (when leading==trailing==0). The meaningful XOR bits are stored right-shifted by trailing (the trailing zeros are dropped and re-shifted on decode).

func EncodeFloatsDecimal

func EncodeFloatsDecimal(dst []byte, vals []float64, precisionBits uint8) []byte

EncodeFloatsDecimal appends a scaled-decimal + nearest-delta encoded float64 column to dst (DESIGN.md §6, §14 M0; VictoriaMetrics-style). precisionBits controls the lossiness: 64 = lossless (no bit zeroing), <64 = lossy (zeros trailing low bits of deltas so the varint stream is ZSTD-compressible, reaching ~0.4–0.8 B/point).

Layout: [uvarint rows] [varint exponent] [uvarint precisionBits]

[zigzag-varint v0] [zigzag-varint delta1] ... [zigzag-varint deltaN-1]

Pipeline:

  1. Convert each float64 to a scaled int64 with a shared exponent e: f ≈ v * 10^e. The mantissa is minimized (trailing decimal zeros stripped into e).
  2. Compute first differences: d_n = v_n - v_{n-1}.
  3. If precisionBits < 64, zero the trailing (originBits - precisionBits) low bits of each delta (the "nearest-delta" step), with counter-reset hysteresis.
  4. Zigzag-varint encode the deltas; v0 is stored out-of-band.

With precisionBits=64 the round-trip is exact for integer-valued floats and near-exact (within float64 rounding) for fractional values. With precisionBits<64 the codec is deliberately lossy.

func EncodeIntsT64

func EncodeIntsT64(dst []byte, vals []int64) []byte

EncodeIntsT64 appends a T64-encoded int64 column to dst (DESIGN.md §6, §14 M0; ClickHouse T64: bit-transpose + crop of unused high bits).

Layout: [uvarint rows] [8 bytes min] [8 bytes max] [transposed valuable bits]

The valuable-bit count is numBits = 64 - clz(min ^ max). If min == max (numBits==0), only the header is stored (constant column). Otherwise the values are processed in blocks of 64: each block is transposed so that bit j of all 64 values sits in a single uint64, and only the lowest numBits transposed rows are emitted (the high zero bits are cropped). On decode the cropped high bits are restored by OR-ing the common high prefix (min & ~((1<<numBits)-1)).

This codec is lossless. It is a preprocessor: the output is designed to be fed to a general compressor (ZSTD/LZ4) which benefits from the transposed, repetitive layout.

func EncodeTimestamps

func EncodeTimestamps(dst []byte, ts []int64) []byte

EncodeTimestamps appends a delta-of-delta encoded timestamp column to dst and returns the extended slice (DESIGN.md §6, §14 M0; Prometheus-style).

Layout: [uvarint rows] [bitstream payload]:

row 0:  varint(t0)                 // signed zigzag varint, absolute timestamp
row 1:  uvarint(t1 - t0)           // unsigned varint, first delta
row 2+: dod(t_n)                   // delta-of-delta, 5-case prefix (see below)

The DoD bit layout (dod = tDelta_n - tDelta_{n-1}):

0b0              + 0  bits  → dod == 0            (1 bit total)
0b10             + 14 bits  → |dod| ≤ 2^13        (16 bits)
0b110            + 17 bits  → |dod| ≤ 2^16        (20 bits)
0b1110           + 20 bits  → |dod| ≤ 2^19        (24 bits)
0b1111           + 64 bits  → escape, full int64  (68 bits)

The 14/17/20-bit values are stored as unsigned two's-complement and sign-extended on decode (values ≥ 1<<(n-1) are negative). The 64-bit escape is a raw int64 cast. Timestamps must be non-decreasing for optimal compression; decreasing timestamps still round-trip but produce 68-bit escapes.

func EncodeU128

func EncodeU128(dst []byte, vals []U128) []byte

EncodeU128 appends a run-length-encoded U128 column to dst (CodecID128). The id sort key of a metric part is long runs of one id (all of a series' samples are contiguous), so RLE stores a distinct id + run length per run — far smaller than storing every row.

Layout: [uvarint rows] then per run [u128 id big-endian][uvarint runLength].

func IsEOF

func IsEOF(err error) bool

IsEOF reports whether err is an end-of-stream error.

Types

type Codec

type Codec uint8

Codec identifies a column encoding (DESIGN.md §6, §14 M0). It is stored in the column/part metadata so the decoder selects the right [Decode] function. Values are stable (persisted/wire-stable); never reorder.

const (
	// CodecNone is the identity codec (raw values, no compression).
	CodecNone Codec = iota
	// CodecDoD is delta-of-delta for timestamps (Prometheus-style, ms-resolution
	// buckets 14/17/20/64). ~0 bits/sample at constant scrape interval.
	CodecDoD
	// CodecGorilla is Gorilla XOR for float64 values. ~1 bit/sample when consecutive
	// float values share leading/trailing zero structure.
	CodecGorilla
	// CodecDict is dictionary encoding for low-cardinality strings (≤256 distinct →
	// 1 byte/row).
	CodecDict
	// CodecT64 is ClickHouse T64: bit-transpose + crop for low-range int64 values.
	// Lossless; crops unused high bits, transposes 64-wide blocks.
	CodecT64
	// CodecDecimal is VictoriaMetrics-style scaled-decimal + nearest-delta for
	// float64. Optionally lossy (precisionBits < 64): zeros trailing low bits of
	// deltas so the varint stream is ZSTD-compressible.
	CodecDecimal
	// CodecID128 is run-length encoding for 128-bit id columns ([U128]): a sorted id
	// column (e.g. the SeriesID sort key of a metric part) is long runs of one id, so
	// RLE stores a distinct id + run length per run.
	CodecID128
	// CodecBytesRaw stores byte-string columns with no dictionary: a fixed-width block
	// (one shared length + values back-to-back) when every value is the same length, else
	// length-prefixed inline values. It is the right choice for high-cardinality, near-unique
	// id columns (e.g. a span id) where a dictionary is pure overhead. The decoder reads the
	// self-describing flag in the stream, so [DecodeBytes]/[DecodeBytesDict] handle it without
	// consulting the codec.
	CodecBytesRaw
)

func (Codec) String

func (c Codec) String() string

String returns a stable lower-case codec name.

type DecimalDecoder added in v0.15.0

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

DecimalDecoder is a forward cursor over a scaled-decimal + nearest-delta encoded float64 column (EncodeFloatsDecimal).

func NewDecimalDecoder added in v0.15.0

func NewDecimalDecoder(src []byte) (*DecimalDecoder, error)

NewDecimalDecoder parses the column header and returns a cursor over its rows.

func (*DecimalDecoder) Len added in v0.15.0

func (d *DecimalDecoder) Len() int

Len returns the column's total row count.

func (*DecimalDecoder) Next added in v0.15.0

func (d *DecimalDecoder) Next() (float64, error)

Next decodes and returns the next value. It returns an IsEOF error after the last row.

func (*DecimalDecoder) Pos added in v0.15.0

func (d *DecimalDecoder) Pos() int

Pos returns the number of rows already decoded.

type DictColumn

type DictColumn struct {
	// Entries holds the unique values, indexed by id. In the flat-fallback form
	// (IDWidth == 0) it instead holds one value per row, indexed directly by row.
	Entries [][]byte
	// IDs is the raw row-id array as stored: IDWidth bytes per row, big-endian. It is
	// nil when IDWidth == 0.
	IDs []byte
	// IDWidth is the number of id bytes per row: 1 (dict ≤ 256 entries), 2 (larger
	// dict), or 0 for the flat fallback where Entries is indexed by row directly.
	IDWidth int
}

DictColumn is a decoded dictionary column in its split form: the unique entries plus the raw per-row id array, without the gather that materializes one []byte slice header per row. It is the output of DecodeBytesDict.

Profiling (see git history of the dict codec) showed 82% of DecodeBytes time was the gather loop `dst[i] = entries[idBuf[i]]`, which is pure store bandwidth and cannot be sped up in place. The split form defers that work to DictColumn.At, so a query that filters most rows on their id before projecting pays the gather only for surviving rows (DESIGN.md §7 fetch contract: decode only what the query touches).

All byte slices alias the source passed to the decode; they are valid only until that source is mutated or released. A DictColumn must not be retained past its source.

func DecodeBytesDict

func DecodeBytesDict(src []byte) (*DictColumn, int, error)

DecodeBytesDict decodes a dictionary-encoded column (as written by EncodeBytes or DictEncoder.Encode) into its split form without gathering per-row values. It returns the column, the number of source bytes consumed, and any error. The returned DictColumn and all its byte slices alias src.

This allocates a fresh DictColumn; to decode repeatedly with zero allocations, reuse one column via DictColumn.DecodeBytes.

func (*DictColumn) At

func (c *DictColumn) At(row int) []byte

At returns the value at row, as a view aliasing the decode source. row must be in [0, Len()).

func (*DictColumn) DecodeBytes

func (c *DictColumn) DecodeBytes(src []byte) (int, error)

DecodeBytes decodes a dictionary-encoded column from src into c, reusing c's Entries backing array. It returns the number of source bytes consumed and any error. The decoded byte slices alias src.

func (*DictColumn) Len

func (c *DictColumn) Len() int

Len reports the number of rows in the column. It is derived from the fields so a DictColumn can be constructed directly (e.g. a constant column: one entry, an all-zero IDs slice of the desired length, IDWidth 1).

type DictEncoder

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

DictEncoder is a reusable dictionary-column encoder. Unlike the standalone EncodeBytes, it owns its pool.ByteIntMap and scratch slices for the lifetime of the encoder, so a sequence of DictEncoder.Encode calls keeps the hash table and id arrays cache-warm and skips the per-call sync.Pool Get/Put.

It is intended for the write path, where one goroutine encodes many column batches in a row. The zero value is not usable; create one with NewDictEncoder. Not safe for concurrent use; pool one per goroutine.

func NewDictEncoder

func NewDictEncoder() *DictEncoder

NewDictEncoder returns a DictEncoder with a warm, empty hash table.

func (*DictEncoder) Encode

func (e *DictEncoder) Encode(dst []byte, vals [][]byte) []byte

Encode appends a dictionary-encoded column for vals to dst and returns the extended slice. The output is byte-identical to EncodeBytes for the same input. Each call resets the encoder's internal state first, so Encode is self-contained.

func (*DictEncoder) Release

func (e *DictEncoder) Release()

Release returns the encoder's hash table to the shared pool. After Release the encoder must not be used. Use it when an encoder's lifetime ends so the map can be reused by [NewByteIntMap]/EncodeBytes.

func (*DictEncoder) Reset

func (e *DictEncoder) Reset()

Reset clears the encoder's retained state without freeing its backing arrays, so the next DictEncoder.Encode starts from a clean, cache-warm encoder. Encode resets implicitly, so calling Reset is only needed to drop references to a prior batch's input values held in the entries slice.

type FloatDecoder added in v0.15.0

type FloatDecoder interface {
	Len() int
	Pos() int
	Next() (float64, error)
}

FloatDecoder is the forward-cursor interface over a float64 column, regardless of codec.

func NewConstFloatDecoder added in v0.15.0

func NewConstFloatDecoder(n int, value float64) FloatDecoder

NewConstFloatDecoder returns a cursor that yields value for n rows.

func NewFloatDecoder added in v0.15.0

func NewFloatDecoder(codec Codec, src []byte) (FloatDecoder, error)

NewFloatDecoder returns a forward cursor over a float64 column encoded with codec, or an error if codec is not a float codec (CodecGorilla, CodecDecimal).

type GorillaDecoder added in v0.15.0

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

GorillaDecoder is a forward cursor over a Gorilla XOR encoded float64 column (EncodeFloats).

func NewGorillaDecoder added in v0.15.0

func NewGorillaDecoder(src []byte) (*GorillaDecoder, error)

NewGorillaDecoder parses the column header and returns a cursor over its rows.

func (*GorillaDecoder) Len added in v0.15.0

func (d *GorillaDecoder) Len() int

Len returns the column's total row count.

func (*GorillaDecoder) Next added in v0.15.0

func (d *GorillaDecoder) Next() (float64, error)

Next decodes and returns the next value. It returns an IsEOF error after the last row.

func (*GorillaDecoder) Pos added in v0.15.0

func (d *GorillaDecoder) Pos() int

Pos returns the number of rows already decoded.

type TsCursor added in v0.15.0

type TsCursor interface {
	Len() int
	Pos() int
	Next() (int64, error)
}

TsCursor is the forward-cursor interface over an int64 timestamp column (CodecDoD).

func NewConstTsCursor added in v0.15.0

func NewConstTsCursor(n int, value int64) TsCursor

NewConstTsCursor returns a cursor that yields value for n rows.

type TsDecoder added in v0.15.0

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

TsDecoder is a forward cursor over a delta-of-delta encoded timestamp column (EncodeTimestamps).

func NewTsDecoder added in v0.15.0

func NewTsDecoder(src []byte) (*TsDecoder, error)

NewTsDecoder parses the column header and returns a cursor over its rows. bound is the byte length of the column stream (header + payload), used to bound-check the row count.

func (*TsDecoder) Len added in v0.15.0

func (d *TsDecoder) Len() int

Len returns the column's total row count.

func (*TsDecoder) Next added in v0.15.0

func (d *TsDecoder) Next() (int64, error)

Next decodes and returns the next timestamp. It returns an IsEOF error after the last row.

func (*TsDecoder) Pos added in v0.15.0

func (d *TsDecoder) Pos() int

Pos returns the number of rows already decoded.

type U128

type U128 struct {
	Hi, Lo uint64
}

U128 is an unsigned 128-bit value (a SeriesID at the storage boundary). Comparable, so runs of equal ids are detected directly.

func DecodeU128

func DecodeU128(dst []U128, src []byte) ([]U128, int, error)

DecodeU128 decodes a U128 column into dst (reusing its capacity), returning the result and bytes consumed.

Jump to

Keyboard shortcuts

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