use super::*;
struct MongoDedupStore {
coll: Collection<Document>,
ttl_seconds: u64,
}
#[async_trait]
impl crate::middleware::deduplication::DedupStore for MongoDedupStore {
async fn reserve(&self, key: &[u8], now: u64) -> Result<bool, ConsumerError> {
use crate::middleware::deduplication::{hex_key, PENDING_TTL_SECS};
let id = hex_key(key);
let now_ms = now as i64 * 1000;
let now_date = mongodb::bson::DateTime::from_millis(now_ms);
let pending_date =
mongodb::bson::DateTime::from_millis(now_ms + PENDING_TTL_SECS as i64 * 1000);
let filter = doc! { "_id": &id, "expireAt": { "$lte": now_date } };
let update = doc! { "$set": { "expireAt": pending_date } };
let opts = FindOneAndUpdateOptions::builder()
.upsert(true)
.return_document(ReturnDocument::Before)
.build();
match self
.coll
.find_one_and_update(filter, update)
.with_options(opts)
.await
{
Ok(_) => Ok(false),
Err(e) => {
let is_dup = matches!(&*e.kind, ErrorKind::Write(mongodb::error::WriteFailure::WriteError(w)) if w.code == 11000);
if is_dup {
Ok(true)
} else {
Err(ConsumerError::Connection(anyhow!(
"Deduplication MongoDB reserve failed: {e}"
)))
}
}
}
}
async fn mark_processed(&self, key: &[u8], now: u64) {
use crate::middleware::deduplication::hex_key;
let id = hex_key(key);
let expire_date =
mongodb::bson::DateTime::from_millis((now as i64 + self.ttl_seconds as i64) * 1000);
if let Err(e) = self
.coll
.update_one(
doc! { "_id": &id },
doc! { "$set": { "expireAt": expire_date } },
)
.with_options(UpdateOptions::builder().upsert(true).build())
.await
{
warn!("Failed to mark dedup key processed in MongoDB: {}", e);
}
}
}
pub(crate) async fn build_mongo_dedup_store(
url: &str,
database: &str,
collection: Option<String>,
ttl_seconds: u64,
route_name: &str,
) -> anyhow::Result<Arc<dyn crate::middleware::deduplication::DedupStore>> {
let client = Client::with_uri_str(url)
.await
.with_context(|| format!("Failed to connect deduplication store at '{}'", url))?;
let db = client.database(database);
let coll_name = collection.unwrap_or_else(|| {
format!(
"mqb_dedup_{}",
crate::checkpoint::sanitize_ident(route_name)
)
});
let coll = db.collection::<Document>(&coll_name);
let index = IndexModel::builder()
.keys(doc! { "expireAt": 1 })
.options(
mongodb::options::IndexOptions::builder()
.expire_after(Duration::from_secs(0))
.build(),
)
.build();
if let Err(e) = coll.create_index(index).await {
warn!(
"Failed to create TTL index on dedup collection {}: {}",
coll_name, e
);
}
Ok(Arc::new(MongoDedupStore { coll, ttl_seconds }))
}