Background task that collects events and writes them in batches
(db: Arc<Database>, mut rx: mpsc::UnboundedReceiver<AttackEvent>)
| 24 | |
| 25 | /// Background task that collects events and writes them in batches |
| 26 | async fn write_buffer_task(db: Arc<Database>, mut rx: mpsc::UnboundedReceiver<AttackEvent>) { |
| 27 | const BATCH_SIZE: usize = 100; |
| 28 | const FLUSH_INTERVAL_MS: u64 = 250; |
| 29 | |
| 30 | let mut buffer: Vec<AttackEvent> = Vec::with_capacity(BATCH_SIZE); |
| 31 | let mut flush_interval = tokio::time::interval( |
| 32 | tokio::time::Duration::from_millis(FLUSH_INTERVAL_MS) |
| 33 | ); |
| 34 | |
| 35 | info!("Write buffer started (batch_size={}, flush_interval={}ms)", BATCH_SIZE, FLUSH_INTERVAL_MS); |
| 36 | |
| 37 | loop { |
| 38 | tokio::select! { |
| 39 | // Receive events from handlers |
| 40 | event = rx.recv() => { |
| 41 | match event { |
| 42 | Some(e) => { |
| 43 | buffer.push(e); |
| 44 | // Flush immediately if batch is full |
| 45 | if buffer.len() >= BATCH_SIZE { |
| 46 | flush_batch(&db, &mut buffer).await; |
| 47 | } |
| 48 | } |
| 49 | None => { |
| 50 | // Channel closed, flush remaining and exit |
| 51 | if !buffer.is_empty() { |
| 52 | flush_batch(&db, &mut buffer).await; |
| 53 | } |
| 54 | info!("Write buffer shutting down"); |
| 55 | break; |
| 56 | } |
| 57 | } |
| 58 | } |
| 59 | // Periodic flush for low-traffic periods |
| 60 | _ = flush_interval.tick() => { |
| 61 | if !buffer.is_empty() { |
| 62 | flush_batch(&db, &mut buffer).await; |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | /// Flush buffered events to database in a single transaction |
| 70 | async fn flush_batch(db: &Database, buffer: &mut Vec<AttackEvent>) { |
no outgoing calls
no test coverage detected