mirror of
https://github.com/grafana/loki.git
synced 2026-03-13 09:33:58 +08:00
Originally, the dataobj package was a higher-level API around sections. This design caused it to become a bottleneck: * Implementing any new public behaviour for a section required bubbling it up to the dataobj API for it to be exposed, making it tedious to add new sections or update existing ones. * The `dataobj.Builder` pattern was focused on constructing dataobjs for storing log data, which will cause friction as we build objects around other use cases. This PR builds on top of the foundation laid out by #17704 and #17708, fully inverting the dependency between dataobj and sections: * The `dataobj` package has no knowledge of what sections exist, and can now be used for writing and reading generic sections. Section packages now create higher-level APIs around the abstractions provided by `dataobj`. * Section packages are now public, and callers interact directly with these packages for writing and reading section-specific data. * All logic for a section (encoding, decoding, buffering, reading) is now fully self-contained inside the section package. Previously, the implementation of each section was spread across three packages (`pkg/dataobj/internal/encoding`, `pkg/dataobj/internal/sections/SECTION`, `pkg/dataobj`). * Cutting a section is now a decision made by the caller rather than the section implementation. Previously, the logs section builder would create multiple sections. For the most part, this change is a no-op, with two exceptions: 1. Section cutting is now performed by the caller; however, this shouldn't result in any issues. 2. Removing the high-level `dataobj.Stream` and `dataobj.Record` types will temporarily reduce the allocation gains from #16988. I will address this after this PR is merged.
93 lines
2.9 KiB
Go
93 lines
2.9 KiB
Go
package dataobj
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
|
|
"github.com/grafana/loki/v3/pkg/dataobj/internal/metadata/filemd"
|
|
)
|
|
|
|
type sectionReader struct {
|
|
rr rangeReader // Reader for absolute ranges within the file.
|
|
md *filemd.Metadata
|
|
sec *filemd.SectionInfo
|
|
}
|
|
|
|
func (sr *sectionReader) DataRange(ctx context.Context, offset, length int64) (io.ReadCloser, error) {
|
|
if offset < 0 || length < 0 {
|
|
return nil, fmt.Errorf("parameters must not be negative: offset=%d length=%d", offset, length)
|
|
}
|
|
|
|
// In newer versions of data objects, the offset to read is relative to the
|
|
// beginning of the section. In older versions, it's an absolute offset.
|
|
//
|
|
// We default to the older interpretation and adjust to the correct relative
|
|
// offset if a layout is provided.
|
|
var absoluteOffset = offset
|
|
|
|
if layout := sr.sec.Layout; layout != nil {
|
|
if layout.Data == nil {
|
|
return nil, fmt.Errorf("section has no data")
|
|
} else if layout.Data.Offset > math.MaxInt64 {
|
|
return nil, fmt.Errorf("section data offset is too large")
|
|
} else if layout.Data.Length > math.MaxInt64 {
|
|
return nil, fmt.Errorf("section data length is too large")
|
|
}
|
|
|
|
// Validate bounds within the range of the section.
|
|
var (
|
|
start = offset
|
|
end = offset + length - 1
|
|
)
|
|
if start > int64(layout.Data.Length) || end > int64(layout.Data.Length) {
|
|
return nil, fmt.Errorf("section data is invalid: start=%d end=%d length=%d", start, end, layout.Data.Length)
|
|
}
|
|
|
|
absoluteOffset = int64(layout.Data.Offset) + offset
|
|
}
|
|
|
|
return sr.rr.ReadRange(ctx, absoluteOffset, length)
|
|
}
|
|
|
|
func (sr *sectionReader) Metadata(ctx context.Context) (io.ReadCloser, error) {
|
|
metadataRegion, err := findMetadataRegion(sr.sec)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if metadataRegion == nil {
|
|
return nil, fmt.Errorf("section is missing metadata")
|
|
}
|
|
|
|
return sr.rr.ReadRange(ctx, int64(metadataRegion.Offset), int64(metadataRegion.Length))
|
|
}
|
|
|
|
// findMetadataRegion returns the region where a section's metadata is stored.
|
|
// If section specifies the new [filemd.SectionLayout] field, then the region
|
|
// from tha layout is returned. Otherwise, it returns the deprecated
|
|
// MetadataOffset and MetadataSize fields.
|
|
//
|
|
// findMetadataRegion returns an error if both the layout and metadata fields
|
|
// are set.
|
|
//
|
|
// findMetadtaRegion returns nil for sections without metadata.
|
|
func findMetadataRegion(section *filemd.SectionInfo) (*filemd.Region, error) {
|
|
// Fallbacks to deprecated fields if the layout is not set.
|
|
var (
|
|
deprecatedOffset = section.MetadataOffset //nolint:staticcheck // Ignore deprecation warning
|
|
deprecatedSize = section.MetadataSize //nolint:staticcheck // Ignore deprecation warning
|
|
)
|
|
|
|
if section.Layout != nil {
|
|
if deprecatedOffset != 0 || deprecatedSize != 0 {
|
|
return nil, fmt.Errorf("invalid section: both layout and deprecated metadata fields are set")
|
|
}
|
|
return section.Layout.Metadata, nil
|
|
}
|
|
|
|
return &filemd.Region{
|
|
Offset: deprecatedOffset,
|
|
Length: deprecatedSize,
|
|
}, nil
|
|
}
|