github.com/segmentio/kafka-go@v0.4.48-0.20240318174348-3f6244eb34fd/protocol/listoffsets/listoffsets.go (about) 1 package listoffsets 2 3 import ( 4 "sort" 5 6 "github.com/segmentio/kafka-go/protocol" 7 ) 8 9 func init() { 10 protocol.Register(&Request{}, &Response{}) 11 } 12 13 type Request struct { 14 ReplicaID int32 `kafka:"min=v1,max=v5"` 15 IsolationLevel int8 `kafka:"min=v2,max=v5"` 16 Topics []RequestTopic `kafka:"min=v1,max=v5"` 17 } 18 19 type RequestTopic struct { 20 Topic string `kafka:"min=v1,max=v5"` 21 Partitions []RequestPartition `kafka:"min=v1,max=v5"` 22 } 23 24 type RequestPartition struct { 25 Partition int32 `kafka:"min=v1,max=v5"` 26 CurrentLeaderEpoch int32 `kafka:"min=v4,max=v5"` 27 Timestamp int64 `kafka:"min=v1,max=v5"` 28 // v0 of the API predates kafka 0.10, and doesn't make much sense to 29 // use so we chose not to support it. It had this extra field to limit 30 // the number of offsets returned, which has been removed in v1. 31 // 32 // MaxNumOffsets int32 `kafka:"min=v0,max=v0"` 33 } 34 35 func (r *Request) ApiKey() protocol.ApiKey { return protocol.ListOffsets } 36 37 func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) { 38 // Expects r to be a request that was returned by Map, will likely panic 39 // or produce the wrong result if that's not the case. 40 partition := r.Topics[0].Partitions[0].Partition 41 topic := r.Topics[0].Topic 42 43 for _, p := range cluster.Topics[topic].Partitions { 44 if p.ID == partition { 45 return cluster.Brokers[p.Leader], nil 46 } 47 } 48 49 return protocol.Broker{ID: -1}, nil 50 } 51 52 func (r *Request) Split(cluster protocol.Cluster) ([]protocol.Message, protocol.Merger, error) { 53 // Because kafka refuses to answer ListOffsets requests containing multiple 54 // entries of unique topic/partition pairs, we submit multiple requests on 55 // the wire and merge their results back. 56 // 57 // ListOffsets requests also need to be sent to partition leaders, to keep 58 // the logic simple we simply split each offset request into a single 59 // message. This may cause a bit more requests to be sent on the wire but 60 // it keeps the code sane, we can still optimize the aggregation mechanism 61 // later if it becomes a problem. 62 // 63 // Really the idea here is to shield applications from having to deal with 64 // the limitation of the kafka server, so they can request any combinations 65 // of topic/partition/offsets. 66 requests := make([]Request, 0, 2*len(r.Topics)) 67 68 for _, t := range r.Topics { 69 for _, p := range t.Partitions { 70 requests = append(requests, Request{ 71 ReplicaID: r.ReplicaID, 72 IsolationLevel: r.IsolationLevel, 73 Topics: []RequestTopic{{ 74 Topic: t.Topic, 75 Partitions: []RequestPartition{{ 76 Partition: p.Partition, 77 CurrentLeaderEpoch: p.CurrentLeaderEpoch, 78 Timestamp: p.Timestamp, 79 }}, 80 }}, 81 }) 82 } 83 } 84 85 messages := make([]protocol.Message, len(requests)) 86 87 for i := range requests { 88 messages[i] = &requests[i] 89 } 90 91 return messages, new(Response), nil 92 } 93 94 type Response struct { 95 ThrottleTimeMs int32 `kafka:"min=v2,max=v5"` 96 Topics []ResponseTopic `kafka:"min=v1,max=v5"` 97 } 98 99 type ResponseTopic struct { 100 Topic string `kafka:"min=v1,max=v5"` 101 Partitions []ResponsePartition `kafka:"min=v1,max=v5"` 102 } 103 104 type ResponsePartition struct { 105 Partition int32 `kafka:"min=v1,max=v5"` 106 ErrorCode int16 `kafka:"min=v1,max=v5"` 107 Timestamp int64 `kafka:"min=v1,max=v5"` 108 Offset int64 `kafka:"min=v1,max=v5"` 109 LeaderEpoch int32 `kafka:"min=v4,max=v5"` 110 } 111 112 func (r *Response) ApiKey() protocol.ApiKey { return protocol.ListOffsets } 113 114 func (r *Response) Merge(requests []protocol.Message, results []interface{}) (protocol.Message, error) { 115 type topicPartition struct { 116 topic string 117 partition int32 118 } 119 120 // Kafka doesn't always return the timestamp in the response, for example 121 // when the request sends -2 (for the first offset) it always returns -1, 122 // probably to indicate that the timestamp is unknown. This means that we 123 // can't correlate the requests and responses based on their timestamps, 124 // the primary key is the topic/partition pair. 125 // 126 // To make the API a bit friendly, we reconstructing an index of topic 127 // partitions to the timestamps that were requested, and override the 128 // timestamp value in the response. 129 timestamps := make([]map[topicPartition]int64, len(requests)) 130 131 for i, m := range requests { 132 req := m.(*Request) 133 ts := make(map[topicPartition]int64, len(req.Topics)) 134 135 for _, t := range req.Topics { 136 for _, p := range t.Partitions { 137 ts[topicPartition{ 138 topic: t.Topic, 139 partition: p.Partition, 140 }] = p.Timestamp 141 } 142 } 143 144 timestamps[i] = ts 145 } 146 147 topics := make(map[string][]ResponsePartition) 148 errors := 0 149 150 for i, res := range results { 151 m, err := protocol.Result(res) 152 if err != nil { 153 for _, t := range requests[i].(*Request).Topics { 154 partitions := topics[t.Topic] 155 156 for _, p := range t.Partitions { 157 partitions = append(partitions, ResponsePartition{ 158 Partition: p.Partition, 159 ErrorCode: -1, // UNKNOWN, can we do better? 160 Timestamp: -1, 161 Offset: -1, 162 LeaderEpoch: -1, 163 }) 164 } 165 166 topics[t.Topic] = partitions 167 } 168 errors++ 169 continue 170 } 171 172 response := m.(*Response) 173 174 if r.ThrottleTimeMs < response.ThrottleTimeMs { 175 r.ThrottleTimeMs = response.ThrottleTimeMs 176 } 177 178 for _, t := range response.Topics { 179 for _, p := range t.Partitions { 180 if timestamp, ok := timestamps[i][topicPartition{ 181 topic: t.Topic, 182 partition: p.Partition, 183 }]; ok { 184 p.Timestamp = timestamp 185 } 186 topics[t.Topic] = append(topics[t.Topic], p) 187 } 188 } 189 190 } 191 192 if errors > 0 && errors == len(results) { 193 _, err := protocol.Result(results[0]) 194 return nil, err 195 } 196 197 r.Topics = make([]ResponseTopic, 0, len(topics)) 198 199 for topicName, partitions := range topics { 200 r.Topics = append(r.Topics, ResponseTopic{ 201 Topic: topicName, 202 Partitions: partitions, 203 }) 204 } 205 206 sort.Slice(r.Topics, func(i, j int) bool { 207 return r.Topics[i].Topic < r.Topics[j].Topic 208 }) 209 210 for _, t := range r.Topics { 211 sort.Slice(t.Partitions, func(i, j int) bool { 212 p1 := &t.Partitions[i] 213 p2 := &t.Partitions[j] 214 215 if p1.Partition != p2.Partition { 216 return p1.Partition < p2.Partition 217 } 218 219 return p1.Offset < p2.Offset 220 }) 221 } 222 223 return r, nil 224 } 225 226 var ( 227 _ protocol.BrokerMessage = (*Request)(nil) 228 _ protocol.Splitter = (*Request)(nil) 229 _ protocol.Merger = (*Response)(nil) 230 )