@@ -2,24 +2,39 @@ package limits
2
2
3
3
import (
4
4
"context"
5
+ "fmt"
6
+ "time"
5
7
6
8
"github.com/go-kit/log"
9
+ "github.com/go-kit/log/level"
7
10
"github.com/twmb/franz-go/pkg/kgo"
11
+
12
+ kafka_partition "github.com/grafana/loki/v3/pkg/kafka/partition"
8
13
)
9
14
10
15
// PartitionLifecycler manages assignment and revocation of partitions.
11
16
type PartitionLifecycler struct {
17
+ cfg Config
12
18
partitionManager * PartitionManager
19
+ offsetManager kafka_partition.OffsetManager
13
20
usage * UsageStore
14
21
logger log.Logger
15
22
}
16
23
17
24
// NewPartitionLifecycler returns a new PartitionLifecycler.
18
- func NewPartitionLifecycler (partitionManager * PartitionManager , usage * UsageStore , logger log.Logger ) * PartitionLifecycler {
25
+ func NewPartitionLifecycler (
26
+ cfg Config ,
27
+ partitionManager * PartitionManager ,
28
+ offsetManager kafka_partition.OffsetManager ,
29
+ usage * UsageStore ,
30
+ logger log.Logger ,
31
+ ) * PartitionLifecycler {
19
32
return & PartitionLifecycler {
33
+ cfg : cfg ,
20
34
partitionManager : partitionManager ,
35
+ offsetManager : offsetManager ,
21
36
usage : usage ,
22
- logger : log . With ( logger , "component" , "limits.PartitionLifecycler" ) ,
37
+ logger : logger ,
23
38
}
24
39
}
25
40
@@ -29,6 +44,16 @@ func (l *PartitionLifecycler) Assign(ctx context.Context, _ *kgo.Client, topics
29
44
// TODO(grobinson): Figure out what to do if this is not the case.
30
45
for _ , partitions := range topics {
31
46
l .partitionManager .Assign (ctx , partitions )
47
+ for _ , partition := range partitions {
48
+ if err := l .checkOffsets (ctx , partition ); err != nil {
49
+ level .Error (l .logger ).Log (
50
+ "msg" , "failed to check offsets, partition is ready" ,
51
+ "partition" , partition ,
52
+ "err" , err ,
53
+ )
54
+ l .partitionManager .SetReady (partition )
55
+ }
56
+ }
32
57
return
33
58
}
34
59
}
@@ -43,3 +68,55 @@ func (l *PartitionLifecycler) Revoke(ctx context.Context, _ *kgo.Client, topics
43
68
return
44
69
}
45
70
}
71
+
72
+ func (l * PartitionLifecycler ) checkOffsets (ctx context.Context , partition int32 ) error {
73
+ logger := log .With (l .logger , "partition" , partition )
74
+ // Get the start offset for the partition. This can be greater than zero
75
+ // if a retention period has deleted old records.
76
+ startOffset , err := l .offsetManager .FetchPartitionOffset (
77
+ ctx , partition , kafka_partition .KafkaStartOffset )
78
+ if err != nil {
79
+ return fmt .Errorf ("failed to get last produced offset: %w" , err )
80
+ }
81
+ // The last produced offset is the next offset after the last produced
82
+ // record. For example, if a partition contains 1 record, then the last
83
+ // produced offset is 1. However, the offset of the last produced record
84
+ // is 0, as offsets start from 0.
85
+ lastProducedOffset , err := l .offsetManager .FetchPartitionOffset (
86
+ ctx , partition , kafka_partition .KafkaEndOffset )
87
+ if err != nil {
88
+ return fmt .Errorf ("failed to get last produced offset: %w" , err )
89
+ }
90
+ // Get the first offset produced within the window. This can be the same
91
+ // offset as the last produced offset if no records have been produced
92
+ // within that time.
93
+ nextOffset , err := l .offsetManager .NextOffset (ctx , partition , time .Now ().Add (- l .cfg .WindowSize ))
94
+ if err != nil {
95
+ return fmt .Errorf ("failed to get next offset: %w" , err )
96
+ }
97
+ level .Debug (logger ).Log (
98
+ "msg" , "fetched offsets" ,
99
+ "start_offset" , startOffset ,
100
+ "last_produced_offset" , lastProducedOffset ,
101
+ "next_offset" , nextOffset ,
102
+ )
103
+ if startOffset >= lastProducedOffset {
104
+ // The partition has no records. This happens when either the
105
+ // partition has never produced a record, or all records that have
106
+ // been produced have been deleted due to the retention period.
107
+ level .Debug (logger ).Log ("msg" , "no records in partition, partition is ready" )
108
+ l .partitionManager .SetReady (partition )
109
+ return nil
110
+ }
111
+ if nextOffset == lastProducedOffset {
112
+ level .Debug (logger ).Log ("msg" , "no records within window size, partition is ready" )
113
+ l .partitionManager .SetReady (partition )
114
+ return nil
115
+ }
116
+ // Since we want to fetch all records up to and including the last
117
+ // produced record, we must fetch all records up to and including the
118
+ // last produced offset - 1.
119
+ level .Debug (logger ).Log ("msg" , "partition is replaying" )
120
+ l .partitionManager .SetReplaying (partition , lastProducedOffset - 1 )
121
+ return nil
122
+ }
0 commit comments