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 13 return key; 14 14 } 15 15 16 - // In-memory cache for profile data (lives for the function instance lifetime) 16 + // ENHANCED: Two-tier cache system 17 + // Tier 1: In-memory cache for profile data (lives for function instance) 17 18 const profileCache = new Map<string, { data: any; timestamp: number }>(); 18 - const CACHE_TTL = 5 * 60 * 1000; // 5 minutes 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 + }>(); 19 27 20 28 export const handler: Handler = async (event: HandlerEvent): Promise<HandlerResponse> => { 21 29 try { ··· 30 38 }; 31 39 } 32 40 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 + // 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 + } 41 81 } 42 82 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); 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 + 47 94 return { 48 95 statusCode: 200, 49 96 headers: { 50 97 'Content-Type': 'application/json', 51 98 'Access-Control-Allow-Origin': '*', 52 99 'Cache-Control': 'private, max-age=300', // Browser can cache for 5 minutes 100 + 'X-Cache-Status': 'HIT' 53 101 }, 54 102 body: JSON.stringify(cached.data), 55 103 }; 56 104 } 57 105 58 - // If not in cache, fetch full profile 106 + // Cache miss - fetch full profile 59 107 try { 60 108 const config = getOAuthConfig(); 61 109 const normalizedKey = normalizePrivateKey(process.env.OAUTH_PRIVATE_KEY!); ··· 82 130 }); 83 131 84 132 // Restore OAuth session 85 - const oauthSession = await client.restore(userSession.did); 133 + const oauthSession = await client.restore(did); 86 134 87 135 // Create agent from OAuth session 88 136 const agent = new Agent(oauthSession); 89 137 90 138 // Get profile 91 - const profile = await agent.getProfile({ actor: userSession.did }); 139 + const profile = await agent.getProfile({ actor: did }); 92 140 93 141 const profileData = { 94 - did: userSession.did, 142 + did: did, 95 143 handle: profile.data.handle, 96 144 displayName: profile.data.displayName, 97 145 avatar: profile.data.avatar, 98 146 description: profile.data.description, 99 147 }; 100 148 101 - // Cache the profile data 102 - profileCache.set(userSession.did, { 149 + // Cache the profile data (Tier 1) 150 + profileCache.set(did, { 103 151 data: profileData, 104 - timestamp: Date.now(), 152 + timestamp: now, 105 153 }); 106 154 107 - // Clean up old cache entries (simple cleanup) 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 108 163 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); 164 + for (const [cachedDid, entry] of profileCache.entries()) { 165 + if (now - entry.timestamp > PROFILE_CACHE_TTL) { 166 + profileCache.delete(cachedDid); 113 167 } 114 168 } 115 169 } ··· 119 173 headers: { 120 174 'Content-Type': 'application/json', 121 175 'Access-Control-Allow-Origin': '*', 122 - 'Cache-Control': 'private, max-age=300', // Browser can cache for 5 minutes 176 + 'Cache-Control': 'private, max-age=300', 177 + 'X-Cache-Status': 'MISS' 123 178 }, 124 179 body: JSON.stringify(profileData), 125 180 }; ··· 132 187 headers: { 133 188 'Content-Type': 'application/json', 134 189 'Access-Control-Allow-Origin': '*', 190 + 'X-Cache-Status': 'ERROR' 135 191 }, 136 192 body: JSON.stringify({ 137 - did: userSession.did, 193 + did: did, 138 194 // Profile data unavailable 139 195 }), 140 196 };