0

I am currently experimenting with Kafka Exactly-Once Semantics in Python with the Confluent Kafka library. I have 3 programs :

  • One sending incremental integers in a topic called INPUT_TOPIC with 2 partitions.
  • The second one implements read-process-write with EOS to copy the messages in an OUTPUT_TOPIC. There is one couple "consumer-producer" per INPUT_TOPIC partition (so 2)
  • The third one reads messages 2 by 2, and expects a difference of one between the value of the 2 messages. If the difference is not one, that could mean that EOS is not functioning as expected and messages are not forwarded to the OUTPUT_TOPIC.

I am still seeing gaps in the data.

Here is a snippet for the incremental producer :

def run():
    raw_ensure_topic(INPUT_TOPIC, num_partitions=INPUT_TOPIC_PARTITIONS)
    p_conf = get_base_kafka_config()
    p_conf['enable.idempotence'] = True
    p_conf['acks'] = 'all'
    p = Producer(p_conf)
    counter = 0
    while True:
        p.produce(INPUT_TOPIC, f"{counter}", f"{counter}", on_delivery=delivery_report)
        counter += 1
        time.sleep(10.0/1000.0)
        p.poll(0)

Here is the code for the read-process-write:

def __run(consumer_group_id, partition_id):
    raw_ensure_topic(OUTPUT_TOPIC, num_partitions=OUTPUT_TOPIC_PARTITIONS)
    consumer_config = get_consumer_kafka_config(autocommit=False, group_id=consumer_group_id, offset_reset='earliest')
    consumer = Consumer(consumer_config)

    consumer.assign([TopicPartition(INPUT_TOPIC, partition_id)])

    producer_config = get_base_kafka_config()
    producer_config['transactional.id'] = 'eos-transaction-demo'
    producer_config['enable.idempotence'] = True
    producer_config['acks'] = 'all'
    producer = Producer(producer_config)
    producer.init_transactions()
    producer.begin_transaction()
    msg_cnt = 0
    while True:
        msg = consumer.poll(timeout=1.0)
        if msg is None:
            continue

        if msg.error():
            raise msg.error()

        msg_cnt += 1

        if random.randint(0, 1000) > 990:
            print("Generating fake failure...")
            producer.abort_transaction()
            producer.begin_transaction()
            continue

        producer.produce(OUTPUT_TOPIC, msg.key(), msg.value(), on_delivery=delivery_report)
        if msg_cnt % 100 == 0:
            print("=== Committing transaction with {} messages at input offset {} ===".format(
                msg_cnt, msg.offset()))
            # Send the consumer's position to transaction to commit
            # them along with the transaction, committing both
            # input and outputs in the same transaction is what provides EOS.
            producer.send_offsets_to_transaction(
                consumer.position(consumer.assignment()),
                consumer.consumer_group_metadata())

            # Commit the transaction
            producer.commit_transaction()

            # Begin new transaction
            producer.begin_transaction()
            msg_cnt = 0

def run():
    for pid in range(0, INPUT_TOPIC_PARTITIONS):
        __run(f"demo-transfer", pid)

And the program that validates the data:

def run():
    consumer_config = get_consumer_kafka_config(autocommit=True, group_id='validator', offset_reset='earliest')
    print(consumer_config)
    consumer = Consumer(consumer_config)

    missing = []
    last_missing_stats_idx = 0

    consumer.subscribe([OUTPUT_TOPIC])
    while True:
        msgs = consumer.consume(2)
        [v1, v2] = [int(msgs[0].value()), int(msgs[1].value())]
        if v1 in missing:
            missing.remove(v1)
        elif v2 in missing:
            missing.remove(v2)
        elif abs(v1 - v2) != 1:
            missing.extend(range(v1+1, v2))
        if v1 % 100 == 0:
            print(f"Reached {v1}")
        if last_missing_stats_idx+1000 < v1:
            last_missing_stats_idx = v1
            print(f"Missing state {missing}")
TheProphet
  • 21
  • 2

0 Answers0