LruDidDocumentStorage

Struct LruDidDocumentStorage 

Source
pub struct LruDidDocumentStorage { /* private fields */ }
Expand description

An LRU-based implementation of DidDocumentStorage that maintains a fixed-size cache of DID documents.

This storage implementation uses an LRU (Least Recently Used) cache to store DID documents in memory with automatic eviction of the least recently accessed entries when the cache reaches its capacity. This is ideal for scenarios where you want to cache frequently accessed DID documents while keeping memory usage bounded.

§Thread Safety

This implementation is thread-safe through the use of Arc<Mutex<LruCache<String, Document>>>. All operations are protected by a mutex, ensuring safe concurrent access from multiple threads or async tasks.

§Cache Behavior

  • Get operations: Move accessed entries to the front of the LRU order
  • Store operations: Add new entries at the front, evicting the least recently used if at capacity
  • Delete operations: Remove entries from the cache entirely
  • Capacity management: Automatically evicts least recently used entries when capacity is exceeded

§Use Cases

This implementation is particularly suitable for:

  • Caching frequently accessed DID documents from PLC/web resolution
  • Scenarios with bounded memory requirements
  • Applications where some document lookup misses are acceptable
  • High-performance applications requiring in-memory access
  • Identity resolution caching layers

§Limitations

  • Persistence: Data is lost when the application restarts
  • Capacity: Limited to the configured cache size
  • Cache misses: Older entries may be evicted and need to be re-resolved
  • Memory usage: All cached data is kept in memory
  • Document size: Large documents consume more memory per entry

§Examples

use atproto_identity::storage_lru::LruDidDocumentStorage;
use atproto_identity::storage::DidDocumentStorage;
use atproto_identity::model::Document;
use std::num::NonZeroUsize;
use std::collections::HashMap;

// Create an LRU cache with capacity for 1000 documents
let storage = LruDidDocumentStorage::new(NonZeroUsize::new(1000).unwrap());

// Create a sample document
let document = Document {
    context: vec![],
    id: "did:plc:bv6ggog3tya2z3vxsub7hnal".to_string(),
    also_known_as: vec!["at://alice.bsky.social".to_string()],
    service: vec![], // simplified for example
    verification_method: vec![], // simplified for example
    extra: HashMap::new(),
};

// Store the document
storage.store_document(document.clone()).await?;

// Retrieve the document
let retrieved = storage.get_document_by_did("did:plc:bv6ggog3tya2z3vxsub7hnal").await?;
assert_eq!(retrieved.as_ref().map(|d| &d.id), Some(&document.id));

// Delete the document
storage.delete_document_by_did("did:plc:bv6ggog3tya2z3vxsub7hnal").await?;
let retrieved = storage.get_document_by_did("did:plc:bv6ggog3tya2z3vxsub7hnal").await?;
assert_eq!(retrieved, None);

§Capacity Planning

When choosing the cache capacity, consider:

  • Expected number of unique DIDs: Size cache to hold frequently accessed documents
  • Memory constraints: Each entry uses approximately (document size + DID length + overhead) bytes
  • Document complexity: Documents with many services/keys use more memory
  • Access patterns: Higher capacity reduces cache misses for varied access patterns
  • Performance requirements: Larger caches may have slightly higher lookup times
use atproto_identity::storage_lru::LruDidDocumentStorage;
use std::num::NonZeroUsize;

// Small cache for testing or low-memory environments
let small_cache = LruDidDocumentStorage::new(NonZeroUsize::new(100).unwrap());

// Medium cache for typical applications
let medium_cache = LruDidDocumentStorage::new(NonZeroUsize::new(10_000).unwrap());

// Large cache for high-traffic applications
let large_cache = LruDidDocumentStorage::new(NonZeroUsize::new(100_000).unwrap());

Implementations§

Source§

impl LruDidDocumentStorage

Source

pub fn new(capacity: NonZeroUsize) -> Self

Creates a new LruDidDocumentStorage with the specified capacity.

The capacity determines the maximum number of DID documents that can be stored in the cache. When the cache reaches this capacity, the least recently used entries will be automatically evicted to make room for new entries.

§Arguments
  • capacity - The maximum number of DID documents to store. Must be greater than 0.
§Examples
use atproto_identity::storage_lru::LruDidDocumentStorage;
use std::num::NonZeroUsize;

// Create a cache that can hold up to 5000 DID documents
let storage = LruDidDocumentStorage::new(NonZeroUsize::new(5000).unwrap());
§Performance Considerations
  • Larger capacities provide better cache hit rates but use more memory
  • The underlying LRU implementation has O(1) access time for all operations
  • Memory usage is approximately: capacity * (average_document_size + DID_size + overhead)
  • Document size varies based on number of services, verification methods, and aliases
Source

