ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

add two-tier caching

Changed files
+84 -28
netlify
functions
+84 -28
netlify/functions/session.ts
··· 13 return key; 14 } 15 16 - // In-memory cache for profile data (lives for the function instance lifetime) 17 const profileCache = new Map<string, { data: any; timestamp: number }>(); 18 - const CACHE_TTL = 5 * 60 * 1000; // 5 minutes 19 20 export const handler: Handler = async (event: HandlerEvent): Promise<HandlerResponse> => { 21 try { ··· 30 }; 31 } 32 33 - // Get the DID from our simple session store 34 - const userSession = await userSessions.get(sessionId); 35 - if (!userSession) { 36 - return { 37 - statusCode: 401, 38 - headers: { 'Content-Type': 'application/json' }, 39 - body: JSON.stringify({ error: 'Invalid or expired session' }), 40 - }; 41 } 42 43 - // Check cache first 44 - const cached = profileCache.get(userSession.did); 45 - if (cached && Date.now() - cached.timestamp < CACHE_TTL) { 46 - console.log('Returning cached profile for', userSession.did); 47 return { 48 statusCode: 200, 49 headers: { 50 'Content-Type': 'application/json', 51 'Access-Control-Allow-Origin': '*', 52 'Cache-Control': 'private, max-age=300', // Browser can cache for 5 minutes 53 }, 54 body: JSON.stringify(cached.data), 55 }; 56 } 57 58 - // If not in cache, fetch full profile 59 try { 60 const config = getOAuthConfig(); 61 const normalizedKey = normalizePrivateKey(process.env.OAUTH_PRIVATE_KEY!); ··· 82 }); 83 84 // Restore OAuth session 85 - const oauthSession = await client.restore(userSession.did); 86 87 // Create agent from OAuth session 88 const agent = new Agent(oauthSession); 89 90 // Get profile 91 - const profile = await agent.getProfile({ actor: userSession.did }); 92 93 const profileData = { 94 - did: userSession.did, 95 handle: profile.data.handle, 96 displayName: profile.data.displayName, 97 avatar: profile.data.avatar, 98 description: profile.data.description, 99 }; 100 101 - // Cache the profile data 102 - profileCache.set(userSession.did, { 103 data: profileData, 104 - timestamp: Date.now(), 105 }); 106 107 - // Clean up old cache entries (simple cleanup) 108 if (profileCache.size > 100) { 109 - const now = Date.now(); 110 - for (const [did, entry] of profileCache.entries()) { 111 - if (now - entry.timestamp > CACHE_TTL) { 112 - profileCache.delete(did); 113 } 114 } 115 } ··· 119 headers: { 120 'Content-Type': 'application/json', 121 'Access-Control-Allow-Origin': '*', 122 - 'Cache-Control': 'private, max-age=300', // Browser can cache for 5 minutes 123 }, 124 body: JSON.stringify(profileData), 125 }; ··· 132 headers: { 133 'Content-Type': 'application/json', 134 'Access-Control-Allow-Origin': '*', 135 }, 136 body: JSON.stringify({ 137 - did: userSession.did, 138 // Profile data unavailable 139 }), 140 };
··· 13 return key; 14 } 15 16 + // ENHANCED: Two-tier cache system 17 + // Tier 1: In-memory cache for profile data (lives for function instance) 18 const profileCache = new Map<string, { data: any; timestamp: number }>(); 19 + const PROFILE_CACHE_TTL = 5 * 60 * 1000; // 5 minutes 20 + 21 + // Tier 2: Session metadata cache (DID -> basic info, faster than full OAuth restore) 22 + const sessionMetadataCache = new Map<string, { 23 + did: string; 24 + lastSeen: number; 25 + profileFetchNeeded: boolean; 26 + }>(); 27 28 export const handler: Handler = async (event: HandlerEvent): Promise<HandlerResponse> => { 29 try { ··· 38 }; 39 } 40 41 + // OPTIMIZATION: Check session metadata cache first (avoids DB query) 42 + const cachedMetadata = sessionMetadataCache.get(sessionId); 43 + const now = Date.now(); 44 + 45 + let did: string; 46 + 47 + if (cachedMetadata && (now - cachedMetadata.lastSeen < 60000)) { 48 + // Session seen within last minute, trust the cache 49 + did = cachedMetadata.did; 50 + console.log('Session metadata from cache'); 51 + } else { 52 + // Need to verify session from database 53 + const userSession = await userSessions.get(sessionId); 54 + if (!userSession) { 55 + // Clear stale cache entry 56 + sessionMetadataCache.delete(sessionId); 57 + return { 58 + statusCode: 401, 59 + headers: { 'Content-Type': 'application/json' }, 60 + body: JSON.stringify({ error: 'Invalid or expired session' }), 61 + }; 62 + } 63 + 64 + did = userSession.did; 65 + 66 + // Update session metadata cache 67 + sessionMetadataCache.set(sessionId, { 68 + did, 69 + lastSeen: now, 70 + profileFetchNeeded: true 71 + }); 72 + 73 + // Cleanup: Remove old session metadata entries 74 + if (sessionMetadataCache.size > 200) { 75 + for (const [sid, meta] of sessionMetadataCache.entries()) { 76 + if (now - meta.lastSeen > 300000) { // 5 minutes 77 + sessionMetadataCache.delete(sid); 78 + } 79 + } 80 + } 81 } 82 83 + // Check profile cache (Tier 1) 84 + const cached = profileCache.get(did); 85 + if (cached && now - cached.timestamp < PROFILE_CACHE_TTL) { 86 + console.log('Returning cached profile for', did); 87 + 88 + // Update session metadata last seen 89 + const meta = sessionMetadataCache.get(sessionId); 90 + if (meta) { 91 + meta.lastSeen = now; 92 + } 93 + 94 return { 95 statusCode: 200, 96 headers: { 97 'Content-Type': 'application/json', 98 'Access-Control-Allow-Origin': '*', 99 'Cache-Control': 'private, max-age=300', // Browser can cache for 5 minutes 100 + 'X-Cache-Status': 'HIT' 101 }, 102 body: JSON.stringify(cached.data), 103 }; 104 } 105 106 + // Cache miss - fetch full profile 107 try { 108 const config = getOAuthConfig(); 109 const normalizedKey = normalizePrivateKey(process.env.OAUTH_PRIVATE_KEY!); ··· 130 }); 131 132 // Restore OAuth session 133 + const oauthSession = await client.restore(did); 134 135 // Create agent from OAuth session 136 const agent = new Agent(oauthSession); 137 138 // Get profile 139 + const profile = await agent.getProfile({ actor: did }); 140 141 const profileData = { 142 + did: did, 143 handle: profile.data.handle, 144 displayName: profile.data.displayName, 145 avatar: profile.data.avatar, 146 description: profile.data.description, 147 }; 148 149 + // Cache the profile data (Tier 1) 150 + profileCache.set(did, { 151 data: profileData, 152 + timestamp: now, 153 }); 154 155 + // Update session metadata (Tier 2) 156 + const meta = sessionMetadataCache.get(sessionId); 157 + if (meta) { 158 + meta.lastSeen = now; 159 + meta.profileFetchNeeded = false; 160 + } 161 + 162 + // Clean up old profile cache entries 163 if (profileCache.size > 100) { 164 + for (const [cachedDid, entry] of profileCache.entries()) { 165 + if (now - entry.timestamp > PROFILE_CACHE_TTL) { 166 + profileCache.delete(cachedDid); 167 } 168 } 169 } ··· 173 headers: { 174 'Content-Type': 'application/json', 175 'Access-Control-Allow-Origin': '*', 176 + 'Cache-Control': 'private, max-age=300', 177 + 'X-Cache-Status': 'MISS' 178 }, 179 body: JSON.stringify(profileData), 180 }; ··· 187 headers: { 188 'Content-Type': 'application/json', 189 'Access-Control-Allow-Origin': '*', 190 + 'X-Cache-Status': 'ERROR' 191 }, 192 body: JSON.stringify({ 193 + did: did, 194 // Profile data unavailable 195 }), 196 };