mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-26 20:30:10 +01:00
c23b66a1ad
This reduces memory usage and memory fragmentation when working with big number of time series
212 lines
6.7 KiB
Go
212 lines
6.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
|
)
|
|
|
|
// blockHeader is a header for a time series block.
|
|
//
|
|
// Each block contains rows for a single time series. Rows are sorted
|
|
// by timestamp.
|
|
//
|
|
// A single time series may span multiple blocks.
|
|
type blockHeader struct {
|
|
// TSID is the TSID for the block.
|
|
// Multiple blocks may have the same TSID.
|
|
TSID TSID
|
|
|
|
// MinTimestamp is the minimum timestamp in the block.
|
|
//
|
|
// This is the first timestamp, since rows are sorted by timestamps.
|
|
MinTimestamp int64
|
|
|
|
// MaxTimestamp is the maximum timestamp in the block.
|
|
//
|
|
// This is the last timestamp, since rows are sorted by timestamps.
|
|
MaxTimestamp int64
|
|
|
|
// FirstValue is the first value in the block.
|
|
//
|
|
// It is stored here for better compression level, since usually
|
|
// the first value significantly differs from subsequent values
|
|
// which may be delta-encoded.
|
|
FirstValue int64
|
|
|
|
// TimestampsBlockOffset is the offset in bytes for a block
|
|
// with timestamps in timestamps file.
|
|
TimestampsBlockOffset uint64
|
|
|
|
// ValuesBlockOffset is the offset in bytes for a block with values
|
|
// in values file.
|
|
ValuesBlockOffset uint64
|
|
|
|
// TimestampsBlocksSize is the size in bytes for a block with timestamps.
|
|
TimestampsBlockSize uint32
|
|
|
|
// ValuesBlockSize is the size in bytes for a block with values.
|
|
ValuesBlockSize uint32
|
|
|
|
// RowsCount is the number of rows in the block.
|
|
//
|
|
// The block must contain at least one row.
|
|
RowsCount uint32
|
|
|
|
// Scale is the 10^Scale multiplier for values in the block.
|
|
Scale int16
|
|
|
|
// TimestampsMarshalType is the marshal type used for marshaling
|
|
// a block with timestamps.
|
|
TimestampsMarshalType encoding.MarshalType
|
|
|
|
// ValuesMarshalType is the marshal type used for marshaling
|
|
// a block with values.
|
|
ValuesMarshalType encoding.MarshalType
|
|
|
|
// PrecisionBits is the number of significant bits when using
|
|
// MarshalTypeNearestDelta2 encoding.
|
|
//
|
|
// Possible values are in the range [1...64], where
|
|
// 1 means max 50% error,
|
|
// 2 means max 25% error,
|
|
// n means max 100/(2^n)% error,
|
|
// 64 means exact values.
|
|
//
|
|
// Lower PrecisionBits give better block compression and speed.
|
|
PrecisionBits uint8
|
|
}
|
|
|
|
// Less returns true if b is less than src.
|
|
func (bh *blockHeader) Less(src *blockHeader) bool {
|
|
if bh.TSID.MetricID == src.TSID.MetricID {
|
|
// Fast path for identical TSIDs.
|
|
return bh.MinTimestamp < src.MinTimestamp
|
|
}
|
|
|
|
// Slow path for distinct TSIDs.
|
|
return bh.TSID.Less(&src.TSID)
|
|
}
|
|
|
|
// marshaledBlockHeaderSize is the size of marshaled block header.
|
|
var marshaledBlockHeaderSize = func() int {
|
|
var bh blockHeader
|
|
data := bh.Marshal(nil)
|
|
return len(data)
|
|
}()
|
|
|
|
// Marshal appends marshaled bh to dst and returns the result.
|
|
func (bh *blockHeader) Marshal(dst []byte) []byte {
|
|
dst = bh.TSID.Marshal(dst)
|
|
dst = encoding.MarshalInt64(dst, bh.MinTimestamp)
|
|
dst = encoding.MarshalInt64(dst, bh.MaxTimestamp)
|
|
dst = encoding.MarshalInt64(dst, bh.FirstValue)
|
|
dst = encoding.MarshalUint64(dst, bh.TimestampsBlockOffset)
|
|
dst = encoding.MarshalUint64(dst, bh.ValuesBlockOffset)
|
|
dst = encoding.MarshalUint32(dst, bh.TimestampsBlockSize)
|
|
dst = encoding.MarshalUint32(dst, bh.ValuesBlockSize)
|
|
dst = encoding.MarshalUint32(dst, bh.RowsCount)
|
|
dst = encoding.MarshalInt16(dst, bh.Scale)
|
|
dst = append(dst, byte(bh.TimestampsMarshalType), byte(bh.ValuesMarshalType), bh.PrecisionBits)
|
|
return dst
|
|
}
|
|
|
|
// Unmarshal unmarshals bh from src and returns the rest of src.
|
|
func (bh *blockHeader) Unmarshal(src []byte) ([]byte, error) {
|
|
if len(src) < marshaledBlockHeaderSize {
|
|
return src, fmt.Errorf("too short block header; got %d bytes; want %d bytes", len(src), marshaledBlockHeaderSize)
|
|
}
|
|
|
|
tail, err := bh.TSID.Unmarshal(src)
|
|
if err != nil {
|
|
return src, fmt.Errorf("cannot unmarshal TSID: %s", err)
|
|
}
|
|
src = tail
|
|
|
|
bh.MinTimestamp = encoding.UnmarshalInt64(src)
|
|
src = src[8:]
|
|
bh.MaxTimestamp = encoding.UnmarshalInt64(src)
|
|
src = src[8:]
|
|
bh.FirstValue = encoding.UnmarshalInt64(src)
|
|
src = src[8:]
|
|
bh.TimestampsBlockOffset = encoding.UnmarshalUint64(src)
|
|
src = src[8:]
|
|
bh.ValuesBlockOffset = encoding.UnmarshalUint64(src)
|
|
src = src[8:]
|
|
bh.TimestampsBlockSize = encoding.UnmarshalUint32(src)
|
|
src = src[4:]
|
|
bh.ValuesBlockSize = encoding.UnmarshalUint32(src)
|
|
src = src[4:]
|
|
bh.RowsCount = encoding.UnmarshalUint32(src)
|
|
src = src[4:]
|
|
bh.Scale = encoding.UnmarshalInt16(src)
|
|
src = src[2:]
|
|
bh.TimestampsMarshalType = encoding.MarshalType(src[0])
|
|
src = src[1:]
|
|
bh.ValuesMarshalType = encoding.MarshalType(src[0])
|
|
src = src[1:]
|
|
bh.PrecisionBits = uint8(src[0])
|
|
src = src[1:]
|
|
|
|
if bh.RowsCount == 0 {
|
|
return src, fmt.Errorf("RowsCount in block header cannot be zero")
|
|
}
|
|
if err = encoding.CheckMarshalType(bh.TimestampsMarshalType); err != nil {
|
|
return src, fmt.Errorf("unsupported TimestampsMarshalType: %s", err)
|
|
}
|
|
if err = encoding.CheckMarshalType(bh.ValuesMarshalType); err != nil {
|
|
return src, fmt.Errorf("unsupported ValuesMarshalType: %s", err)
|
|
}
|
|
if err = encoding.CheckPrecisionBits(bh.PrecisionBits); err != nil {
|
|
return src, err
|
|
}
|
|
if bh.TimestampsBlockSize > 2*8*maxBlockSize {
|
|
return src, fmt.Errorf("too big TimestampsBlockSize; got %d; cannot exceed %d", bh.TimestampsBlockSize, 2*8*maxBlockSize)
|
|
}
|
|
if bh.ValuesBlockSize > 2*8*maxBlockSize {
|
|
return src, fmt.Errorf("too big ValuesBlockSize; got %d; cannot exceed %d", bh.ValuesBlockSize, 2*8*maxBlockSize)
|
|
}
|
|
|
|
return src, nil
|
|
}
|
|
|
|
// unmarshalBlockHeaders unmarshals all the block headers from src,
|
|
// appends them to dst and returns the appended result.
|
|
//
|
|
// Block headers must be sorted by bh.TSID.
|
|
func unmarshalBlockHeaders(dst []blockHeader, src []byte, blockHeadersCount int) ([]blockHeader, error) {
|
|
if blockHeadersCount <= 0 {
|
|
logger.Panicf("BUG: blockHeadersCount must be greater than zero; got %d", blockHeadersCount)
|
|
}
|
|
dstLen := len(dst)
|
|
if n := dstLen + blockHeadersCount - cap(dst); n > 0 {
|
|
dst = append(dst[:cap(dst)], make([]blockHeader, n)...)
|
|
dst = dst[:dstLen]
|
|
}
|
|
var bh blockHeader
|
|
for len(src) > 0 {
|
|
tmp, err := bh.Unmarshal(src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot unmarshal block header: %s", err)
|
|
}
|
|
src = tmp
|
|
dst = append(dst, bh)
|
|
}
|
|
|
|
newBHS := dst[dstLen:]
|
|
|
|
// Verify the number of read block headers.
|
|
if len(newBHS) != blockHeadersCount {
|
|
return nil, fmt.Errorf("invalid number of block headers found: %d; want %d block headers", len(newBHS), blockHeadersCount)
|
|
}
|
|
|
|
// Verify that block headers are sorted by tsid.
|
|
if !sort.SliceIsSorted(newBHS, func(i, j int) bool { return newBHS[i].TSID.Less(&newBHS[j].TSID) }) {
|
|
return nil, fmt.Errorf("block headers must be sorted by tsid; unmarshaled unsorted block headers: %+v", newBHS)
|
|
}
|
|
|
|
return dst, nil
|
|
}
|