[MIRROR ONLY] A correct and efficient ATProto blob proxy for secure content delivery. codeberg.org/Blooym/porxie
at main 182 lines 5.4 kB view raw
1use crate::types::blob_cid::BlobCid; 2use anyhow::{Context, Result}; 3use axum::http::HeaderMap; 4use bytes::Bytes; 5use jacquard_common::types::did::Did; 6use moka::{future::Cache as MokaCache, policy::EvictionPolicy}; 7use reqwest::Url; 8use std::{cmp, num::NonZeroU64, time::Duration}; 9 10// Blob Content Cache 11 12type BlobContentCache = MokaCache<BlobCid, CachedBlobData>; 13 14#[derive(Clone)] 15pub struct CachedBlobData { 16 pub bytes: Bytes, 17 pub headers: HeaderMap, 18} 19 20#[must_use] 21fn build_blob_content_cache(mem_capacity: u64, ttl: Duration) -> BlobContentCache { 22 tracing::debug!( 23 "building blob content cache with a mem_capacity of {mem_capacity} bytes and a ttl of {}s", 24 ttl.as_secs() 25 ); 26 27 BlobContentCache::builder() 28 .name("blob-content") 29 .weigher(|_key, value| { 30 (value.bytes.len() 31 + value 32 .headers 33 .iter() 34 .map(|(k, v)| k.as_str().len() + v.len() + 32) 35 .sum::<usize>()) 36 .try_into() 37 .unwrap_or(u32::MAX) 38 }) 39 .eviction_policy(EvictionPolicy::tiny_lfu()) 40 .max_capacity(mem_capacity) 41 .time_to_idle(ttl) 42 .build() 43} 44 45// Blob Ownership Cache 46 47type BlobOwnershipCache = MokaCache<(BlobCid, Did<'static>), ()>; 48 49#[must_use] 50fn build_blob_ownership_cache(mem_capacity: u64, ttl: Duration) -> BlobOwnershipCache { 51 tracing::debug!( 52 "building blob ownership cache with a mem_capacity of {mem_capacity} bytes and a ttl of {}s", 53 ttl.as_secs() 54 ); 55 56 BlobOwnershipCache::builder() 57 .name("blob-ownership") 58 .weigher(|key, _value| { 59 (key.0.encoded_len() + key.1.len()) 60 .try_into() 61 .unwrap_or(u32::MAX) 62 }) 63 .eviction_policy(EvictionPolicy::tiny_lfu()) 64 .max_capacity(mem_capacity) 65 .time_to_live(ttl) 66 .support_invalidation_closures() 67 .build() 68} 69 70// Policy Cache 71 72type BlobPolicyCache = MokaCache<(Did<'static>, BlobCid), CachedBlobPolicy>; 73 74#[derive(Debug, Copy, Clone)] 75pub struct CachedBlobPolicy { 76 pub can_serve: bool, 77} 78 79#[must_use] 80pub fn build_blob_policy_cache(mem_capacity: u64, ttl: Duration) -> BlobPolicyCache { 81 tracing::debug!( 82 "building blob policy cache with a mem_capacity of {mem_capacity} bytes and a ttl of {}s", 83 ttl.as_secs() 84 ); 85 86 BlobPolicyCache::builder() 87 .name("blob-policy") 88 .weigher(|key, _value| { 89 (key.0.len() + key.1.encoded_len()) 90 .try_into() 91 .unwrap_or(u32::MAX) 92 }) 93 .eviction_policy(EvictionPolicy::tiny_lfu()) 94 .max_capacity(mem_capacity) 95 .time_to_live(ttl) 96 .support_invalidation_closures() 97 .build() 98} 99 100// Identity DID <-> PDS Cache 101 102type IdentityCache = MokaCache<Did<'static>, Url>; 103 104#[must_use] 105pub fn build_identity_cache(mem_capacity: u64, ttl: Duration) -> IdentityCache { 106 tracing::debug!( 107 "building identity cache with a mem_capacity of {mem_capacity} bytes and a ttl of {}s", 108 ttl.as_secs() 109 ); 110 111 IdentityCache::builder() 112 .name("identity") 113 .weigher(|key, value| { 114 (key.len() + value.as_str().len()) 115 .try_into() 116 .unwrap_or(u32::MAX) 117 }) 118 .eviction_policy(EvictionPolicy::tiny_lfu()) 119 .max_capacity(mem_capacity) 120 .time_to_live(ttl) 121 .support_invalidation_closures() 122 .build() 123} 124 125// Builder 126 127pub struct Caches { 128 pub blob_content: BlobContentCache, 129 pub blob_ownership: BlobOwnershipCache, 130 pub blob_policy: BlobPolicyCache, 131 pub identity: IdentityCache, 132} 133 134pub struct CacheBuildOptions { 135 pub memory_capacity: NonZeroU64, 136 pub blob_content_ttl: Duration, 137 pub blob_ownership_ttl: Duration, 138 pub blob_policy_ttl: Duration, 139 pub identity_cache_ttl: Duration, 140} 141 142pub fn build_caches(options: &CacheBuildOptions) -> Result<Caches> { 143 let sizes = { 144 struct CacheSizes { 145 pub blob: u64, 146 pub ownership: u64, 147 pub policy: u64, 148 pub identity: u64, 149 } 150 let policy = cmp::min( 151 (options.memory_capacity.get() as f64 * 0.06) as u64, 152 48_000_000, 153 ); // 6% up to 48mb max. 154 let ownership = cmp::min( 155 (options.memory_capacity.get() as f64 * 0.06) as u64, 156 48_000_000, 157 ); // 6% up to 48mb max. 158 let identity = cmp::min( 159 (options.memory_capacity.get() as f64 * 0.06) as u64, 160 48_000_000, 161 ); // 6% up to 48mb max. 162 CacheSizes { 163 policy, 164 ownership, 165 identity, 166 blob: options 167 .memory_capacity 168 .get() 169 .checked_sub(policy) 170 .and_then(|r| r.checked_sub(ownership)) 171 .and_then(|r| r.checked_sub(identity)) 172 .context("cache size allocation underflow")?, 173 } 174 }; 175 176 Ok(Caches { 177 blob_content: build_blob_content_cache(sizes.blob, options.blob_content_ttl), 178 blob_ownership: build_blob_ownership_cache(sizes.ownership, options.blob_ownership_ttl), 179 blob_policy: build_blob_policy_cache(sizes.policy, options.blob_policy_ttl), 180 identity: build_identity_cache(sizes.identity, options.identity_cache_ttl), 181 }) 182}