77
88 "github.com/go-kit/log"
99 "github.com/go-kit/log/level"
10- "github.com/grafana/dskit/ring"
1110 "github.com/prometheus/client_golang/prometheus"
1211 "github.com/prometheus/client_golang/prometheus/promauto"
1312 "github.com/twmb/franz-go/pkg/kgo"
@@ -16,31 +15,43 @@ import (
1615)
1716
1817type DataObjTeeConfig struct {
19- Enabled bool `yaml:"enabled"`
20- Topic string `yaml:"topic"`
21- MaxBufferedBytes int `yaml:"max_buffered_bytes"`
18+ Enabled bool `yaml:"enabled"`
19+ Topic string `yaml:"topic"`
20+ MaxBufferedBytes int `yaml:"max_buffered_bytes"`
21+ PerPartitionRateBytes int `yaml:"per_partition_rate_bytes"`
2222}
2323
2424func (c * DataObjTeeConfig ) RegisterFlags (f * flag.FlagSet ) {
2525 f .BoolVar (& c .Enabled , "distributor.dataobj-tee.enabled" , false , "Enable data object tee." )
2626 f .StringVar (& c .Topic , "distributor.dataobj-tee.topic" , "" , "Topic for data object tee." )
2727 f .IntVar (& c .MaxBufferedBytes , "distributor.dataobj-tee.max-buffered-bytes" , 100 << 20 , "Maximum number of bytes to buffer." )
28+ f .IntVar (& c .PerPartitionRateBytes , "distributor.dataobj-tee.per-partition-rate-bytes" , 1024 * 1024 , "The per-tenant partition rate (bytes/sec)." )
2829}
2930
3031func (c * DataObjTeeConfig ) Validate () error {
31- if c .Enabled && c .Topic == "" {
32+ if ! c .Enabled {
33+ return nil
34+ }
35+ if c .Topic == "" {
3236 return errors .New ("the topic is required" )
3337 }
38+ if c .MaxBufferedBytes < 0 {
39+ return errors .New ("max buffered bytes cannot be negative" )
40+ }
41+ if c .PerPartitionRateBytes < 0 {
42+ return errors .New ("per partition rate bytes cannot be negative" )
43+ }
3444 return nil
3545}
3646
3747// DataObjTee is a tee that duplicates streams to the data object topic.
3848// It is a temporary solution while we work on segmentation keys.
3949type DataObjTee struct {
40- cfg * DataObjTeeConfig
41- client * kgo.Client
42- ringReader ring.PartitionRingReader
43- logger log.Logger
50+ cfg * DataObjTeeConfig
51+ limitsClient * ingestLimits
52+ kafkaClient * kgo.Client
53+ resolver * SegmentationPartitionResolver
54+ logger log.Logger
4455
4556 // Metrics.
4657 failures prometheus.Counter
@@ -50,16 +61,18 @@ type DataObjTee struct {
5061// NewDataObjTee returns a new DataObjTee.
5162func NewDataObjTee (
5263 cfg * DataObjTeeConfig ,
53- client * kgo.Client ,
54- ringReader ring.PartitionRingReader ,
64+ resolver * SegmentationPartitionResolver ,
65+ limitsClient * ingestLimits ,
66+ kafkaClient * kgo.Client ,
5567 logger log.Logger ,
5668 reg prometheus.Registerer ,
5769) (* DataObjTee , error ) {
5870 return & DataObjTee {
59- cfg : cfg ,
60- client : client ,
61- ringReader : ringReader ,
62- logger : logger ,
71+ cfg : cfg ,
72+ resolver : resolver ,
73+ kafkaClient : kafkaClient ,
74+ limitsClient : limitsClient ,
75+ logger : logger ,
6376 failures : promauto .With (reg ).NewCounter (prometheus.CounterOpts {
6477 Name : "loki_distributor_dataobj_tee_duplicate_stream_failures_total" ,
6578 Help : "Total number of streams that could not be duplicated." ,
@@ -71,18 +84,47 @@ func NewDataObjTee(
7184 }, nil
7285}
7386
87+ // A SegmentedStream is a KeyedStream with a segmentation key.
88+ type SegmentedStream struct {
89+ KeyedStream
90+ SegmentationKey SegmentationKey
91+ }
92+
7493// Duplicate implements the [Tee] interface.
75- func (t * DataObjTee ) Duplicate (_ context.Context , tenant string , streams []KeyedStream ) {
76- for _ , s := range streams {
77- go t .duplicate (tenant , s )
94+ func (t * DataObjTee ) Duplicate (ctx context.Context , tenant string , streams []KeyedStream ) {
95+ segmentationKeyStreams := make ([]SegmentedStream , 0 , len (streams ))
96+ for _ , stream := range streams {
97+ segmentationKey , err := GetSegmentationKey (stream )
98+ if err != nil {
99+ level .Error (t .logger ).Log ("msg" , "failed to get segmentation key" , "err" , err )
100+ t .failures .Inc ()
101+ return
102+ }
103+ segmentationKeyStreams = append (segmentationKeyStreams , SegmentedStream {
104+ KeyedStream : stream ,
105+ SegmentationKey : segmentationKey ,
106+ })
107+ }
108+ rates , err := t .limitsClient .UpdateRates (ctx , tenant , segmentationKeyStreams )
109+ if err != nil {
110+ level .Error (t .logger ).Log ("msg" , "failed to update rates" , "err" , err )
111+ }
112+ // fastRates is a temporary lookup table that lets us find the rate
113+ // for a segmentation key in constant time.
114+ fastRates := make (map [uint64 ]uint64 , len (rates ))
115+ for _ , rate := range rates {
116+ fastRates [rate .StreamHash ] = rate .Rate
117+ }
118+ for _ , s := range segmentationKeyStreams {
119+ go t .duplicate (ctx , tenant , s , fastRates [s .SegmentationKey .Sum64 ()])
78120 }
79121}
80122
81- func (t * DataObjTee ) duplicate (tenant string , stream KeyedStream ) {
123+ func (t * DataObjTee ) duplicate (ctx context. Context , tenant string , stream SegmentedStream , rateBytes uint64 ) {
82124 t .total .Inc ()
83- partition , err := t .ringReader . PartitionRing (). ActivePartitionForKey ( stream .HashKey )
125+ partition , err := t .resolver . Resolve ( ctx , stream .SegmentationKey , rateBytes )
84126 if err != nil {
85- level .Error (t .logger ).Log ("msg" , "failed to get partition" , "err" , err )
127+ level .Error (t .logger ).Log ("msg" , "failed to resolve partition" , "err" , err )
86128 t .failures .Inc ()
87129 return
88130 }
@@ -92,7 +134,7 @@ func (t *DataObjTee) duplicate(tenant string, stream KeyedStream) {
92134 t .failures .Inc ()
93135 return
94136 }
95- results := t .client .ProduceSync (context . TODO () , records ... )
137+ results := t .kafkaClient .ProduceSync (ctx , records ... )
96138 if err := results .FirstErr (); err != nil {
97139 level .Error (t .logger ).Log ("msg" , "failed to produce records" , "err" , err )
98140 t .failures .Inc ()
0 commit comments