Append-only WAL with CRC32-validated records, automatic segment rotation at 128 MiB, background periodic fsync, and crash recovery by truncation at the first corrupt or incomplete record. Record format: type(1) | len(4) | payload | crc32c(4). Two payload types: series (ref + labels) and samples (batch of ref/t/v). Fixed-width encoding througout. Torn-write harness truncates at every byte offset and asserts recovery produces a valid prefix of the original sequnce, both single-segment and multi-segment. DESIGN.md, NOTICE.md, and README.md.
143 lines
3.3 KiB
Go
143 lines
3.3 KiB
Go
package wal
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"math"
|
|
|
|
"git.dvdt.dev/david/ingot/labels"
|
|
)
|
|
|
|
var ErrShortPayload = errors.New("wal: payload too short")
|
|
|
|
// SeriesRecord is a WAL record that registers a new series.
|
|
type SeriesRecord struct {
|
|
Ref uint64
|
|
Labels []labels.Label
|
|
}
|
|
|
|
// RefSample is a single sample keyed by series ref.
|
|
type RefSample struct {
|
|
Ref uint64
|
|
T int64
|
|
V float64
|
|
}
|
|
|
|
// EncodeSeriesRecord appends the encoded series record to dst.
|
|
//
|
|
// ref(8) | nlabels(4) | for each: namelen(2) name valuelen(2) value
|
|
func EncodeSeriesRecord(dst []byte, rec SeriesRecord) []byte {
|
|
n := 8 + 4
|
|
for _, l := range rec.Labels {
|
|
n += 2 + len(l.Name) + 2 + len(l.Value)
|
|
}
|
|
dst = grow(dst, n)
|
|
off := len(dst) - n
|
|
|
|
binary.BigEndian.PutUint64(dst[off:], rec.Ref)
|
|
off += 8
|
|
binary.BigEndian.PutUint32(dst[off:], uint32(len(rec.Labels)))
|
|
off += 4
|
|
|
|
for _, l := range rec.Labels {
|
|
binary.BigEndian.PutUint16(dst[off:], uint16(len(l.Name)))
|
|
off += 2
|
|
off += copy(dst[off:], l.Name)
|
|
binary.BigEndian.PutUint16(dst[off:], uint16(len(l.Value)))
|
|
off += 2
|
|
off += copy(dst[off:], l.Value)
|
|
}
|
|
|
|
return dst
|
|
}
|
|
|
|
// DecodeSeriesRecord decodes a series payload. The returned Labels
|
|
// hold copies of the strings (not sub-slices of data).
|
|
func DecodeSeriesRecord(data []byte) (SeriesRecord, error) {
|
|
if len(data) < 12 {
|
|
return SeriesRecord{}, ErrShortPayload
|
|
}
|
|
|
|
ref := binary.BigEndian.Uint64(data)
|
|
nLabels := int(binary.BigEndian.Uint32(data[8:]))
|
|
off := 12
|
|
|
|
ls := make([]labels.Label, nLabels)
|
|
for i := range ls {
|
|
if off+2 > len(data) {
|
|
return SeriesRecord{}, ErrShortPayload
|
|
}
|
|
nameLen := int(binary.BigEndian.Uint16(data[off:]))
|
|
off += 2
|
|
if off+nameLen > len(data) {
|
|
return SeriesRecord{}, ErrShortPayload
|
|
}
|
|
name := string(data[off : off+nameLen])
|
|
off += nameLen
|
|
|
|
if off+2 > len(data) {
|
|
return SeriesRecord{}, ErrShortPayload
|
|
}
|
|
valueLen := int(binary.BigEndian.Uint16(data[off:]))
|
|
off += 2
|
|
if off+valueLen > len(data) {
|
|
return SeriesRecord{}, ErrShortPayload
|
|
}
|
|
value := string(data[off : off+valueLen])
|
|
off += valueLen
|
|
|
|
ls[i] = labels.Label{Name: name, Value: value}
|
|
}
|
|
|
|
return SeriesRecord{Ref: ref, Labels: ls}, nil
|
|
}
|
|
|
|
// EncodeSamplesRecord appends the encoded samples record to dst.
|
|
//
|
|
// nsamples(4) | for each: ref(8) t(8) v(8)
|
|
func EncodeSamplesRecord(dst []byte, samples []RefSample) []byte {
|
|
n := 4 + len(samples)*24
|
|
dst = grow(dst, n)
|
|
off := len(dst) - n
|
|
|
|
binary.BigEndian.PutUint32(dst[off:], uint32(len(samples)))
|
|
off += 4
|
|
|
|
for _, s := range samples {
|
|
binary.BigEndian.PutUint64(dst[off:], s.Ref)
|
|
off += 8
|
|
binary.BigEndian.PutUint64(dst[off:], uint64(s.T))
|
|
off += 8
|
|
binary.BigEndian.PutUint64(dst[off:], math.Float64bits(s.V))
|
|
off += 8
|
|
}
|
|
|
|
return dst
|
|
}
|
|
|
|
// DecodeSamplesRecord decodes a samples payload.
|
|
func DecodeSamplesRecord(data []byte) ([]RefSample, error) {
|
|
if len(data) < 4 {
|
|
return nil, ErrShortPayload
|
|
}
|
|
|
|
n := int(binary.BigEndian.Uint32(data))
|
|
off := 4
|
|
|
|
if len(data) < 4+n*24 {
|
|
return nil, ErrShortPayload
|
|
}
|
|
|
|
samples := make([]RefSample, n)
|
|
for i := range samples {
|
|
samples[i].Ref = binary.BigEndian.Uint64(data[off:])
|
|
off += 8
|
|
samples[i].T = int64(binary.BigEndian.Uint64(data[off:]))
|
|
off += 8
|
|
samples[i].V = math.Float64frombits(binary.BigEndian.Uint64(data[off:]))
|
|
off += 8
|
|
}
|
|
|
|
return samples, nil
|
|
}
|