Producer

What is Producer

The Producer is a program that continuously generates records and sends them to a target system. To optimize performance, it utilize a buffer to send data in batches. Regardless of whether it’s the producer program or the buffer program that encounters a failure, we must have the capability to restart the program and retry the operation without data loss. This retry process should be executed gracefully, incorporating an exponential backoff strategy. Upon successful completion, we can remove the data from the buffer to save space. This action is referred to as “commit”.

../_images/producer.png ../_images/event-loop.png

Who Implements What

  • send() and new()Plugin/backend developers implement these to integrate with a specific streaming backend (e.g. Kinesis put_records, Kafka produce).

  • put(record)End users call this method to send records. Buffer management, retry, and send() invocation are handled automatically by BaseProducer.

Error Handling

What exponential backoff mean is that, we wait longer and longer between each retry and stop retrying at certain number of failure. The wait internal and max retry count strategy is called a “Schedule”.

When the max retry count is reached, we have two options:

  1. skip: persist the context data of this batch for debug or future retry, and skip to the next batch.

  2. raise: raise error immediately and stop the program.

../_images/error-handling.png

SimpleProducer Example

Below is the sample usage of SimpleProducer, a simple producer that send data to a target file on your local machine in append-only mode. This producer is for demo and for testing purpose.

simple_producer.py
 1# -*- coding: utf-8 -*-
 2
 3import typing as T
 4import time
 5import random
 6import shutil
 7import dataclasses
 8from pathlib import Path
 9
10from unistream.api import (
11    SendError,
12    DataClassRecord,
13    FileBuffer,
14    RetryConfig,
15    SimpleProducer,
16)
17
18
19def rand_value() -> int:
20    return random.randint(1, 100)
21
22
23@dataclasses.dataclass(frozen=True)
24class MyRecord(DataClassRecord):
25    value: int = dataclasses.field(default_factory=rand_value)
26
27
28@dataclasses.dataclass
29class MyProducer(SimpleProducer):
30    def send(self, records: T.List[MyRecord]):
31        if random.randint(1, 100) <= 50:
32            raise SendError("randomly failed due to send error")
33        super().send(records)
34
35
36dir_demo = Path(__file__).absolute().parent.joinpath("simple_producer_demo")
37shutil.rmtree(dir_demo, ignore_errors=True)
38dir_demo.mkdir(exist_ok=True)
39
40path_log = dir_demo / "simple_producer_buffer.log"
41path_client_target = dir_demo / "simple_producer_history.log"
42
43
44def make_producer() -> MyProducer:
45    producer = MyProducer.new(
46        buffer=FileBuffer.new(
47            record_class=MyRecord,
48            path_wal=path_log,
49            max_records=3,
50        ),
51        retry_config=RetryConfig(
52            exp_backoff=[1, 2, 4],
53        ),
54        path_sink=path_client_target,
55    )
56    return producer
57
58
59producer = make_producer()
60
61# --- test 1 ---
62n = 15
63for i in range(1, 1 + n):
64    time.sleep(1)
65    # The producer program can be terminated with a 30% chance.
66    # we create a new producer object to simulate that.
67    if random.randint(1, 100) <= 30:
68        producer = make_producer()
69    producer.put(DataClassRecord(id=str(i)), verbose=True)
70
71# --- test 2 ---
72# producer.retry_config.exp_backoff = [0.1, 0.2, 0.4]
73# n = 1000
74# for i in range(1, 1 + n):
75#     time.sleep(0.001)
76#     producer.put(DataClassRecord(id=str(i)), verbose=True)
simple_producer.py Output
+----- ⏱ 📤 Start 'put record' -------------------------------------------------+
📤
📤 record = {"id": "1", "create_at": "2024-01-07T07:31:41.482432+00:00"}
📤 🚫 we should not emit
📤
+----- ⏰ ✅ 📤 End 'put record', elapsed = 0.00 sec -----------------------------+
+----- ⏱ 📤 Start 'put record' -------------------------------------------------+
📤
📤 record = {"id": "2", "create_at": "2024-01-07T07:31:42.486702+00:00"}
📤 🚫 we should not emit
📤
+----- ⏰ ✅ 📤 End 'put record', elapsed = 0.00 sec -----------------------------+
+----- ⏱ 📤 Start 'put record' -------------------------------------------------+
📤
📤 record = {"id": "3", "create_at": "2024-01-07T07:31:43.487467+00:00"}
📤 📤 send records: ['1', '2', '3']
📤 🔴 failed, error: SendError('randomly failed due to send error')
📤
+----- ⏰ ✅ 📤 End 'put record', elapsed = 0.00 sec -----------------------------+
+----- ⏱ 📤 Start 'put record' -------------------------------------------------+
📤
📤 record = {"id": "4", "create_at": "2024-01-07T07:31:44.493604+00:00"}
📤 📤 send records: ['1', '2', '3']
📤 🟢 succeeded
📤
+----- ⏰ ✅ 📤 End 'put record', elapsed = 0.00 sec -----------------------------+

Vendor-Specific Producers

Note

Vendor-specific producers (AWS CloudWatch Logs, AWS Kinesis Stream, Apache Kafka, etc.) are released as separate plugin packages — for example unistream-aws-cloudwatch, unistream-aws-kinesis, unistream-kafka. Refer to those plugins for usage examples and installation instructions.