use std::{
collections::{BTreeMap, HashMap, HashSet},
ops::Deref,
};
use bloomfilter::Bloom;
use serde::{Deserialize, Serialize};
use zenoh::{key_expr::OwnedKeyExpr, sample::SampleKind, time::Timestamp, Result as ZResult};
use zenoh_backend_traits::config::ReplicaConfig;
use super::{
classification::{EventLookup, EventRemoval, Interval, IntervalIdx},
configuration::Configuration,
digest::{Digest, Fingerprint},
};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Hash)]
pub(crate) enum Action {
Put,
Delete,
WildcardPut(OwnedKeyExpr),
WildcardDelete(OwnedKeyExpr),
}
impl From<SampleKind> for Action {
fn from(kind: SampleKind) -> Self {
match kind {
SampleKind::Put => Action::Put,
SampleKind::Delete => Action::Delete,
}
}
}
impl From<&Action> for SampleKind {
fn from(action: &Action) -> Self {
match action {
Action::Put | Action::WildcardPut(_) => SampleKind::Put,
Action::Delete | Action::WildcardDelete(_) => SampleKind::Delete,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum ActionKind {
PutOrDelete,
WildcardPut,
WildcardDelete,
}
impl From<&Action> for ActionKind {
fn from(action: &Action) -> Self {
match action {
Action::Put | Action::Delete => Self::PutOrDelete,
Action::WildcardPut(_) => Self::WildcardPut,
Action::WildcardDelete(_) => Self::WildcardDelete,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Hash)]
pub struct EventMetadata {
pub(crate) stripped_key: Option<OwnedKeyExpr>,
pub(crate) timestamp: Timestamp,
pub(crate) timestamp_last_non_wildcard_update: Option<Timestamp>,
pub(crate) action: Action,
}
impl EventMetadata {
pub fn key_expr(&self) -> &Option<OwnedKeyExpr> {
&self.stripped_key
}
pub fn timestamp(&self) -> &Timestamp {
&self.timestamp
}
pub fn action(&self) -> &Action {
&self.action
}
pub fn log_key(&self) -> LogLatestKey {
LogLatestKey {
maybe_stripped_key: self.stripped_key.clone(),
action: (&self.action).into(),
}
}
}
impl From<&Event> for EventMetadata {
fn from(event: &Event) -> Self {
Self {
stripped_key: event.stripped_key.clone(),
timestamp: event.timestamp,
timestamp_last_non_wildcard_update: event.timestamp_last_non_wildcard_update,
action: event.action.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Event {
metadata: EventMetadata,
fingerprint: Fingerprint,
}
impl Deref for Event {
type Target = EventMetadata;
fn deref(&self) -> &Self::Target {
&self.metadata
}
}
impl From<EventMetadata> for Event {
fn from(metadata: EventMetadata) -> Self {
let fingerprint = Event::compute_fingerprint(metadata.key_expr(), metadata.timestamp());
Event {
metadata,
fingerprint,
}
}
}
impl Event {
fn determine_action(key_expr: &Option<OwnedKeyExpr>, action: &Action) -> Action {
match action {
Action::Put => return Action::Put,
Action::Delete => return Action::Delete,
Action::WildcardPut(wildcard_ke) => {
if let Some(ke) = &key_expr {
if ke != wildcard_ke {
return Action::Put;
}
}
}
Action::WildcardDelete(wildcard_ke) => {
if let Some(ke) = &key_expr {
if ke != wildcard_ke {
return Action::Delete;
}
}
}
}
action.clone()
}
pub fn new(key_expr: Option<OwnedKeyExpr>, timestamp: Timestamp, action: &Action) -> Self {
let timestamp_last_non_wildcard_update = match action {
Action::Put | Action::Delete => Some(timestamp),
Action::WildcardPut(_) | Action::WildcardDelete(_) => None,
};
let actual_action = Event::determine_action(&key_expr, action);
Self {
fingerprint: Event::compute_fingerprint(&key_expr, ×tamp),
metadata: EventMetadata {
stripped_key: key_expr,
timestamp,
timestamp_last_non_wildcard_update,
action: actual_action,
},
}
}
fn compute_fingerprint(
maybe_stripped_key: &Option<OwnedKeyExpr>,
timestamp: &Timestamp,
) -> Fingerprint {
let mut hasher = xxhash_rust::xxh3::Xxh3::default();
if let Some(key_expr) = maybe_stripped_key {
hasher.update(key_expr.as_bytes());
}
hasher.update(×tamp.get_time().0.to_le_bytes());
hasher.update(×tamp.get_id().to_le_bytes());
hasher.digest().into()
}
pub fn set_timestamp_and_action(&mut self, timestamp: Timestamp, action: Action) {
if matches!(action, Action::Put | Action::Delete) {
self.metadata.timestamp_last_non_wildcard_update = Some(timestamp);
}
self.metadata.timestamp = timestamp;
self.metadata.action = Event::determine_action(self.key_expr(), &action);
self.fingerprint = Event::compute_fingerprint(self.key_expr(), self.timestamp());
}
pub fn fingerprint(&self) -> Fingerprint {
self.fingerprint
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventInsertion {
New(Event),
ReplacedOlder(Event),
NotInsertedAsOlder,
}
pub struct LogLatest {
pub(crate) configuration: Configuration,
pub(crate) intervals: BTreeMap<IntervalIdx, Interval>,
pub(crate) bloom_filter_event: Bloom<LogLatestKey>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct LogLatestKey {
maybe_stripped_key: Option<OwnedKeyExpr>,
action: ActionKind,
}
impl LogLatest {
#[cfg(debug_assertions)]
pub(crate) fn assert_only_one_event_per_key_expr(&self) -> bool {
let mut hash_set = HashSet::new();
for interval in self.intervals.values() {
if !interval.assert_only_one_event_per_key_expr(&mut hash_set) {
return false;
}
}
true
}
pub fn new(
storage_key_expr: OwnedKeyExpr,
prefix: Option<OwnedKeyExpr>,
replica_config: ReplicaConfig,
) -> Self {
Self {
configuration: Configuration::new(storage_key_expr, prefix, replica_config),
intervals: BTreeMap::default(),
bloom_filter_event: Bloom::new_for_fp_rate(2 << 22, 0.01),
}
}
pub fn configuration(&self) -> &Configuration {
&self.configuration
}
pub fn lookup_newer(&self, event_to_lookup: &EventMetadata) -> Option<&Event> {
if !self.bloom_filter_event.check(&event_to_lookup.log_key()) {
return None;
}
let Ok((event_interval_idx, event_sub_interval_idx)) = self
.configuration
.get_time_classification(&event_to_lookup.timestamp)
else {
tracing::error!(
"Fatal error: failed to compute the time classification of < {event_to_lookup:?} >"
);
return None;
};
for (interval_idx, interval) in self
.intervals
.iter()
.filter(|(&idx, _)| idx >= event_interval_idx)
{
for (_, sub_interval) in interval.sub_intervals().filter(|(&sub_idx, _)| {
if *interval_idx == event_interval_idx {
return sub_idx >= event_sub_interval_idx;
}
true
}) {
match sub_interval.lookup(event_to_lookup) {
EventLookup::NotFound => continue,
EventLookup::NewerOrIdentical(event) => return Some(event),
EventLookup::Older => return None,
}
}
}
None
}
pub(crate) fn remove_event(&mut self, event_to_remove: &EventMetadata) -> Option<Event> {
let Ok((interval_idx, sub_interval_idx)) = self
.configuration
.get_time_classification(&event_to_remove.timestamp)
else {
return None;
};
self.intervals
.get_mut(&interval_idx)
.and_then(|interval| interval.remove_event(&sub_interval_idx, event_to_remove))
}
pub(crate) fn insert_event(&mut self, event: Event) -> EventInsertion {
let event_insertion = match self.remove_older(&(&event).into()) {
EventRemoval::RemovedOlder(old_event) => EventInsertion::ReplacedOlder(old_event),
EventRemoval::KeptNewer => return EventInsertion::NotInsertedAsOlder,
EventRemoval::NotFound => EventInsertion::New(event.clone()),
};
self.insert_event_unchecked(event);
event_insertion
}
pub(crate) fn insert_event_unchecked(&mut self, event: Event) {
let Ok((interval_idx, sub_interval_idx)) = self
.configuration
.get_time_classification(event.timestamp())
else {
tracing::error!(
"Fatal error: timestamp of Event < {:?} > is out of bounds: {}",
event.stripped_key,
event.timestamp
);
return;
};
tracing::trace!("Inserting < {:?} > in Replication Log", event);
self.bloom_filter_event.set(&event.log_key());
self.intervals
.entry(interval_idx)
.or_default()
.insert_unchecked(sub_interval_idx, event);
#[cfg(debug_assertions)]
assert!(self.assert_only_one_event_per_key_expr());
}
pub fn remove_older(&mut self, event_to_remove: &EventMetadata) -> EventRemoval {
if self.bloom_filter_event.check(&event_to_remove.log_key()) {
for interval in self.intervals.values_mut().rev() {
let removal = interval.remove_older(event_to_remove);
if !matches!(removal, EventRemoval::NotFound) {
return removal;
}
}
}
EventRemoval::NotFound
}
pub fn update(&mut self, events: impl Iterator<Item = Event>) {
events.for_each(|event| {
self.insert_event(event);
});
}
pub fn digest(&self) -> ZResult<Digest> {
let last_elapsed_interval = self.configuration.last_elapsed_interval()?;
Ok(self.digest_from(last_elapsed_interval))
}
fn digest_from(&self, hot_era_upper_bound: IntervalIdx) -> Digest {
let hot_era_lower_bound = self.configuration.hot_era_lower_bound(hot_era_upper_bound);
let warm_era_lower_bound = self.configuration.warm_era_lower_bound(hot_era_upper_bound);
let mut warm_era_fingerprints = HashMap::default();
let mut hot_era_fingerprints = HashMap::default();
let mut cold_era_fingerprint = Fingerprint::default();
for (interval_idx, interval) in self
.intervals
.iter()
.filter(|(&idx, _)| idx <= hot_era_upper_bound)
{
if *interval_idx < warm_era_lower_bound {
cold_era_fingerprint ^= interval.fingerprint();
} else if *interval_idx < hot_era_lower_bound {
if interval.fingerprint() != Fingerprint::default() {
warm_era_fingerprints.insert(*interval_idx, interval.fingerprint());
}
} else {
hot_era_fingerprints.insert(*interval_idx, interval.sub_intervals_fingerprints());
}
}
Digest {
configuration_fingerprint: self.configuration.fingerprint(),
cold_era_fingerprint,
warm_era_fingerprints,
hot_era_fingerprints,
}
}
pub(crate) fn remove_events_overridden_by_wildcard_update(
&mut self,
wildcard_key_expr: &OwnedKeyExpr,
wildcard_timestamp: &Timestamp,
wildcard_kind: SampleKind,
) -> ZResult<HashSet<Event>> {
let mut overridden_events = HashSet::new();
for interval in self.intervals.values_mut() {
overridden_events.extend(interval.remove_events_overridden_by_wildcard_update(
self.configuration.prefix(),
wildcard_key_expr,
wildcard_timestamp,
wildcard_kind,
));
}
Ok(overridden_events)
}
}
#[cfg(test)]
#[path = "tests/log.test.rs"]
mod tests;