Skip to content
This repository has been archived by the owner on Aug 23, 2023. It is now read-only.

Use Confluent and move the kafka-consumers into one consumer struct #879

Closed
wants to merge 24 commits into from

Conversation

replay
Copy link
Contributor

@replay replay commented Mar 22, 2018

Replaces the sarama consumers with confluent ones.
Also gets rid of the duplication between the kafka notifier and kafka input by moving all kafka consumer related stuff into a new struct that's used by both of them.

@replay replay requested review from Dieterbe, jtlisi and woodsaj March 22, 2018 20:22
@replay replay changed the title Use Confluent and move the kafka-consumer stuff into one struct Use Confluent and move the kafka-consumers into one consumer struct Mar 22, 2018
@tehlers320
Copy link

Just some feedback as somebody who tried tsdb-gw via rpm after this similar update. I think FPM should be set to have a depends on librdkafka, there is a no librdkafka in any of the el6/amzn1 repos however there is a nice spec file out there already for users to build it themselves.

How metrictank will probably behave after merge:

tsdb-gw
tsdb-gw: error while loading shared libraries: librdkafka.so.1: cannot open shared object file: No such file or directory

Users can build the dependant rpm here:
https://github.com/edenhill/librdkafka/blob/master/packaging/rpm/librdkafka.spec

Suggested update file: https://github.com/grafana/metrictank/blob/master/scripts/build_packages.sh

I'm not sure how to handle it but this is a breaking change for anybody upgrading rpm on el6/amzn1. el6 is EOL in 2020.

@replay replay changed the title Use Confluent and move the kafka-consumers into one consumer struct WIP: Use Confluent and move the kafka-consumers into one consumer struct Mar 22, 2018
@replay replay changed the title WIP: Use Confluent and move the kafka-consumers into one consumer struct [WIP] Use Confluent and move the kafka-consumers into one consumer struct Mar 22, 2018
defer c.wg.Done()

var ok bool
var offsetPtr *int64
Copy link
Member

Choose a reason for hiding this comment

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

offsetPtr is being updated on every msg, but then nothing is done with it.

Copy link
Contributor Author

@replay replay Mar 23, 2018

Choose a reason for hiding this comment

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

}

c.conf.MessageHandler(e.Value, tp.Partition)
atomic.StoreInt64(offsetPtr, int64(tp.Offset))
Copy link
Member

Choose a reason for hiding this comment

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

looks like this should be

atomic.StoreInt64(c.currentOffsets[tp.Partition], int64(tp.Offset))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i was worried about a message arriving from a partition that we did not expect. in such a case we would then call atomic.StoreInt64(nil, int64(tp.Offset)) if we didn't verify first that this partition id exists in c.currentOffsets

Copy link
Member

Choose a reason for hiding this comment

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

oh, i missed that you are setting offsetPtr to c.currentOffsets[tp.Partition] on L#221

c.consumer.Unassign()
log.Info("kafka-consumer: Revoked partitions: %+v", e)
case confluent.PartitionEOF:
fmt.Printf("%% Reached %v\n", e)
Copy link
Member

Choose a reason for hiding this comment

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

this should either be removed, or use log.Debug. But i think we should just set enable.partition.eof to false in the confluent.ConfigMap to prevent these events from being emitted.

c.partitionLogSize[partition].Set(int(newest))
}

c.partitionOffset[partition].Set(int(offset))
Copy link
Member

Choose a reason for hiding this comment

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

this is already set on L#252

currentOffset = time.Now().Add(-1*offsetDuration).UnixNano() / int64(time.Millisecond)
currentOffset, _, err = c.tryGetOffset(topic, partition, currentOffset, 3, time.Second)
if err != nil {
return err
Copy link
Member

@woodsaj woodsaj Mar 23, 2018

Choose a reason for hiding this comment

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

If the offset is outside of the range what kafka has, it will return an error. If that happens we just want to use oldest, not return an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

actually this comment sounds like in this case it would just use oldest:
https://github.com/confluentinc/confluent-kafka-go/blob/master/kafka/consumer.go#L488-L490

but i'll fall back to oldest in case of any error now

return 0, 0, err
}

var val1, val2 int64
Copy link
Member

Choose a reason for hiding this comment

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

lets rename these to something meaningful.

