-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathadmin_offsets.go
More file actions
208 lines (177 loc) · 5.77 KB
/
Copy pathadmin_offsets.go
File metadata and controls
208 lines (177 loc) · 5.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package sarama
import (
"errors"
"sync"
)
// ListOffsetsOptions configures how offsets are fetched.
type ListOffsetsOptions struct {
// IsolationLevel selects between ReadUncommitted (default) and ReadCommitted
// when fetching the latest offset. Only honored by brokers running v0.11+.
IsolationLevel IsolationLevel
}
// OffsetResult contains the response for a single topic partition.
type OffsetResult struct {
Offset int64
Timestamp int64
LeaderEpoch int32
Err error
}
// OffsetAndMetadata describes the offset commit for a single partition.
type OffsetAndMetadata struct {
Offset int64
Metadata string
// LeaderEpoch contains the leader epoch of the last consumed record.
// It is used by the broker to fence stale commits after leader changes.
// Use the epoch returned by offset fetch/list APIs, or -1 to omit.
LeaderEpoch int32
}
// AlterConsumerGroupOffsetsOptions configures how offsets are committed.
// It is currently empty and reserved for future Kafka protocol options
type AlterConsumerGroupOffsetsOptions struct{}
// ListOffsets fans out across the partition leaders to fetch offsets in parallel.
// Per-partition results may carry their own Err (e.g. NotLeaderForPartition,
// UnknownTopicOrPartition) when metadata is stale; the caller can refresh
// metadata via the underlying client and retry those partitions if needed. The
// retry loop here only covers transport-level failures.
func (ca *clusterAdmin) ListOffsets(partitions map[string]map[int32]int64, options *ListOffsetsOptions) (map[string]map[int32]*OffsetResult, error) {
type topicPartition struct {
topic string
partition int32
}
type brokerOffsetRequest struct {
request *OffsetRequest
partitions []topicPartition
}
type brokerOffsetResult struct {
result map[topicPartition]*OffsetResult
err error
}
if len(partitions) == 0 {
return nil, ConfigurationError("no partitions provided")
}
if options == nil {
options = &ListOffsetsOptions{}
}
allResults := make(map[string]map[int32]*OffsetResult, len(partitions))
setResult := func(topic string, partition int32, result *OffsetResult) {
if allResults[topic] == nil {
allResults[topic] = make(map[int32]*OffsetResult)
}
allResults[topic][partition] = result
}
requests := make(map[*Broker]*brokerOffsetRequest)
for topic, topicOffsets := range partitions {
for partition, offsetQuery := range topicOffsets {
broker, _, err := ca.client.LeaderAndEpoch(topic, partition)
if err != nil {
setResult(topic, partition, &OffsetResult{Err: err})
continue
}
req := requests[broker]
if req == nil {
req = &brokerOffsetRequest{
request: NewOffsetRequest(ca.conf.Version),
}
req.request.IsolationLevel = options.IsolationLevel
requests[broker] = req
}
req.request.AddBlock(topic, partition, offsetQuery, 1)
req.partitions = append(req.partitions, topicPartition{topic: topic, partition: partition})
}
}
if len(requests) == 0 {
return allResults, nil
}
results := make(chan brokerOffsetResult, len(requests))
var wg sync.WaitGroup
for broker, req := range requests {
wg.Go(func() {
var resp *OffsetResponse
err := ca.retryOnError(isRetriableBrokerError, func() error {
var err error
_ = broker.Open(ca.client.Config())
resp, err = broker.GetAvailableOffsets(req.request)
return err
})
if err != nil {
results <- brokerOffsetResult{err: err}
return
}
broker.handleThrottledResponse(resp)
partitionResults := make(map[topicPartition]*OffsetResult, len(req.partitions))
for _, tp := range req.partitions {
block := resp.GetBlock(tp.topic, tp.partition)
if block == nil {
partitionResults[tp] = &OffsetResult{Err: ErrIncompleteResponse}
continue
}
partitionResults[tp] = &OffsetResult{
Offset: block.Offset,
Timestamp: block.Timestamp,
LeaderEpoch: block.LeaderEpoch,
Err: block.Err,
}
}
results <- brokerOffsetResult{result: partitionResults}
})
}
go func() {
wg.Wait()
close(results)
}()
var errs []error
for res := range results {
if res.err != nil {
errs = append(errs, res.err)
}
for tp, info := range res.result {
setResult(tp.topic, tp.partition, info)
}
}
return allResults, errors.Join(errs...)
}
// AlterConsumerGroupOffsets retries on transport-level errors and on
// per-partition coordinator errors (NOT_COORDINATOR,
// COORDINATOR_NOT_AVAILABLE, EOF). Other per-partition errors
// (e.g. UNKNOWN_TOPIC_OR_PARTITION) are returned to the caller in
// OffsetCommitResponse.Errors without retry.
func (ca *clusterAdmin) AlterConsumerGroupOffsets(group string, offsets map[string]map[int32]OffsetAndMetadata, _ *AlterConsumerGroupOffsetsOptions) (*OffsetCommitResponse, error) {
if len(offsets) == 0 {
return nil, ConfigurationError("no offsets provided")
}
var response *OffsetCommitResponse
request := NewOffsetCommitRequest(ca.conf, group)
var commitTimestamp int64
if request.Version == 1 {
commitTimestamp = ReceiveTime
}
for topic, topicOffsets := range offsets {
for partition, offset := range topicOffsets {
request.AddBlockWithLeaderEpoch(topic, partition, offset.Offset, offset.LeaderEpoch, commitTimestamp, offset.Metadata)
}
}
err := ca.retryOnError(isRetriableGroupCoordinatorError, func() (err error) {
defer func() {
if err != nil && isRetriableGroupCoordinatorError(err) {
_ = ca.client.RefreshCoordinator(group)
}
}()
coordinator, err := ca.client.Coordinator(group)
if err != nil {
return err
}
response, err = coordinator.CommitOffset(request)
if err != nil {
return err
}
for _, topicErrors := range response.Errors {
for _, partErr := range topicErrors {
if isRetriableGroupCoordinatorError(partErr) {
return partErr
}
}
}
return nil
})
return response, err
}