use std::sync::{Arc, Weak};
use parking_lot::Mutex;
use uqa_core::memory::{MemoryError, MemoryReservation, OwnedMap};
use super::{
DocId, FieldName, IndexedFieldMetadata, MemoryDocument, MemoryFieldCounters, MemoryIndexState,
MemoryPosting, PostingKey,
};
use crate::{read_control::StorageReadControl, StorageBackendResult};
#[derive(Debug, Default)]
pub(super) struct RetainedPayload {
bytes: u128,
cached: Mutex<Weak<MemoryReservation>>,
}
impl RetainedPayload {
fn add(&mut self, bytes: u128) {
self.bytes += bytes;
*self.cached.get_mut() = Weak::new();
}
fn remove(&mut self, bytes: u128) {
self.bytes = self
.bytes
.checked_sub(bytes)
.expect("removed payload belongs to the tracked corpus");
*self.cached.get_mut() = Weak::new();
}
pub(super) fn with_retained<T>(
&self,
control: &StorageReadControl,
build: impl FnOnce(Arc<MemoryReservation>) -> StorageBackendResult<T>,
) -> StorageBackendResult<T> {
control.check()?;
let mut cached = self.cached.lock();
control.check()?;
let memory = if let Some(memory) = cached
.upgrade()
.filter(|memory| memory.budget().shares_allowance(control.memory()))
{
memory
} else {
let bytes = usize::try_from(
self.bytes
+ size_of::<MemoryIndexState>() as u128
+ size_of::<MemoryReservation>() as u128,
)
.map_err(|_| MemoryError::SizeOverflow)?;
Arc::new(control.memory().reserve(bytes)?)
};
let result = build(Arc::clone(&memory))?;
control.check()?;
*cached = Arc::downgrade(&memory);
Ok(result)
}
}
pub(super) fn posting_buffers(posting: &MemoryPosting) -> u128 {
posting.occurrences.capacity() as u128 * size_of::<uqa_core::TokenOccurrence>() as u128
}
fn posting_size(posting: &MemoryPosting) -> u128 {
OwnedMap::<DocId, MemoryPosting>::entry_bytes() as u128 + posting_buffers(posting)
}
fn term_payload(key: &PostingKey) -> u128 {
key.0.capacity() as u128 + key.1.allocated_bytes() as u128
}
fn index_key_size(key: &PostingKey) -> u128 {
OwnedMap::<PostingKey, OwnedMap<DocId, MemoryPosting>>::entry_bytes() as u128
+ term_payload(key)
}
fn terms_size(terms: &Vec<PostingKey>) -> u128 {
terms.capacity() as u128 * size_of::<PostingKey>() as u128
+ terms.iter().map(term_payload).sum::<u128>()
}
fn fields_size(fields: &OwnedMap<FieldName, IndexedFieldMetadata>) -> u128 {
fields
.keys()
.map(|field| {
OwnedMap::<FieldName, IndexedFieldMetadata>::entry_bytes() as u128
+ field.capacity() as u128
})
.sum::<u128>()
}
fn document_size(document: &MemoryDocument) -> u128 {
OwnedMap::<DocId, MemoryDocument>::entry_bytes() as u128
+ fields_size(&document.fields)
+ terms_size(&document.terms)
}
fn counter_size(capacity: usize) -> u128 {
OwnedMap::<FieldName, MemoryFieldCounters>::entry_bytes() as u128 + capacity as u128
}
pub(super) fn set_counter(
target: &mut OwnedMap<FieldName, MemoryFieldCounters>,
field: FieldName,
value: Option<MemoryFieldCounters>,
retention: &mut RetainedPayload,
) {
if let Some(value) = value {
if let Some(previous) = target.get_mut(&field) {
*previous = value;
} else {
retention.add(counter_size(field.capacity()));
target.insert(field, value);
}
} else if let Some((field, _)) = target.remove_entry(&field) {
retention.remove(counter_size(field.capacity()));
}
}
impl MemoryIndexState {
pub(super) fn insert_posting(
&mut self,
doc_id: DocId,
key: PostingKey,
posting: MemoryPosting,
) {
self.retention.add(posting_size(&posting));
if let Some(postings) = self.index.get_mut(&key) {
if let Some(previous) = postings.insert(doc_id, posting) {
self.retention.remove(posting_size(&previous));
}
} else {
self.retention.add(index_key_size(&key));
let mut postings = OwnedMap::new();
postings.insert(doc_id, posting);
self.index.insert(key, postings);
}
}
pub(super) fn remove_posting(
&mut self,
doc_id: DocId,
key: &PostingKey,
) -> StorageBackendResult<()> {
let postings = self.index.get_mut(key).ok_or_else(|| {
super::StorageBackendError::Other(format!(
"inverted-index document {doc_id} lost a validated posting before removal"
))
})?;
if let Some(posting) = postings.remove(&doc_id) {
self.retention.remove(posting_size(&posting));
}
if postings.is_empty() {
let (key, _) = self.index.remove_entry(key).expect("validated posting key");
self.retention.remove(index_key_size(&key));
}
Ok(())
}
pub(super) fn remove_document_metadata(&mut self, doc_id: DocId) {
drop(self.take_document_metadata(doc_id));
}
pub(super) fn take_document_metadata(&mut self, doc_id: DocId) -> Option<MemoryDocument> {
let document = self.documents.remove(&doc_id);
if let Some(document) = &document {
self.retention.remove(document_size(document));
}
document
}
pub(super) fn insert_postings(
&mut self,
key: PostingKey,
postings: OwnedMap<DocId, MemoryPosting>,
) {
if let Some(previous) = self.index.get_mut(&key) {
for (id, posting) in &postings {
self.retention.add(posting_size(posting));
if let Some(replaced) = previous.get(id) {
self.retention.remove(posting_size(replaced));
}
}
previous.append(postings);
} else {
self.retention
.add(index_key_size(&key) + postings.values().map(posting_size).sum::<u128>());
self.index.insert(key, postings);
}
}
pub(super) fn insert_document_metadata(
&mut self,
doc_id: DocId,
fields: OwnedMap<FieldName, IndexedFieldMetadata>,
terms: Vec<PostingKey>,
) {
self.remove_document_metadata(doc_id);
let document = MemoryDocument { fields, terms };
self.retention.add(document_size(&document));
self.documents.insert(doc_id, document);
}
fn payload_size(&self) -> u128 {
self.index
.iter()
.map(|(key, postings)| {
index_key_size(key) + postings.values().map(posting_size).sum::<u128>()
})
.sum::<u128>()
+ self.documents.values().map(document_size).sum::<u128>()
+ self
.field_counters
.keys()
.map(|field| counter_size(field.capacity()))
.sum::<u128>()
}
}
impl Clone for MemoryIndexState {
fn clone(&self) -> Self {
let mut state = Self {
index: self.index.clone(),
documents: self.documents.clone(),
field_counters: self.field_counters.clone(),
doc_count: self.doc_count,
retention: RetainedPayload::default(),
};
state.retention.bytes = state.payload_size();
state
}
}