times, err = c.consumer.OffsetsForTimes(times, c.conf.MetadataTimeout)
if err == nil {
if len(times) == 0 {
err = fmt.Errorf("Got 0 topics returned from broker")
Copy link
Member

Choose a reason for hiding this comment

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

we need to fall back to offsetEnd

Copy link
Contributor Author

Choose a reason for hiding this comment

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

why should this fall back to offsetEnd, while in the above case it should fall back to offsetBeginning? shouldn't they both fall back to offsetBeginning?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

how is that?

for {
if offset == confluent.OffsetBeginning || offset == confluent.OffsetEnd {
beginning, end, err = c.consumer.QueryWatermarkOffsets(topic, partition, c.conf.MetadataTimeout)
if err == nil {
if offset == confluent.OffsetBeginning {
return beginning, nil
} else {
return end, nil
}
}
} else {
times := []confluent.TopicPartition{{Topic: &topic, Partition: partition, Offset: offset}}
times, err = c.consumer.OffsetsForTimes(times, c.conf.MetadataTimeout)
if err != nil || len(times) == 0 {
if err == nil {
err = fmt.Errorf("Failed to get offset %d from kafka, falling back to \"oldest\"", offset)
} else {
err = fmt.Errorf("Failed to get offset %d from kafka, falling back to \"oldest\": %s", offset, err)
}
offset = confluent.OffsetBeginning
} else {
return int64(times[0].Offset), nil
}
}

Copy link
Member

@woodsaj woodsaj Mar 26, 2018

Choose a reason for hiding this comment

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

yep, i meant offsetBeginning.

return c.consumer.Assign(topicPartitions)
}

func (c *Consumer) tryGetOffset(topic string, partition int32, offsetI int64, attempts int, sleep time.Duration) (int64, int64, error) {
Copy link
Member

Choose a reason for hiding this comment

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

why does this return 2 values?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changing that. it made sense in an older version of the code, but now not anymore

continue
}

if tm, ok = metadata.Topics[topic]; !ok || len(tm.Partitions) == 0 {
Copy link
Member

Choose a reason for hiding this comment

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

no need to check if topic is in metadata.Topics, it has already been checked.

Maybe change these 2 checks into

tm, ok := metadata.Topics[topic]
if !ok || tm.Error.Code() == confluent.ErrUnknownTopic {
  log.Warn("kafka: unknown topic %s, %d retries", topic, retry)
  time.Sleep()
  continue
}
if len(tm.Partitions) == 0 {
  log.Warn("kafka: 0 partitions returned for %s, %d retries left, %d backoffMs", topic, retry, backoff)
  sleep()
  continue
}

fs.DurationVar(&offsetCommitInterval, "offset-commit-interval", time.Second*5, "Interval at which offsets should be saved.")
fs.IntVar(&batchNumMessages, "batch-num-messages", 10000, "Maximum number of messages batched in one MessageSet")
Copy link
Member

@woodsaj woodsaj Mar 23, 2018

Choose a reason for hiding this comment

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

ass all of these flag vars are just going to be put into a kafka.ConsumerConf{} why just just a package global consumerConfig
eg

var consumerConfig kafka.ConsumerConf
func init() {
  consumerConfig = kafka.NewConfig()
  fs := flag.NewFlagSet("kafka-cluster", flag.ExitOnError)
  fs.IntVar(&consumerConfig.MetadataTimeout, "consumer-metadata-timeout-ms", 10000, "Maximum time to wait for the broker to send its metadata in ms")
  ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good idea

break EVENTS
}
default:
fmt.Printf("Ignored unexpected event: %s\n", ev)
Copy link
Member

Choose a reason for hiding this comment

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

this should be a log message

@replay replay force-pushed the confluent branch 11 times, most recently from fdd5285 to 7f010ed Compare March 23, 2018 15:58
@Dieterbe
Copy link
Contributor

Afaict this is disabled, or did I miss something?

where is it being disabled? i don't see that in our code and according to https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md it defaults to true

@replay replay force-pushed the confluent branch 2 times, most recently from 0884f9e to 718029b Compare April 25, 2018 21:20
@replay
Copy link
Contributor Author

replay commented Apr 26, 2018

@Dieterbe

Afaict this is disabled, or did I miss something?

where is it being disabled? i don't see that in our code and according to https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md it defaults to true

here: https://github.com/grafana/metrictank/pull/879/files#diff-57960cc1ee87fab707306aab51440a91R102

@replay
Copy link
Contributor Author

replay commented Apr 26, 2018

I ran two types of benchmarks now, comparing the current master with this branch.
In the first one I filled kafka with 72h of data (100 mpo), then i started MT and collected metrics about it while it was replicating the backlog.
In the second one I fed MT with a steady 100k datapoints/second, which is less than the maximum it could do on my test env.

When I fed them with a steady stream of data that was below their maximum, then the cpu & memory usage metrics look pretty similar. When they have to replay the backlog, which maxes them out, the current confluent branch seems to be quite a lot faster, but it also uses much more CPU/Memory. I think I should probably check if there is a way to optimize this memory usage.


replaying backlog:

master:
MT: https://snapshot.raintank.io/dashboard/snapshot/EWZd5a7yzGes0q72HehUelgusv4qZ4lh
Containers: https://snapshot.raintank.io/dashboard/snapshot/MTmhJUOO0LWRq2JhQbLOEN7qWfxGcLaB

confluent:
MT: https://snapshot.raintank.io/dashboard/snapshot/hxthSWe23LqxxacxPy0sauglYT652u9P
Containers: https://snapshot.raintank.io/dashboard/snapshot/296TJsNXArhpt7QzYFmY84mJsnOi0xbZ


steady consuming:

master:
MT: https://snapshot.raintank.io/dashboard/snapshot/9EXHJwmvJVZh2jdC24iUAjH1j5GbM3qQ
Containers: https://snapshot.raintank.io/dashboard/snapshot/pRk6cI9cMXCaJpe9K5uury829hf3Uf5q

confluent:
MT: https://snapshot.raintank.io/dashboard/snapshot/htB6ukVxKBK755rLBF9osWnl0mitvKG8
Containers: https://snapshot.raintank.io/dashboard/snapshot/l3U6jDVTDDREKF5Fa9fVbBlU1c4IPtrn

@Dieterbe Dieterbe added this to the 0.9.0 milestone May 2, 2018
@Dieterbe
Copy link
Contributor

we decided to go forward with sarama instead. see #906

@Dieterbe Dieterbe closed this May 15, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants