1use crate::error::{KafkoError, Result};
2use crate::position::RecordPosition;
3use crate::record::Record;
4use crate::topic::Topic;
5use bytes::Bytes;
6use std::sync::Arc;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9#[derive(Clone)]
16pub struct Producer {
17 topic: Arc<Topic>,
18}
19
20impl Producer {
21 pub fn new(topic: Arc<Topic>) -> Self {
26 Self { topic }
27 }
28
29 pub fn partition_count(&self) -> u32 {
31 self.topic.partition_count()
32 }
33
34 #[cfg_attr(feature = "hotpath", hotpath::measure)]
58 pub async fn send(&self, key: Option<Bytes>, value: Bytes) -> Result<RecordPosition> {
59 let partition = self.route(key.as_deref());
60 let record = Record::new(current_timestamp_ms(), key, value);
61 self.append_to(partition, record).await
62 }
63
64 #[cfg_attr(feature = "hotpath", hotpath::measure)]
68 pub async fn send_to(
69 &self,
70 partition: u32,
71 key: Option<Bytes>,
72 value: Bytes,
73 ) -> Result<RecordPosition> {
74 let record = Record::new(current_timestamp_ms(), key, value);
75 self.append_to(partition, record).await
76 }
77
78 #[cfg_attr(feature = "hotpath", hotpath::measure)]
81 pub async fn send_record(&self, record: Record) -> Result<RecordPosition> {
82 let partition = self.route(record.key().map(|k| k.as_ref()));
83 self.append_to(partition, record).await
84 }
85
86 #[cfg_attr(feature = "hotpath", hotpath::measure)]
97 pub async fn send_batch(
98 &self,
99 items: Vec<(Option<Bytes>, Bytes)>,
100 ) -> Result<Vec<RecordPosition>> {
101 if items.is_empty() {
102 return Ok(Vec::new());
103 }
104 let timestamp_ms = current_timestamp_ms();
105 let records = items
106 .into_iter()
107 .map(|(key, value)| Record::new(timestamp_ms, key, value));
108 self.append_batch_routed(records).await
109 }
110
111 #[cfg_attr(feature = "hotpath", hotpath::measure)]
117 pub async fn send_batch_records(&self, records: Vec<Record>) -> Result<Vec<RecordPosition>> {
118 if records.is_empty() {
119 return Ok(Vec::new());
120 }
121 self.append_batch_routed(records.into_iter()).await
122 }
123
124 fn route(&self, key: Option<&[u8]>) -> u32 {
125 match key {
126 Some(k) => self.topic.partition_for_key(k),
127 None => self.topic.next_round_robin(),
128 }
129 }
130
131 async fn append_to(&self, partition: u32, record: Record) -> Result<RecordPosition> {
132 let target = self
133 .topic
134 .partition(partition)
135 .ok_or(KafkoError::InvalidPartitionCount(partition))?;
136 let offset = target.append(record).await?;
137 Ok(RecordPosition::new(partition, offset))
138 }
139
140 async fn append_batch_routed(
141 &self,
142 records: impl Iterator<Item = Record>,
143 ) -> Result<Vec<RecordPosition>> {
144 let n = self.topic.partition_count() as usize;
145 let mut buckets: Vec<Vec<(usize, Record)>> = vec![Vec::new(); n];
148 let mut total = 0usize;
149 for (idx, record) in records.enumerate() {
150 let p = self.route(record.key().map(|k| k.as_ref())) as usize;
151 buckets[p].push((idx, record));
152 total += 1;
153 }
154
155 let mut positions = vec![RecordPosition::new(0, 0); total];
156 for (p, bucket) in buckets.into_iter().enumerate() {
157 if bucket.is_empty() {
158 continue;
159 }
160 let (indices, recs): (Vec<usize>, Vec<Record>) = bucket.into_iter().unzip();
161 let partition = self
162 .topic
163 .partition(p as u32)
164 .expect("bucket index is always a valid partition");
165 let offsets = partition.append_batch(recs).await?;
166 for (input_idx, offset) in indices.into_iter().zip(offsets) {
167 positions[input_idx] = RecordPosition::new(p as u32, offset);
168 }
169 }
170 Ok(positions)
171 }
172}
173
174fn current_timestamp_ms() -> i64 {
175 SystemTime::now()
176 .duration_since(UNIX_EPOCH)
177 .map(|d| d.as_millis() as i64)
178 .unwrap_or(0)
179}