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
impl LruDidDocumentStorage
Sourcepub fn new(capacity: NonZeroUsize) -> Self
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
Sourcepub fn len(&self) -> usize
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);Sourcepub fn is_empty(&self) -> bool
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());Sourcepub fn capacity(&self) -> NonZeroUsize
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);Sourcepub fn clear(&self)
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
impl Clone for LruDidDocumentStorage
Source§fn clone(&self) -> LruDidDocumentStorage
fn clone(&self) -> LruDidDocumentStorage
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl DidDocumentStorage for LruDidDocumentStorage
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,
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 cacheOk(None)- If the DID is not found in the cacheErr(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,
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’sidfield will be used as the storage key.
§Returns
Ok(())- If the document was successfully storedErr(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,
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 existErr(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?;