reifydb_store_multi/store/
worker.rs1use std::collections::HashMap;
5
6use reifydb_codec::key::encoded::EncodedKey;
7use reifydb_core::{
8 common::CommitVersion,
9 event::{
10 EventBus,
11 metric::{MultiCommittedEvent, MultiDrop},
12 },
13 interface::store::EntryKind,
14};
15use reifydb_runtime::{
16 actor::{
17 context::Context,
18 mailbox::ActorRef,
19 system::{ActorConfig, ActorSpawner},
20 timers::TimerHandle,
21 traits::{Actor, Directive},
22 },
23 context::clock::{Clock, Instant},
24};
25use reifydb_value::value::duration::Duration;
26use tracing::{Span, debug, error, instrument};
27
28use super::{drop::find_keys_to_drop, pending::PendingDrops};
29use crate::tier::{
30 TierStorage, commit::buffer::MultiCommitBufferTier, persistent::MultiPersistentTier, read::MultiReadBufferTier,
31};
32
33#[derive(Debug, Clone)]
34pub struct DropWorkerConfig {
35 pub batch_size: usize,
36
37 pub flush_interval: Duration,
38}
39
40impl Default for DropWorkerConfig {
41 fn default() -> Self {
42 Self {
43 batch_size: 100,
44 flush_interval: Duration::from_milliseconds(50).unwrap(),
45 }
46 }
47}
48
49use reifydb_core::actors::drop::{DropMessage, DropRequest};
50
51pub struct DropActor {
52 storage: MultiCommitBufferTier,
53 event_bus: EventBus,
54 config: DropWorkerConfig,
55 clock: Clock,
56 persistent: Option<MultiPersistentTier>,
57 read: Option<MultiReadBufferTier>,
58 pending_drops: PendingDrops,
59}
60
61pub struct DropActorState {
62 pending_requests: Vec<DropRequest>,
63
64 last_flush: Instant,
65
66 _timer_handle: Option<TimerHandle>,
67
68 flush_count: u64,
69}
70
71impl DropActor {
72 pub fn new(
73 config: DropWorkerConfig,
74 storage: MultiCommitBufferTier,
75 event_bus: EventBus,
76 clock: Clock,
77 persistent: Option<MultiPersistentTier>,
78 read: Option<MultiReadBufferTier>,
79 pending_drops: PendingDrops,
80 ) -> Self {
81 Self {
82 storage,
83 event_bus,
84 config,
85 clock,
86 persistent,
87 read,
88 pending_drops,
89 }
90 }
91
92 #[allow(clippy::too_many_arguments)]
93 pub fn spawn(
94 spawner: &ActorSpawner,
95 config: DropWorkerConfig,
96 storage: MultiCommitBufferTier,
97 event_bus: EventBus,
98 clock: Clock,
99 persistent: Option<MultiPersistentTier>,
100 read: Option<MultiReadBufferTier>,
101 pending_drops: PendingDrops,
102 ) -> ActorRef<DropMessage> {
103 let actor = Self::new(config, storage, event_bus, clock, persistent, read, pending_drops);
104 spawner.spawn_coordination("drop-worker", actor).actor_ref().clone()
105 }
106
107 fn maybe_flush(&self, state: &mut DropActorState) {
108 if state.pending_requests.len() >= self.config.batch_size {
109 self.flush(state);
110 }
111 }
112
113 fn flush(&self, state: &mut DropActorState) {
114 if state.pending_requests.is_empty() && self.pending_drops.is_empty() {
115 return;
116 }
117
118 if !state.pending_requests.is_empty() {
119 Self::process_batch(&self.storage, &mut state.pending_requests, &self.event_bus);
120 }
121 self.pending_drops.purge(self.persistent.as_ref(), self.read.as_ref());
122 state.last_flush = self.clock.instant();
123
124 state.flush_count += 1;
125 if state.flush_count.is_multiple_of(100) {
126 self.storage.maintenance();
127 }
128 }
129
130 #[instrument(name = "drop::process_batch", level = "debug", skip_all, fields(num_requests = requests.len(), total_dropped))]
131 fn process_batch(storage: &MultiCommitBufferTier, requests: &mut Vec<DropRequest>, event_bus: &EventBus) {
132 let mut batches: HashMap<EntryKind, Vec<(EncodedKey, CommitVersion)>> = HashMap::new();
133
134 let mut drops_with_stats = Vec::new();
135 let mut max_pending_version = CommitVersion(0);
136
137 for request in requests.drain(..) {
138 let version_for_event = request.pending_version.unwrap_or(request.commit_version);
139 if version_for_event > max_pending_version {
140 max_pending_version = version_for_event;
141 }
142
143 match find_keys_to_drop(storage, request.table, request.key.as_ref(), request.pending_version) {
144 Ok(entries_to_drop) => {
145 for entry in entries_to_drop {
146 drops_with_stats.push(MultiDrop {
147 key: request.key.clone(),
148 value_bytes: entry.value_bytes,
149 });
150
151 batches.entry(request.table)
152 .or_default()
153 .push((entry.key, entry.version));
154 }
155 }
156 Err(e) => {
157 error!("Drop actor failed to find keys to drop: {}", e);
158 }
159 }
160 }
161
162 if !batches.is_empty()
163 && let Err(e) = storage.drop(batches)
164 {
165 error!("Drop actor failed to execute drops: {}", e);
166 }
167
168 let total_dropped = drops_with_stats.len();
169 Span::current().record("total_dropped", total_dropped);
170
171 event_bus.emit(MultiCommittedEvent::new(vec![], vec![], drops_with_stats, max_pending_version));
172 }
173}
174
175impl Actor for DropActor {
176 type State = DropActorState;
177 type Message = DropMessage;
178
179 fn init(&self, ctx: &Context<Self::Message>) -> Self::State {
180 debug!("Drop actor started");
181
182 let timer_handle = ctx.schedule_repeat(Duration::from_milliseconds(10).unwrap(), DropMessage::Tick);
183
184 DropActorState {
185 pending_requests: Vec::with_capacity(self.config.batch_size),
186 last_flush: self.clock.instant(),
187 _timer_handle: Some(timer_handle),
188 flush_count: 0,
189 }
190 }
191
192 fn handle(&self, state: &mut Self::State, msg: Self::Message, ctx: &Context<Self::Message>) -> Directive {
193 if ctx.is_cancelled() {
194 self.flush(state);
195 return Directive::Stop;
196 }
197
198 match msg {
199 DropMessage::Request(request) => {
200 state.pending_requests.push(request);
201 self.maybe_flush(state);
202 }
203 DropMessage::Batch(requests) => {
204 state.pending_requests.extend(requests);
205 self.maybe_flush(state);
206 }
207 DropMessage::PurgePending => {
208 if state.last_flush.elapsed() >= self.config.flush_interval.to_std() {
209 self.flush(state);
210 }
211 }
212 DropMessage::Tick => {
213 if (!state.pending_requests.is_empty() || !self.pending_drops.is_empty())
214 && state.last_flush.elapsed() >= self.config.flush_interval.to_std()
215 {
216 self.flush(state);
217 }
218 }
219 DropMessage::Shutdown => {
220 debug!("Drop actor received shutdown signal");
221
222 self.flush(state);
223 return Directive::Stop;
224 }
225 }
226
227 Directive::Continue
228 }
229
230 fn post_stop(&self) {
231 debug!("Drop actor stopped");
232 }
233
234 fn config(&self) -> ActorConfig {
235 ActorConfig::new().mailbox_capacity(4096 * 16)
236 }
237}