pub fn len(&self) -> usize

Returns the current number of entries in the cache.

This method provides visibility into cache usage for monitoring and debugging purposes. The count represents the current number of DID documents stored in the cache.

§Returns

The number of entries currently stored in the cache.

§Examples
use atproto_identity::storage_lru::LruDidDocumentStorage;
use atproto_identity::storage::DidDocumentStorage;
use atproto_identity::model::Document;
use std::num::NonZeroUsize;
use std::collections::HashMap;

let storage = LruDidDocumentStorage::new(NonZeroUsize::new(100).unwrap());
assert_eq!(storage.len(), 0);

let doc1 = Document {
    context: vec![],
    id: "did:plc:example1".to_string(),
    also_known_as: vec![],
    service: vec![],
    verification_method: vec![],
    extra: HashMap::new(),
};
storage.store_document(doc1).await?;
assert_eq!(storage.len(), 1);

let doc2 = Document {
    context: vec![],
    id: "did:plc:example2".to_string(),
    also_known_as: vec![],
    service: vec![],
    verification_method: vec![],
    extra: HashMap::new(),
};
storage.store_document(doc2).await?;
assert_eq!(storage.len(), 2);
Source

pub fn is_empty(&self) -> bool

Returns whether the cache is empty.

§Returns

true if the cache contains no entries, false otherwise.

§Examples
use atproto_identity::storage_lru::LruDidDocumentStorage;
use std::num::NonZeroUsize;

let storage = LruDidDocumentStorage::new(NonZeroUsize::new(100).unwrap());
assert!(storage.is_empty());
Source

pub fn capacity(&self) -> NonZeroUsize

Returns the maximum capacity of the cache.

This returns the capacity that was set when the cache was created and represents the maximum number of DID documents that can be stored before eviction occurs.

§Returns

The maximum capacity of the cache.

§Examples
use atproto_identity::storage_lru::LruDidDocumentStorage;
use std::num::NonZeroUsize;

let capacity = NonZeroUsize::new(500).unwrap();
let storage = LruDidDocumentStorage::new(capacity);
assert_eq!(storage.capacity().get(), 500);
Source

pub fn clear(&self)

Clears all entries from the cache.

This method removes all DID documents from the cache, effectively resetting it to an empty state. This can be useful for testing or when you need to invalidate all cached data.

§Examples
use atproto_identity::storage_lru::LruDidDocumentStorage;
use atproto_identity::storage::DidDocumentStorage;
use atproto_identity::model::Document;
use std::num::NonZeroUsize;
use std::collections::HashMap;

let storage = LruDidDocumentStorage::new(NonZeroUsize::new(100).unwrap());
let document = Document {
    context: vec![],
    id: "did:plc:example".to_string(),
    also_known_as: vec![],
    service: vec![],
    verification_method: vec![],
    extra: HashMap::new(),
};
storage.store_document(document).await?;
assert_eq!(storage.len(), 1);

storage.clear();
assert_eq!(storage.len(), 0);
assert!(storage.is_empty());

Trait Implementations§

Source§

impl Clone for LruDidDocumentStorage

Source§

fn clone(&self) -> LruDidDocumentStorage

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl DidDocumentStorage for LruDidDocumentStorage

Source§

fn get_document_by_did<'life0, 'life1, 'async_trait>( &'life0 self, did: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<Document>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Retrieves a DID document associated with the given DID from the LRU cache.

This method looks up the complete DID document that is currently cached for the provided DID. If the DID is found in the cache, the entry is moved to the front of the LRU order (marking it as recently used) and the document is returned.

§Arguments
  • did - The DID to look up in the cache
§Returns
  • Ok(Some(document)) - If the DID is found in the cache
  • Ok(None) - If the DID is not found in the cache
  • Err(error) - If an error occurs (primarily mutex poisoning, which is very rare)
§Cache Behavior

When a document is successfully retrieved, it’s marked as recently used in the LRU order, making it less likely to be evicted in future operations.

§Examples
use atproto_identity::storage_lru::LruDidDocumentStorage;
use atproto_identity::storage::DidDocumentStorage;
use atproto_identity::model::Document;
use std::num::NonZeroUsize;
use std::collections::HashMap;

let storage = LruDidDocumentStorage::new(NonZeroUsize::new(100).unwrap());

// Cache miss - DID not in cache
let document = storage.get_document_by_did("did:plc:bv6ggog3tya2z3vxsub7hnal").await?;
assert_eq!(document, None);

// Add document to cache
let doc = Document {
    context: vec![],
    id: "did:plc:bv6ggog3tya2z3vxsub7hnal".to_string(),
    also_known_as: vec!["at://alice.bsky.social".to_string()],
    service: vec![],
    verification_method: vec![],
    extra: HashMap::new(),
};
storage.store_document(doc.clone()).await?;

