#[cfg(not(feature = "std"))]
use crate::nostd_prelude::*;
use alloc::collections::BTreeMap;
use kevy_map::KevyMap;
use super::{AckMode, GroupBatch, StreamData, StreamId};
use crate::StoreError;
use crate::value::SmallBytes;
#[path = "group_types.rs"]
mod types;
pub(super) use types::delivered_again;
pub use types::{ConsumerState, GroupCreateMode, PelEntry, ReadGroupId};
#[derive(Debug, Clone)]
pub struct ConsumerGroup {
pub(crate) last_delivered_id: StreamId,
pub(crate) pel: BTreeMap<StreamId, PelEntry>,
pub(crate) consumers: KevyMap<SmallBytes, Box<ConsumerState>>,
pub(crate) entries_read: Option<u64>,
}
impl ConsumerGroup {
pub fn last_delivered_id(&self) -> StreamId {
self.last_delivered_id
}
pub fn entries_read(&self) -> Option<u64> {
self.entries_read
}
pub fn pending_count(&self) -> usize {
self.pel.len()
}
pub fn pending_entry(&self, id: StreamId) -> Option<&PelEntry> {
self.pel.get(&id)
}
pub fn pending_range(
&self,
range: impl core::ops::RangeBounds<StreamId>,
) -> impl Iterator<Item = (StreamId, &PelEntry)> {
self.pel.range(range).map(|(id, p)| (*id, p))
}
pub fn consumer_count(&self) -> usize {
self.consumers.len()
}
pub fn consumer(&self, name: &[u8]) -> Option<&ConsumerState> {
self.consumers.get(name).map(AsRef::as_ref)
}
pub fn consumers(&self) -> impl Iterator<Item = (&[u8], &ConsumerState)> {
self.consumers.iter().map(|(k, v)| (k.as_slice(), v.as_ref()))
}
}
impl Default for ConsumerGroup {
fn default() -> Self {
Self {
last_delivered_id: StreamId::MIN,
pel: BTreeMap::new(),
consumers: KevyMap::default(),
entries_read: None,
}
}
}
impl StreamData {
pub fn group_create(&mut self, name: &[u8], mode: GroupCreateMode) -> Result<bool, StoreError> {
if self.groups.contains_key(name) {
return Ok(false);
}
let last_delivered_id = match mode {
GroupCreateMode::AtId(id) => id,
GroupCreateMode::AtCurrent => self.last_id,
};
self.groups.insert(
SmallBytes::from_slice(name),
Box::new(ConsumerGroup {
last_delivered_id,
pel: BTreeMap::new(),
consumers: KevyMap::default(),
entries_read: None,
}),
);
Ok(true)
}
pub fn group_destroy(&mut self, name: &[u8]) -> bool {
self.groups.remove(name).is_some()
}
pub fn group_setid(&mut self, name: &[u8], mode: GroupCreateMode) -> bool {
let Some(g) = self.groups.get_mut(name) else {
return false;
};
g.last_delivered_id = match mode {
GroupCreateMode::AtId(id) => id,
GroupCreateMode::AtCurrent => self.last_id,
};
g.entries_read = None;
true
}
pub fn group_consumer_seen(&mut self, group: &[u8], consumer: &[u8], seen_ms: u64) -> bool {
let Some(g) = self.groups.get_mut(group) else {
return false;
};
if let Some(cs) = g.consumers.get_mut(consumer) {
cs.last_seen_ms = seen_ms;
return false;
}
g.consumers.insert(SmallBytes::from_slice(consumer), ConsumerState::new(consumer, seen_ms));
true
}
pub fn group_consumer_active(
&mut self,
group: &[u8],
consumer: &[u8],
active_ms: Option<u64>,
) -> bool {
let Some(g) = self.groups.get_mut(group) else {
return false;
};
if g.consumers.get(consumer).is_none() {
let seen = active_ms.unwrap_or(0);
g.consumers
.insert(SmallBytes::from_slice(consumer), ConsumerState::new(consumer, seen));
}
if let Some(cs) = g.consumers.get_mut(consumer) {
cs.last_active_ms = active_ms;
}
true
}
pub fn group_create_consumer(&mut self, group: &[u8], consumer: &[u8], now_ms: u64) -> bool {
let Some(g) = self.groups.get_mut(group) else {
return false;
};
if g.consumers.contains_key(consumer) {
return false;
}
g.consumers.insert(SmallBytes::from_slice(consumer), ConsumerState::new(consumer, now_ms));
true
}
pub fn group_del_consumer(&mut self, group: &[u8], consumer: &[u8]) -> u64 {
let Some(g) = self.groups.get_mut(group) else {
return 0;
};
let dropped = g.pel.len();
g.pel.retain(|_, p| p.consumer.as_slice() != consumer);
let dropped = dropped - g.pel.len();
g.consumers.remove(consumer);
dropped as u64
}
pub fn readgroup(
&mut self,
group: &[u8],
consumer: &[u8],
last_seen_arg: ReadGroupId,
count: Option<usize>,
ack: AckMode,
now_ms: u64,
) -> Result<GroupBatch, StoreError> {
let Some(g) = self.groups.get_mut(group) else {
return Err(StoreError::NoSuchKey);
};
let consumer_smb = SmallBytes::from_slice(consumer);
seen_consumer(g, &consumer_smb, now_ms);
match last_seen_arg {
ReadGroupId::New => {
let start = g.last_delivered_id.next();
let take: Vec<(StreamId, &[(SmallBytes, SmallBytes)])> = self
.entries
.range(start..=StreamId::MAX)
.map(|(id, fv)| (*id, fv.as_slice()))
.take(count.unwrap_or(usize::MAX))
.collect();
let Some(&(to, _)) = take.last() else {
return Ok(Vec::new());
};
if ack == AckMode::Pending {
record_deliveries(g, &consumer_smb, &take, now_ms);
if let Some(cs) = g.consumers.get_mut(consumer_smb.as_slice()) {
cs.last_active_ms = Some(now_ms);
}
}
let tally = super::lag::Tally::of(
&self.entries,
self.entries_added,
self.max_deleted_id,
self.last_id,
);
let n = take.len() as u64;
g.entries_read = tally.after_read(g.last_delivered_id, g.entries_read, n, to);
g.last_delivered_id = to;
Ok(super::clone_entries(take).into_iter().map(|(id, fv)| (id, Some(fv))).collect())
}
ReadGroupId::ReplayAfter(after) => {
Ok(replay_pel_entries(g, &self.entries, &consumer_smb, after, count, now_ms))
}
}
}
pub fn ack(&mut self, group: &[u8], ids: &[StreamId]) -> u64 {
let Some(g) = self.groups.get_mut(group) else {
return 0;
};
let mut n = 0u64;
for id in ids {
if let Some(p) = g.pel.remove(id) {
if let Some(cs) = g.consumers.get_mut(p.consumer.as_slice()) {
cs.pel_count = cs.pel_count.saturating_sub(1);
}
n += 1;
}
}
n
}
}
fn replay_pel_entries(
g: &mut ConsumerGroup,
entries: &alloc::collections::BTreeMap<StreamId, Vec<(SmallBytes, SmallBytes)>>,
consumer: &SmallBytes,
after: StreamId,
count: Option<usize>,
now_ms: u64,
) -> GroupBatch {
let mut hit: GroupBatch = Vec::new();
if after == StreamId::MAX {
return hit;
}
let limit = count.unwrap_or(usize::MAX);
for (id, p) in g.pel.range_mut(after.next()..=StreamId::MAX) {
if hit.len() >= limit {
break;
}
if p.consumer != *consumer {
continue;
}
let fields = entries.get(id).map(|fv| {
p.delivery_count = delivered_again(p.delivery_count);
p.delivery_time_ms = now_ms;
fv.iter().map(|(f, v)| (f.to_vec(), v.to_vec())).collect()
});
hit.push((*id, fields));
}
hit
}
fn seen_consumer(g: &mut ConsumerGroup, name: &SmallBytes, now_ms: u64) {
ensure_consumer(g, name, now_ms);
if let Some(cs) = g.consumers.get_mut(name.as_slice()) {
cs.last_seen_ms = now_ms;
}
}
pub(super) fn ensure_consumer(g: &mut ConsumerGroup, name: &SmallBytes, now_ms: u64) {
if g.consumers.get(name.as_slice()).is_none() {
g.consumers.insert(name.clone(), ConsumerState::new(name.as_slice(), now_ms));
}
}
fn record_deliveries(
g: &mut ConsumerGroup,
consumer: &SmallBytes,
entries: &[(StreamId, &[(SmallBytes, SmallBytes)])],
now_ms: u64,
) {
let mut new_for_consumer = 0usize;
for (id, _) in entries {
let entry = g.pel.entry(*id).or_insert_with(|| {
new_for_consumer += 1;
PelEntry { consumer: consumer.clone(), delivery_time_ms: now_ms, delivery_count: 0 }
});
if entry.consumer != *consumer {
if let Some(prev) = g.consumers.get_mut(entry.consumer.as_slice()) {
prev.pel_count = prev.pel_count.saturating_sub(1);
}
entry.consumer = consumer.clone();
new_for_consumer += 1;
}
entry.delivery_time_ms = now_ms;
entry.delivery_count = 1;
}
if let Some(cs) = g.consumers.get_mut(consumer.as_slice()) {
cs.pel_count = cs.pel_count.saturating_add(new_for_consumer);
}
}