Files
Zach LeslieandGitHub a9f63d073b Replace TenantIndex, BlockMeta and CompactedBlockMeta with proto near-equivalents (#4072)
* Update proto for v1 backend

* Functioning blocklist with new package

* Functioning blocklist with existing package

* Generate proto to existing package

* Add wrapper around google UUID for proto marshaling

* Update blocklist for wrapper type

* Test WriteTenantIndex

* Implement json for uuid

* Almost functional backend tests

* Passing backend tests

* Improve UUID coverage and utility

* TempoDB updates for UUID and struct type changes

* Update method style for uuid.New()

* Update method style for uuid.MustParse()

* Add uuid.From helper

* Update modules for UUID and proto struct types

* Update poller integration test for UUID

* Update tempo-cli for UUID and proto struct type changes

* Lint and clean up

* Fix serverless

* Fix cli

* Fix cli

* Add fixture tests for roundtrip assurance between versions

* Clean up notes from proto definition

* Update backend/test

* Be more specific about unmarshal UUID

* Include event for compacted block

* Use better json tag for compacted meta

* Implement UnmarshalJson on CompcatedBlockMeta for embedded struct

* Test for CompactedMeta marshal/unmarshal

* Drop benchmark index

* Clean commented code

* Extend mocks to capture multiple writes

* Update backend/test fixtures

* Write both proto and json to the backend for all three files in all backends

* Fix lint

* Back UUID with google.UUID to avoid struct

* Relocate pkg/uuid to tempodb/backend

* Re-plumb UUID through tempodb

* Re-plumb UUID through cmd

* Re-plumb UUID through modules

* Re-plumb UUID through integration/

* Drop package spec from tempo.proto

* Improve encoding marshal performance

* Drop block proto and tenantindex json

* Drop additional proto include

* Implement zstd tenant index compression

* Add note about use of json in the dedicatedcolumns

* Fix ingester local block

* Spell

* Revert mock update

* Drop json fallback from tenantindex read

* Replace single-tenant test fixtures

* Update fixture replacement test

* Improve error handling and fix test

* Extend test for tenantindex round-trip

* Small fixes

* Revert "Drop json fallback from tenantindex read"

This reverts commit 834ff5b85d8a8a4d7eb560fbb67ba8f3a5a3959a.

* Add back mocks to capture multiple backend writes

* Add back proto & json writitng/reading for tenantindex

* Change TotalObjects type from int32 -> int64

The original change was int -> int32, which on 64bit machines is a
downgrade that I overlooked.  Raising here to int64 to maintain the
size.

* Store write buffer at object path in mocks to improve readability in tests

* Avoid allocation in interface validation

* Update note to include intern

* Pluralize metas and compacted_metas fields

* Fallback to json only on ErrDoesNotExist

* Lint

* Drop debug

* Revert "Pluralize metas and compacted_metas fields"

This reverts commit 46a8c36e37ac33e1ffc104287d898930e02cdd97.

* Import original tenantindex and test contents for backwards compatibility

* Update changelog
2024-10-02 18:40:59 +00:00

160 lines
4.4 KiB
Go

package vparquet3
import (
"context"
"encoding/binary"
"io"
"go.uber.org/atomic"
"github.com/google/uuid"
"github.com/grafana/tempo/pkg/cache"
"github.com/grafana/tempo/tempodb/backend"
)
type cacheReaderAt interface {
ReadAtWithCache([]byte, int64, cache.Role) (int, error)
}
// BackendReaderAt is used to track backend requests and present a io.ReaderAt interface backed
// by a backend.Reader
type BackendReaderAt struct {
ctx context.Context
r backend.Reader
name string
meta *backend.BlockMeta
bytesRead atomic.Uint64
}
var _ cacheReaderAt = (*BackendReaderAt)(nil)
func NewBackendReaderAt(ctx context.Context, r backend.Reader, name string, meta *backend.BlockMeta) *BackendReaderAt {
return &BackendReaderAt{ctx, r, name, meta, atomic.Uint64{}}
}
func (b *BackendReaderAt) ReadAt(p []byte, off int64) (int, error) {
return b.ReadAtWithCache(p, off, cache.RoleNone)
}
func (b *BackendReaderAt) ReadAtWithCache(p []byte, off int64, role cache.Role) (int, error) {
err := b.r.ReadRange(b.ctx, b.name, (uuid.UUID)(b.meta.BlockID), b.meta.TenantID, uint64(off), p, &backend.CacheInfo{
Role: role,
Meta: b.meta,
})
if err != nil {
return 0, err
}
b.bytesRead.Add(uint64(len(p)))
return len(p), nil
}
func (b *BackendReaderAt) BytesRead() uint64 {
return b.bytesRead.Load()
}
type cachedObjectRecord struct {
length int64
role cache.Role
}
// cachedReaderAt is used to route specific reads to the caching layer. this must be passed directly into
// the parquet.File so thet Set*Section() methods get called.
type cachedReaderAt struct {
r cacheReaderAt
cachedObjects map[int64]cachedObjectRecord // storing offsets and length of objects we want to cache
readerSize int64
footerSize uint32
maxPageSize int
}
var (
_ cacheReaderAt = (*cachedReaderAt)(nil)
_ io.ReaderAt = (*cachedReaderAt)(nil)
)
func newCachedReaderAt(r cacheReaderAt, maxPageSize int, size int64, footerSize uint32) *cachedReaderAt {
return &cachedReaderAt{r, map[int64]cachedObjectRecord{}, size, footerSize, maxPageSize}
}
// called by parquet-go in OpenFile() to set offset and length of footer section
func (r *cachedReaderAt) SetFooterSection(offset, length int64) {
r.cachedObjects[offset] = cachedObjectRecord{length, cache.RoleParquetFooter}
}
// called by parquet-go in OpenFile() to set offset and length of column indexes
func (r *cachedReaderAt) SetColumnIndexSection(offset, length int64) {
r.cachedObjects[offset] = cachedObjectRecord{length, cache.RoleParquetColumnIdx}
}
// called by parquet-go in OpenFile() to set offset and length of offset index section
func (r *cachedReaderAt) SetOffsetIndexSection(offset, length int64) {
r.cachedObjects[offset] = cachedObjectRecord{length, cache.RoleParquetOffsetIdx}
}
func (r *cachedReaderAt) ReadAt(p []byte, off int64) (int, error) {
if len(p) == 4 && off == 0 {
// Magic header
return copy(p, []byte("PAR1")), nil
}
if len(p) == 8 && off == r.readerSize-8 && r.footerSize > 0 /* not present in previous block metas */ {
// Magic footer
binary.LittleEndian.PutUint32(p, r.footerSize)
copy(p[4:8], []byte("PAR1"))
return 8, nil
}
// check if the offset and length is stored as a special object
rec, ok := r.cachedObjects[off]
if ok && rec.length == int64(len(p)) {
return r.r.ReadAtWithCache(p, off, rec.role)
}
if len(p) <= r.maxPageSize {
return r.r.ReadAtWithCache(p, off, cache.RoleParquetPage)
}
return r.r.ReadAtWithCache(p, off, cache.RoleNone)
}
func (r *cachedReaderAt) ReadAtWithCache(p []byte, off int64, role cache.Role) (int, error) {
return r.r.ReadAtWithCache(p, off, role)
}
// walReaderAt is wrapper over io.ReaderAt, and is used to measure the total bytes read when searching walBlock.
type walReaderAt struct {
ctx context.Context
r io.ReaderAt
bytesRead atomic.Uint64
}
var _ io.ReaderAt = (*walReaderAt)(nil)
func newWalReaderAt(ctx context.Context, r io.ReaderAt) *walReaderAt {
return &walReaderAt{ctx, r, atomic.Uint64{}}
}
func (wr *walReaderAt) ReadAt(p []byte, off int64) (int, error) {
if err := wr.ctx.Err(); err != nil {
return 0, err
}
// parquet-go will call ReadAt when reading data from a parquet file.
n, err := wr.r.ReadAt(p, off)
// ReadAt can read less than len(p) bytes in some cases
wr.bytesRead.Add(uint64(n))
if err != nil {
return 0, err
}
return n, nil
}
func (wr *walReaderAt) BytesRead() uint64 {
return wr.bytesRead.Load()
}