github.com/streamdal/segmentio-kafka-go@v0.4.47-streamdal/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  )