Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
loki/pkg/dataset/layout/codec_array.go

206 lines
5.6 KiB

package layout
import (
"context"
"fmt"
"io"
"github.com/grafana/loki/v3/pkg/columnar"
"github.com/grafana/loki/v3/pkg/columnar/types"
"github.com/grafana/loki/v3/pkg/dataset/array"
"github.com/grafana/loki/v3/pkg/dataset/buffer"
"github.com/grafana/loki/v3/pkg/expr"
"github.com/grafana/loki/v3/pkg/memory"
)
type arrayWriter struct {
inner array.Writer
sink buffer.Sink
}
func newArrayWriter(alloc *memory.Allocator, sink buffer.Sink, spec Spec, typ types.Type) (*arrayWriter, error) {
if got, want := spec.Kind(), KindArray; got != want {
return nil, fmt.Errorf("invalid spec kind for array writer: got %s, want %s", got, want)
}
arrSpec := spec.(*SpecArray)
innerWriter, err := array.NewWriter(alloc, arrSpec.Spec, typ)
if err != nil {
return nil, fmt.Errorf("creating inner writer: %w", err)
}
return &arrayWriter{
inner: innerWriter,
sink: sink,
}, nil
}
func (w *arrayWriter) Append(_ context.Context, arr columnar.Array) error {
return w.inner.Append(arr)
}
func (w *arrayWriter) Flush(ctx context.Context) (Layout, error) {
arr, err := w.inner.Flush(ctx, w.sink)
if err != nil {
return nil, fmt.Errorf("flushing array: %w", err)
}
return &Array{Metadata: arr}, nil
}
type arrayReader struct {
layout *Array
source buffer.Source
alloc *memory.Allocator
// Lazily loaded full array. Loaded once on first Filter/Project call.
data columnar.Array
loaded bool
// Window managed by Next.
totalRows int // Total number of rows in data.
offset int // Offset into data of the first row in the current window.
length int // Number of rows in the current window.
}
func newArrayReader(alloc *memory.Allocator, layout Layout, source buffer.Source) (*arrayReader, error) {
if got, want := layout.Kind(), KindArray; got != want {
return nil, fmt.Errorf("invalid layout kind for array reader: got %s, want %s", got, want)
}
arrLayout := layout.(*Array)
return &arrayReader{
layout: arrLayout,
source: source,
alloc: alloc,
totalRows: arrLayout.Metadata.RowCount,
}, nil
}
func (r *arrayReader) Next(_ context.Context, batchSize int) (int, error) {
r.offset += r.length
if r.offset >= r.totalRows {
r.length = 0
return 0, io.EOF
}
r.length = min(batchSize, r.totalRows-r.offset)
return r.length, nil
}
func (r *arrayReader) AppendBuffers(buf []buffer.ID, _ expr.Expression, _ expr.Expression, _ memory.Bitmap) ([]buffer.ID, error) {
return appendArrayBuffers(buf, &r.layout.Metadata), nil
}
// appendArrayBuffers recursively collects all Buffer references from an
// array.Array metadata tree.
func appendArrayBuffers(buf []buffer.ID, arr *array.Array) []buffer.ID {
buf = append(buf, arr.Buffers...)
for i := range arr.Children {
buf = appendArrayBuffers(buf, &arr.Children[i])
}
return buf
}
func (r *arrayReader) Prune(_ context.Context, _ *memory.Allocator, _ expr.Expression, mask memory.Bitmap) (memory.Bitmap, error) {
if mask.Len() != 0 && mask.Len() != r.length {
return memory.Bitmap{}, fmt.Errorf("mask length %d does not match row range %d", mask.Len(), r.length)
}
// TODO(rfratto): Use Stats for pruning (min/max when available).
return mask, nil
}
func (r *arrayReader) Filter(ctx context.Context, alloc *memory.Allocator, e expr.Expression, mask memory.Bitmap) (memory.Bitmap, error) {
if err := r.loadData(ctx); err != nil {
return mask, err
}
windowData := r.data.Slice(r.offset, r.offset+r.length)
result, err := expr.Evaluate(alloc, e, windowData, mask)
if err != nil {
return mask, fmt.Errorf("evaluating filter expression: %w", err)
}
resultMask, err := datumToMask(alloc, result, r.length)
if err != nil {
return mask, fmt.Errorf("converting filter result to mask: %w", err)
}
if mask.Len() > 0 {
// datumToMask returns the raw values bitmap, which has undefined
// bits at unselected positions. Intersect with the input mask so
// only selected-and-matched rows survive.
resultMask = intersectMasks(alloc, resultMask, mask)
}
return resultMask, nil
}
func (r *arrayReader) loadData(ctx context.Context) error {
if r.loaded {
return nil
}
inner, err := array.NewReader(r.alloc, r.layout.Metadata, r.source)
if err != nil {
return fmt.Errorf("creating array reader: %w", err)
}
defer inner.Close()
// We read the full array into memory here, which can be slightly wasteful,
// but it is generally faster as it minimizes decoder reentrance.
data, err := inner.Read(ctx, r.alloc, r.totalRows)
if err != nil && err != io.EOF {
return fmt.Errorf("reading array data: %w", err)
}
r.data = data
r.loaded = true
return nil
}
func (r *arrayReader) Project(ctx context.Context, alloc *memory.Allocator, projection expr.Expression, mask memory.Bitmap) (columnar.Array, error) {
if err := r.loadData(ctx); err != nil {
return nil, err
}
windowData := r.data.Slice(r.offset, r.offset+r.length)
projected, err := expr.Evaluate(alloc, projection, windowData, mask)
if err != nil {
return nil, fmt.Errorf("evaluating projection: %w", err)
}
// expr.Evaluate may return its input unchanged (e.g., Identity
// projection). If the final result still aliases r.data, clone into
// alloc so the caller owns the memory.
final := projected
if final == windowData {
var err error
final, err = columnar.Clone(alloc, final)
if err != nil {
return nil, fmt.Errorf("cloning result: %w", err)
}
}
result, ok := final.(columnar.Array)
if !ok {
return nil, fmt.Errorf("projection produced %T, expected Array", final)
}
return result, nil
}
func (r *arrayReader) Reset() {
r.offset = 0
r.length = 0
}
func (r *arrayReader) Close() error {
r.Reset()
r.loaded = false
r.data = nil
return nil
}