// Cache hit - DID found in cache
let document = storage.get_document_by_did("did:plc:bv6ggog3tya2z3vxsub7hnal").await?;
assert_eq!(document.as_ref().map(|d| &d.id), Some(&doc.id));
Source§

fn store_document<'life0, 'async_trait>( &'life0 self, document: Document, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Stores or updates a DID document in the LRU cache.

This method stores a complete DID document in the cache. If the DID already exists in the cache, its document is updated and the entry is moved to the front of the LRU order. If the DID is new and the cache is at capacity, the least recently used entry is evicted to make room.

§Arguments
  • document - The complete DID document to store. The document’s id field will be used as the storage key.
§Returns
  • Ok(()) - If the document was successfully stored
  • Err(error) - If an error occurs (primarily mutex poisoning, which is very rare)
§Cache Behavior
  • If the cache is at capacity and this is a new DID, the least recently used entry is evicted
  • The new or updated entry is placed at the front of the LRU order
  • Existing entries with the same DID are updated in place
§Examples
use atproto_identity::storage_lru::LruDidDocumentStorage;
use atproto_identity::storage::DidDocumentStorage;
use atproto_identity::model::Document;
use std::num::NonZeroUsize;
use std::collections::HashMap;

let storage = LruDidDocumentStorage::new(NonZeroUsize::new(2).unwrap()); // Small cache for demo

// Add first document
let doc1 = Document {
    context: vec![],
    id: "did:plc:user1".to_string(),
    also_known_as: vec!["at://alice.bsky.social".to_string()],
    service: vec![],
    verification_method: vec![],
    extra: HashMap::new(),
};
storage.store_document(doc1).await?;
assert_eq!(storage.len(), 1);

// Add second document
let doc2 = Document {
    context: vec![],
    id: "did:plc:user2".to_string(),
    also_known_as: vec!["at://bob.bsky.social".to_string()],
    service: vec![],
    verification_method: vec![],
    extra: HashMap::new(),
};
storage.store_document(doc2).await?;
assert_eq!(storage.len(), 2);

// Add third document - this will evict the least recently used entry (user1)
let doc3 = Document {
    context: vec![],
    id: "did:plc:user3".to_string(),
    also_known_as: vec!["at://charlie.bsky.social".to_string()],
    service: vec![],
    verification_method: vec![],
    extra: HashMap::new(),
};
storage.store_document(doc3).await?;
assert_eq!(storage.len(), 2); // Still at capacity

// user1 should be evicted
let document = storage.get_document_by_did("did:plc:user1").await?;
assert_eq!(document, None);

// user2 and user3 should still be present
let doc2_retrieved = storage.get_document_by_did("did:plc:user2").await?;
let doc3_retrieved = storage.get_document_by_did("did:plc:user3").await?;
assert!(doc2_retrieved.is_some());
assert!(doc3_retrieved.is_some());
Source§

fn delete_document_by_did<'life0, 'life1, 'async_trait>( &'life0 self, did: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Deletes a DID document from the LRU cache by DID.

This method removes a DID document from the cache. If the DID exists in the cache, it is removed entirely, freeing up space for new entries.

§Arguments
  • did - The DID identifying the document to delete
§Returns
  • Ok(()) - If the document was successfully deleted or didn’t exist
  • Err(error) - If an error occurs (primarily mutex poisoning, which is very rare)
§Cache Behavior
  • If the DID exists in the cache, it is removed completely
  • If the DID doesn’t exist, the operation succeeds without error
  • Removing entries frees up capacity for new entries
§Examples
use atproto_identity::storage_lru::LruDidDocumentStorage;
use atproto_identity::storage::DidDocumentStorage;
use atproto_identity::model::Document;
use std::num::NonZeroUsize;
use std::collections::HashMap;

let storage = LruDidDocumentStorage::new(NonZeroUsize::new(100).unwrap());

// Add a document
let document = Document {
    context: vec![],
    id: "did:plc:bv6ggog3tya2z3vxsub7hnal".to_string(),
    also_known_as: vec!["at://alice.bsky.social".to_string()],
    service: vec![],
    verification_method: vec![],
    extra: HashMap::new(),
};
storage.store_document(document).await?;
let retrieved = storage.get_document_by_did("did:plc:bv6ggog3tya2z3vxsub7hnal").await?;
assert!(retrieved.is_some());

// Delete the document
storage.delete_document_by_did("did:plc:bv6ggog3tya2z3vxsub7hnal").await?;
let retrieved = storage.get_document_by_did("did:plc:bv6ggog3tya2z3vxsub7hnal").await?;
assert_eq!(retrieved, None);

// Deleting non-existent entry is safe
storage.delete_document_by_did("did:plc:nonexistent").await?;

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,