1use std::sync::Arc;
16
17use common_telemetry::warn;
18use dashmap::DashMap;
19use rskafka::client::partition::{Compression, OffsetAt, PartitionClient};
20use rskafka::record::Record;
21use store_api::logstore::provider::KafkaProvider;
22use tokio::sync::mpsc::{self, Receiver, Sender};
23
24use crate::error::{self, Result};
25use crate::kafka::log_store::TopicStat;
26use crate::kafka::worker::{BackgroundProducerWorker, ProduceResultHandle, WorkerRequest};
27use crate::metrics::{
28 METRIC_KAFKA_CLIENT_BYTES_TOTAL, METRIC_KAFKA_CLIENT_PRODUCE_ELAPSED,
29 METRIC_KAFKA_CLIENT_TRAFFIC_TOTAL,
30};
31
32pub type OrderedBatchProducerRef = Arc<OrderedBatchProducer>;
33
34const REQUEST_BATCH_SIZE: usize = 64;
36
37const PRODUCER_CHANNEL_SIZE: usize = REQUEST_BATCH_SIZE * 2;
39
40#[derive(Debug)]
42pub(crate) struct OrderedBatchProducer {
43 pub(crate) sender: Sender<WorkerRequest>,
44}
45
46impl OrderedBatchProducer {
47 pub(crate) fn channel() -> (Sender<WorkerRequest>, Receiver<WorkerRequest>) {
48 mpsc::channel(PRODUCER_CHANNEL_SIZE)
49 }
50
51 pub(crate) fn new(
53 (tx, rx): (Sender<WorkerRequest>, Receiver<WorkerRequest>),
54 provider: Arc<KafkaProvider>,
55 client: Arc<dyn ProducerClient>,
56 compression: Compression,
57 max_batch_bytes: usize,
58 topic_stats: Arc<DashMap<Arc<KafkaProvider>, TopicStat>>,
59 ) -> Self {
60 let mut worker = BackgroundProducerWorker {
61 provider,
62 client,
63 compression,
64 receiver: rx,
65 request_batch_size: REQUEST_BATCH_SIZE,
66 max_batch_bytes,
67 topic_stats,
68 };
69 tokio::spawn(async move { worker.run().await });
70 Self { sender: tx }
71 }
72
73 pub(crate) async fn produce(&self, batch: Vec<Record>) -> Result<ProduceResultHandle> {
81 let (req, handle) = WorkerRequest::new_produce_request(batch);
82 if self.sender.send(req).await.is_err() {
83 warn!("OrderedBatchProducer is already exited");
84 return error::OrderedBatchProducerStoppedSnafu {}.fail();
85 }
86
87 Ok(handle)
88 }
89
90 pub(crate) async fn fetch_latest_offset(&self) -> Result<()> {
93 if self
94 .sender
95 .send(WorkerRequest::FetchLatestOffset)
96 .await
97 .is_err()
98 {
99 warn!("OrderedBatchProducer is already exited");
100 return error::OrderedBatchProducerStoppedSnafu {}.fail();
101 }
102 Ok(())
103 }
104}
105
106pub struct ProduceResult {
107 pub offsets: Vec<i64>,
108 pub encoded_request_size: usize,
109}
110
111#[async_trait::async_trait]
112pub trait ProducerClient: std::fmt::Debug + Send + Sync {
113 async fn produce(
114 &self,
115 records: Vec<Record>,
116 compression: Compression,
117 ) -> rskafka::client::error::Result<ProduceResult>;
118
119 async fn get_offset(&self, at: OffsetAt) -> rskafka::client::error::Result<i64>;
120}
121
122#[async_trait::async_trait]
123impl ProducerClient for PartitionClient {
124 async fn produce(
125 &self,
126 records: Vec<Record>,
127 compression: Compression,
128 ) -> rskafka::client::error::Result<ProduceResult> {
129 let partition = self.partition().to_string();
130 let _timer = METRIC_KAFKA_CLIENT_PRODUCE_ELAPSED
131 .with_label_values(&[self.topic(), &partition])
132 .start_timer();
133
134 let result = self.produce(records, compression).await?;
135
136 METRIC_KAFKA_CLIENT_BYTES_TOTAL
137 .with_label_values(&[self.topic(), &partition])
138 .inc_by(result.encoded_request_size as u64);
139 METRIC_KAFKA_CLIENT_TRAFFIC_TOTAL
140 .with_label_values(&[self.topic(), &partition])
141 .inc();
142
143 Ok(ProduceResult {
144 offsets: result.offsets,
145 encoded_request_size: result.encoded_request_size,
146 })
147 }
148
149 async fn get_offset(&self, at: OffsetAt) -> rskafka::client::error::Result<i64> {
150 self.get_offset(at).await
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use std::sync::{Arc, Mutex};
157 use std::time::Duration;
158
159 use common_base::readable_size::ReadableSize;
160 use common_telemetry::debug;
161 use futures::stream::FuturesUnordered;
162 use futures::{FutureExt, StreamExt};
163 use rskafka::client::error::{Error as ClientError, RequestContext};
164 use rskafka::client::partition::Compression;
165 use rskafka::protocol::error::Error as ProtocolError;
166 use rskafka::record::Record;
167
168 use super::*;
169 use crate::kafka::producer::OrderedBatchProducer;
170 use crate::kafka::test_util::record;
171
172 #[derive(Debug)]
173 struct MockClient {
174 error: Option<ProtocolError>,
175 panic: Option<String>,
176 delay: Duration,
177 batch_sizes: Mutex<Vec<usize>>,
178 }
179
180 #[async_trait::async_trait]
181 impl ProducerClient for MockClient {
182 async fn produce(
183 &self,
184 records: Vec<Record>,
185 _compression: Compression,
186 ) -> rskafka::client::error::Result<ProduceResult> {
187 tokio::time::sleep(self.delay).await;
188
189 if let Some(e) = self.error {
190 return Err(ClientError::ServerError {
191 protocol_error: e,
192 error_message: None,
193 request: RequestContext::Partition("foo".into(), 1),
194 response: None,
195 is_virtual: false,
196 });
197 }
198
199 if let Some(p) = self.panic.as_ref() {
200 panic!("{}", p);
201 }
202
203 let mut batch_sizes = self.batch_sizes.lock().unwrap();
204 let offset_base = batch_sizes.iter().sum::<usize>();
205 let offsets = (0..records.len())
206 .map(|x| (x + offset_base) as i64)
207 .collect();
208 batch_sizes.push(records.len());
209 debug!("Return offsets: {offsets:?}");
210 Ok(ProduceResult {
211 offsets,
212 encoded_request_size: 0,
213 })
214 }
215
216 async fn get_offset(&self, _at: OffsetAt) -> rskafka::client::error::Result<i64> {
217 todo!()
218 }
219 }
220
221 #[tokio::test]
222 async fn test_producer() {
223 common_telemetry::init_default_ut_logging();
224 let record = record();
225 let delay = Duration::from_secs(0);
226 let client = Arc::new(MockClient {
227 error: None,
228 panic: None,
229 delay,
230 batch_sizes: Default::default(),
231 });
232 let provider = Arc::new(KafkaProvider::new(String::new()));
233 let producer = OrderedBatchProducer::new(
234 OrderedBatchProducer::channel(),
235 provider,
236 client.clone(),
237 Compression::NoCompression,
238 ReadableSize((record.approximate_size() * 2) as u64).as_bytes() as usize,
239 Arc::new(DashMap::new()),
240 );
241
242 let handle = producer
244 .produce(vec![record.clone(), record.clone(), record.clone()])
245 .await
246 .unwrap();
247 assert_eq!(handle.wait().await.unwrap(), 2);
248 assert_eq!(client.batch_sizes.lock().unwrap().as_slice(), &[2, 1]);
249
250 let handle = producer
252 .produce(vec![record.clone(), record.clone()])
253 .await
254 .unwrap();
255 assert_eq!(handle.wait().await.unwrap(), 4);
256 assert_eq!(client.batch_sizes.lock().unwrap().as_slice(), &[2, 1, 2]);
257
258 let handle = producer.produce(vec![record.clone()]).await.unwrap();
260 assert_eq!(handle.wait().await.unwrap(), 5);
261 assert_eq!(client.batch_sizes.lock().unwrap().as_slice(), &[2, 1, 2, 1]);
262 }
263
264 #[tokio::test]
265 async fn test_producer_client_error() {
266 let record = record();
267 let client = Arc::new(MockClient {
268 error: Some(ProtocolError::NetworkException),
269 panic: None,
270 delay: Duration::from_millis(1),
271 batch_sizes: Default::default(),
272 });
273 let provider = Arc::new(KafkaProvider::new(String::new()));
274 let producer = OrderedBatchProducer::new(
275 OrderedBatchProducer::channel(),
276 provider,
277 client.clone(),
278 Compression::NoCompression,
279 ReadableSize((record.approximate_size() * 2) as u64).as_bytes() as usize,
280 Arc::new(DashMap::new()),
281 );
282
283 let mut futures = FuturesUnordered::new();
284 futures.push(
285 producer
286 .produce(vec![record.clone(), record.clone(), record.clone()])
287 .await
288 .unwrap()
289 .wait(),
290 );
291 futures.push(
292 producer
293 .produce(vec![record.clone(), record.clone()])
294 .await
295 .unwrap()
296 .wait(),
297 );
298 futures.push(producer.produce(vec![record.clone()]).await.unwrap().wait());
299
300 futures.next().await.unwrap().unwrap_err();
301 futures.next().await.unwrap().unwrap_err();
302 futures.next().await.unwrap().unwrap_err();
303 }
304
305 #[tokio::test]
306 async fn test_producer_cancel() {
307 let record = record();
308 let client = Arc::new(MockClient {
309 error: None,
310 panic: None,
311 delay: Duration::from_millis(1),
312 batch_sizes: Default::default(),
313 });
314
315 let provider = Arc::new(KafkaProvider::new(String::new()));
316 let producer = OrderedBatchProducer::new(
317 OrderedBatchProducer::channel(),
318 provider,
319 client.clone(),
320 Compression::NoCompression,
321 ReadableSize((record.approximate_size() * 2) as u64).as_bytes() as usize,
322 Arc::new(DashMap::new()),
323 );
324
325 let a = producer
326 .produce(vec![record.clone(), record.clone(), record.clone()])
327 .await
328 .unwrap()
329 .wait()
330 .fuse();
331
332 let b = producer.produce(vec![record]).await.unwrap().wait().fuse();
333
334 let mut b = Box::pin(b);
335
336 {
337 let mut a = Box::pin(a);
339
340 futures::select_biased! {
343 _ = &mut a => panic!("a should not have flushed"),
344 _ = &mut b => panic!("b should not have flushed"),
345 _ = tokio::time::sleep(Duration::from_millis(1)).fuse() => {},
346 }
347 }
348
349 tokio::time::timeout(Duration::from_secs(1), b)
351 .await
352 .unwrap()
353 .unwrap();
354
355 assert_eq!(
356 client
357 .batch_sizes
358 .lock()
359 .unwrap()
360 .as_slice()
361 .iter()
362 .sum::<usize>(),
363 4
364 );
365 }
366}