mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
* o7 compactor Signed-off-by: Joe Elliott <[email protected]> * wip: o7 v2 Signed-off-by: Joe Elliott <[email protected]> * test cleanup Signed-off-by: Joe Elliott <[email protected]> * remove compactor from jsonnet Signed-off-by: Joe Elliott <[email protected]> * changelog Signed-off-by: Joe Elliott <[email protected]> * gen manifest Signed-off-by: Joe Elliott <[email protected]> * config cleanup Signed-off-by: Joe Elliott <[email protected]> * docs Signed-off-by: Joe Elliott <[email protected]> * jsonnet Signed-off-by: Joe Elliott <[email protected]> * remove encoding Signed-off-by: Joe Elliott <[email protected]> * remove data encoding Signed-off-by: Joe Elliott <[email protected]> * lint and cleanup Signed-off-by: Joe Elliott <[email protected]> * unflake TestWorker\? Signed-off-by: Joe Elliott <[email protected]> --------- Signed-off-by: Joe Elliott <[email protected]>
323 lines
8.0 KiB
Go
323 lines
8.0 KiB
Go
package vparquet4
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"runtime"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/grafana/tempo/pkg/dataquality"
|
|
tempo_io "github.com/grafana/tempo/pkg/io"
|
|
"github.com/grafana/tempo/tempodb/backend"
|
|
"github.com/grafana/tempo/tempodb/encoding/common"
|
|
"github.com/parquet-go/parquet-go"
|
|
)
|
|
|
|
type backendWriter struct {
|
|
ctx context.Context
|
|
w backend.Writer
|
|
name string
|
|
blockID uuid.UUID
|
|
tenantID string
|
|
tracker backend.AppendTracker
|
|
}
|
|
|
|
var _ io.WriteCloser = (*backendWriter)(nil)
|
|
|
|
func (b *backendWriter) Write(p []byte) (n int, err error) {
|
|
b.tracker, err = b.w.Append(b.ctx, b.name, b.blockID, b.tenantID, b.tracker, p)
|
|
return len(p), err
|
|
}
|
|
|
|
func (b *backendWriter) Close() error {
|
|
return b.w.CloseAppend(b.ctx, b.tracker)
|
|
}
|
|
|
|
func CreateBlock(ctx context.Context, cfg *common.BlockConfig, meta *backend.BlockMeta, i common.Iterator, r backend.Reader, to backend.Writer) (*backend.BlockMeta, error) {
|
|
s := newStreamingBlock(ctx, cfg, meta, r, to, tempo_io.NewBufferedWriter)
|
|
|
|
var next func(context.Context) error
|
|
|
|
if ii, ok := i.(*commonIterator); ok {
|
|
next = func(ctx context.Context) error {
|
|
// Use interal iterator and avoid translation to/from proto
|
|
id, row, err := ii.NextRow(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if row == nil {
|
|
return io.EOF
|
|
}
|
|
err = s.AddRaw(id, row, 0, 0) // start and end time of the wal meta are used.
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
completeBlockRowPool.Put(row)
|
|
return nil
|
|
}
|
|
} else {
|
|
// Need to convert from proto->parquet obj
|
|
var (
|
|
buffer = &Trace{}
|
|
connected bool
|
|
resMapping = dedicatedColumnsToColumnMapping(meta.DedicatedColumns, backend.DedicatedColumnScopeResource)
|
|
spanMapping = dedicatedColumnsToColumnMapping(meta.DedicatedColumns, backend.DedicatedColumnScopeSpan)
|
|
)
|
|
next = func(context.Context) error {
|
|
id, tr, err := i.Next(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tr == nil {
|
|
return io.EOF
|
|
}
|
|
|
|
// Copy ID to allow it to escape the iterator.
|
|
id = append([]byte(nil), id...)
|
|
|
|
buffer, connected = traceToParquetWithMapping(id, tr, buffer, resMapping, spanMapping)
|
|
if !connected {
|
|
dataquality.WarnDisconnectedTrace(meta.TenantID, dataquality.PhaseTraceWalToComplete)
|
|
}
|
|
if buffer.RootSpanName == "" {
|
|
dataquality.WarnRootlessTrace(meta.TenantID, dataquality.PhaseTraceWalToComplete)
|
|
}
|
|
|
|
err = s.Add(buffer, 0, 0) // start and end time are set outside
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
for {
|
|
err := next(ctx)
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if s.EstimatedBufferedBytes() > cfg.RowGroupSizeBytes {
|
|
_, err = s.Flush()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
runtime.GC()
|
|
}
|
|
}
|
|
|
|
_, err := s.Complete()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.meta, nil
|
|
}
|
|
|
|
type streamingBlock struct {
|
|
ctx context.Context
|
|
bloom *common.ShardedBloomFilter
|
|
meta *backend.BlockMeta
|
|
bw tempo_io.BufferedWriteFlusher
|
|
pw *parquet.GenericWriter[*Trace]
|
|
w *backendWriter
|
|
r backend.Reader
|
|
to backend.Writer
|
|
index *index
|
|
|
|
withNoCompactFlag bool
|
|
|
|
currentBufferedTraces int
|
|
currentBufferedBytes int
|
|
}
|
|
|
|
func newStreamingBlock(ctx context.Context, cfg *common.BlockConfig, meta *backend.BlockMeta, r backend.Reader, to backend.Writer, createBufferedWriter func(w io.Writer) tempo_io.BufferedWriteFlusher) *streamingBlock {
|
|
newMeta := backend.NewBlockMeta(meta.TenantID, (uuid.UUID)(meta.BlockID), VersionString)
|
|
newMeta.StartTime = meta.StartTime
|
|
newMeta.EndTime = meta.EndTime
|
|
newMeta.ReplicationFactor = meta.ReplicationFactor
|
|
// remove ignored attributes from dedicated columns
|
|
newMeta.DedicatedColumns = filterDedicatedColumns(meta.DedicatedColumns)
|
|
|
|
// TotalObjects is used here an an estimated count for the bloom filter.
|
|
// The real number of objects is tracked below.
|
|
bloom := common.NewBloom(cfg.BloomFP, uint(cfg.BloomShardSizeBytes), uint(meta.TotalObjects))
|
|
|
|
w := &backendWriter{ctx, to, DataFileName, (uuid.UUID)(meta.BlockID), meta.TenantID, nil}
|
|
bw := createBufferedWriter(w)
|
|
pw := parquet.NewGenericWriter[*Trace](bw)
|
|
|
|
return &streamingBlock{
|
|
ctx: ctx,
|
|
meta: newMeta,
|
|
bloom: bloom,
|
|
bw: bw,
|
|
pw: pw,
|
|
w: w,
|
|
r: r,
|
|
to: to,
|
|
index: &index{},
|
|
|
|
withNoCompactFlag: cfg.CreateWithNoCompactFlag,
|
|
}
|
|
}
|
|
|
|
func (b *streamingBlock) Add(tr *Trace, start, end uint32) error {
|
|
_, err := b.pw.Write([]*Trace{tr})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id := tr.TraceID
|
|
|
|
b.index.Add(id)
|
|
b.bloom.Add(id)
|
|
b.meta.ObjectAdded(start, end)
|
|
b.currentBufferedTraces++
|
|
b.currentBufferedBytes += estimateMarshalledSizeFromTrace(tr)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *streamingBlock) AddRaw(id []byte, row parquet.Row, start, end uint32) error {
|
|
_, err := b.pw.WriteRows([]parquet.Row{row})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.index.Add(id)
|
|
b.bloom.Add(id)
|
|
b.meta.ObjectAdded(start, end)
|
|
b.currentBufferedTraces++
|
|
b.currentBufferedBytes += estimateMarshalledSizeFromParquetRow(row)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *streamingBlock) EstimatedBufferedBytes() int {
|
|
return b.currentBufferedBytes
|
|
}
|
|
|
|
func (b *streamingBlock) CurrentBufferedObjects() int {
|
|
return b.currentBufferedTraces
|
|
}
|
|
|
|
func (b *streamingBlock) Flush() (int, error) {
|
|
// Flush row group
|
|
b.index.Flush()
|
|
err := b.pw.Flush()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
n := b.bw.Len()
|
|
b.meta.Size_ += uint64(n)
|
|
b.meta.TotalRecords++
|
|
b.currentBufferedTraces = 0
|
|
b.currentBufferedBytes = 0
|
|
|
|
// Flush to underlying writer
|
|
return n, b.bw.Flush()
|
|
}
|
|
|
|
func (b *streamingBlock) Complete() (int, error) {
|
|
// Flush final row group
|
|
b.index.Flush()
|
|
b.meta.TotalRecords++
|
|
err := b.pw.Flush()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Close parquet file. This writes the footer and metadata.
|
|
err = b.pw.Close()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Now Flush and close out in-memory buffer
|
|
n := b.bw.Len()
|
|
b.meta.Size_ += uint64(n)
|
|
err = b.bw.Flush()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
err = b.bw.Close()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
err = b.w.Close()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Read the footer size out of the parquet footer
|
|
buf := make([]byte, 8)
|
|
err = b.r.ReadRange(b.ctx, DataFileName, (uuid.UUID)(b.meta.BlockID), b.meta.TenantID, b.meta.Size_-8, buf, nil)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("error reading parquet file footer: %w", err)
|
|
}
|
|
if string(buf[4:8]) != "PAR1" {
|
|
return 0, errors.New("failed to confirm magic footer while writing a new parquet block")
|
|
}
|
|
b.meta.FooterSize = binary.LittleEndian.Uint32(buf[0:4])
|
|
|
|
b.meta.BloomShardCount = uint32(b.bloom.GetShardCount())
|
|
|
|
if b.withNoCompactFlag {
|
|
// write nocompact flag first to prevent compaction before completion
|
|
err := b.to.WriteNoCompactFlag(b.ctx, (uuid.UUID)(b.meta.BlockID), b.meta.TenantID)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("unexpected error writing nocompact flag: %w", err)
|
|
}
|
|
}
|
|
|
|
return n, writeBlockMeta(b.ctx, b.to, b.meta, b.bloom, b.index)
|
|
}
|
|
|
|
// estimateMarshalledSizeFromTrace attempts to estimate the size of trace in bytes. This is used to make choose
|
|
// when to cut a row group during block creation.
|
|
// TODO: This function regularly estimates lower values then estimateProtoSize() and the size
|
|
// of the actual proto. It's also quite inefficient. Perhaps just using static values per span or attribute
|
|
// would be a better choice?
|
|
func estimateMarshalledSizeFromTrace(tr *Trace) (size int) {
|
|
size += 7 // 7 trace lvl fields
|
|
|
|
for _, rs := range tr.ResourceSpans {
|
|
size += estimateAttrSize(rs.Resource.Attrs)
|
|
size += 10 // 10 resource span lvl fields
|
|
|
|
for _, ils := range rs.ScopeSpans {
|
|
size += 2 // 2 scope span lvl fields
|
|
|
|
for _, s := range ils.Spans {
|
|
size += 14 // 14 span lvl fields
|
|
size += estimateAttrSize(s.Attrs)
|
|
size += estimateEventsSize(s.Events)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func estimateAttrSize(attrs []Attribute) (size int) {
|
|
return len(attrs) * 7 // 7 attribute lvl fields
|
|
}
|
|
|
|
func estimateEventsSize(events []Event) (size int) {
|
|
for _, e := range events {
|
|
size += 4 // 4 event lvl fields
|
|
size += 4 * len(e.Attrs) // 2 event attribute fields
|
|
}
|
|
return
|
|
}
|