Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kafka module: query each broker all the partitions it is a leader for #16556

Closed
wants to merge 7 commits into from

Conversation

mtojek
Copy link
Contributor

@mtojek mtojek commented Feb 25, 2020

This PR addresses issue reported in #13380 .

Briefly - for the partition metricset:

  1. Get topics metadata
  2. Group partitions per leader broker
  3. Query each broker all the partitions it is a leader for

Meta-issue: #14852

@mtojek mtojek added Metricbeat Metricbeat Team:Services (Deprecated) Label for the former Integrations-Services team labels Feb 25, 2020
@mtojek mtojek requested a review from a team February 25, 2020 13:34
@mtojek mtojek self-assigned this Feb 25, 2020
@mtojek
Copy link
Contributor Author

mtojek commented Feb 26, 2020

jenkins, test this again please

@mtojek
Copy link
Contributor Author

mtojek commented Feb 26, 2020

jenkins, test this again please

@mtojek
Copy link
Contributor Author

mtojek commented Feb 26, 2020

jenkins, test this again please

(Jenkins couldn't trigger jobs again...)

@mtojek mtojek marked this pull request as ready for review February 26, 2020 11:25
@mtojek mtojek changed the title WIP Kafka module: query each broker all the partitions it is a leader for Kafka module: query each broker all the partitions it is a leader for Feb 26, 2020
@mtojek mtojek requested a review from jsoriano February 26, 2020 11:25

for _, topic := range topics {
for _, partition := range topic.Partitions {
broker, err := b.client.Leader(topic.Name, partition.ID)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leader id is available in partition.Leader. We could use this id for the grouping of partitions per broker.

If we continue using b.client.Leader() we have to remember to Close() the returned broker. Maybe we can use b.client.Brokers() to look for the broker per id.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the method b.client.Leader(topic, partition) will always return the most actual leader (there might be a metadata update in background, right?).

The method b.client.Brokers() returns brokers without opening connections to them. To establish a connection, I would need a configuration structure: broker.Open(conf *Config). Leader() handles it on its own.

Regarding Close(), I think you're right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There can still be a problem with this approach, here we are calling Leader for each topic and partition, this may open too many connections to brokers, and we may be leaking connections because we only keep track of one connection per broker in leaderBrokers.

Using the method b.client.Leader(topic, partition) will always return the most actual leader (there might be a metadata update in background, right?).

This is right, but between this moment and the moment we make the offsets request there can still be some metadata change, if we want to solve this for good (not sure if it worths it) we would need to handle leadership errors (second option in #13380).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answered below.

if _, ok := leaderTopicPartition[broker.ID()]; !ok {
leaderTopicPartition[broker.ID()] = map[string]int32{}
}
leaderTopicPartition[broker.ID()][topic.Name] = partition.ID
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to assume that there cannot be two partitions for the same topic in the same broker? (Probably, but I am not sure about that)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be a case in which the number of Kafka brokers is lower than number of Kafka partitions of the same topic, so would rather keep this map as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, we may need to list multiple partitions for the same topic in the same broker.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

metricbeat/module/kafka/broker.go Outdated Show resolved Hide resolved
}

block := resp.GetBlock(topic, partition)
if len(block.Offsets) == 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also check block.Err in any case?

metricbeat/module/kafka/kafka.go Show resolved Hide resolved
continue
} else if newestPartitionOffsets.Err != nil {
msg := fmt.Errorf("failed to query kafka partition (%v:%v) newest offsets: %v",
topic.Name, partition.ID, newestPartitionOffsets.Err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Extract this common logic for oldest and newest offset to a method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted.

@mtojek
Copy link
Contributor Author

mtojek commented Feb 27, 2020

jenkins, test this again please

metricbeat/module/kafka/broker.go Outdated Show resolved Hide resolved

for _, topic := range topics {
for _, partition := range topic.Partitions {
broker, err := b.client.Leader(topic.Name, partition.ID)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There can still be a problem with this approach, here we are calling Leader for each topic and partition, this may open too many connections to brokers, and we may be leaking connections because we only keep track of one connection per broker in leaderBrokers.

Using the method b.client.Leader(topic, partition) will always return the most actual leader (there might be a metadata update in background, right?).

This is right, but between this moment and the moment we make the offsets request there can still be some metadata change, if we want to solve this for good (not sure if it worths it) we would need to handle leadership errors (second option in #13380).

if _, ok := leaderTopicPartition[broker.ID()]; !ok {
leaderTopicPartition[broker.ID()] = map[string]int32{}
}
leaderTopicPartition[broker.ID()][topic.Name] = partition.ID
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, we may need to list multiple partitions for the same topic in the same broker.

metricbeat/module/kafka/kafka.go Show resolved Hide resolved
req.AddBlock(topic, partition, time, 1)
}

resp, err := leaderBrokers[leader].GetAvailableOffsets(req)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit and probably unneeded optimizatiod 😄: we could parallelize requests per leader.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@mtojek
Copy link
Contributor Author

mtojek commented Mar 2, 2020

This is right, but between this moment and the moment we make the offsets request there can still be some metadata change, if we want to solve this for good (not sure if it worths it) we would need to handle leadership errors (second option in #13380).

If you consider this solution as incomplete, vulnerable, please don't hesitate to close this PR. Here, I focused on the first option, but maybe the other one might be better.

@mtojek mtojek requested review from jsoriano, kindsun and a team and removed request for kindsun March 2, 2020 11:15
for _, partition := range topic.Partitions {
if _, ok := leaderTopicPartition[partition.Leader]; !ok {
leaderTopicPartition[partition.Leader] = []topicPartition{}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. This initialization is not needed, append will initialize it if needed.


resp, err := broker.GetAvailableOffsets(req)
if err != nil {
err = fmt.Errorf("get available offsets failed by leader (ID: %d): %v", brokerID, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. We could use errors.Wrap to add context to these errors and others added in this PR.

}

func (b *Broker) queryBrokerForPartitionOffsets(brokerID int32, topicPartitions []topicPartition, time int64) map[string]map[int32]PartitionOffsets {
req := new(sarama.OffsetRequest)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something I am missing here is that in previous implementation we were making a request per replica (the request done in b.PartitionOffset() was also modified with req.SetReplicaID()).
Are we missing the offsets of replicas now? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that this may work (replicaID = -1 means a leader), with I believe this is getting too complex.

@mtojek
Copy link
Contributor Author

mtojek commented Mar 4, 2020

We had an offline discussion about this issue. It's definitely too complex comparing to the gain we may have here. Also, there is still a problem with not fresh metadata, which apparently can be solved only by retries.

Resolving.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Metricbeat Metricbeat Team:Services (Deprecated) Label for the former Integrations-Services team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants